The Open Toolkit Manual (German)

This page is the German Translation of The Open Toolkit Manual.

Das Handbuch ist nicht komplett. Experimentelle Seiten können hier angelegt werden. Weitere Übersetzungen sind hier zu finden..

Willkommen und danke für die Nutzung der Open Toolkit Bibliothek!

Dieses Handbuch wird Ihnen Schritt für Schritt zeigen, wie man OpenTK Projekte entwickelt. Sie werden lernen wie man ein neues Projekt erzeugt, wie man die von OpenTK bereitgestellten Werkzeuge erfolgreich nutzt und wie man seine Projekte an die Endanwender verteilt. Außerdem werden Sie Informationen über performanten Code und Cross-Plattform Kompatibilität finden.

Das Handbuch ist so geschrieben, dass man stets von einem Kapitel ins nächste springen kann; oder man liest es vom Anfang bis Ende. Übrigens ist es an jeder Stelle möglich einen Kommentar zu hinterlassen - wir versuchen die Dokumenation stets zu verbessern, konstruktives Feedback hilft nicht nur Ihnen, sondern auch zukünftigen OpenTK Nutzern.

Wir hoffen, dass sich die Zeit auszahlt, die Sie zum Lesen aufbringen. Los gehts!

Faustregeln

This page is the German Translation of Rules of thumb.

Dieser Text ist gedacht für Leute, die bisher mit C/OpenGL gearbeitet haben.

Obwohl OpenTK automatisch alle GL/AL Funktionsaufrufe von C# nach C umwandelt, arbeiten einige Dinge in der Welt der verwalteten Sprachen etwas anders vergleichen mit simplem C.

Faustregeln

  1. Bevorzugen Sie Server-Speicher statt Klientspeicher

    Ein paar alte OpenGL-Funktionen nutzen Zeiger auf Speicher, der vom Nutzer verwaltet ist. Das bekannteste Beispiel sind Vertex Array, die Familie der GL.*Pointer-Funktionen.

    Dieser Ansatz kann in einer Garbage Collector Umgebung (wie .NET es ist) nicht genutzt werden, da der Garbage Collector (GC) die Inhalte des Buffers eventuell verschiebt. Es ist sehr empfehlenswert alte Vertex Arrays mit Vertex Buffer Objects zu ersetzen, welche dieses Problem nicht haben.

    Anders als OpenGL 2.1, wird OpenGL 3.0 keinerlei solcher Funktionen mit Nutzung des Klientspeichers beinhalten.

  2. Versuchen Sie die Anzahl der OpenGL-Funktionsaufrufe per Frame zu minimieren

    Dies ist für jede Programmierumgebung wichtig, die OpenGL nutzt, für OpenTK jedoch sogar noch ein wenig wichtiger: Obwohl die OpenGL/AL Bindings ziemlich optimiert sind, kostet der Übergang von der verwalteten zur unverwalteten Welt einen kleinen, aber messbaren, Overhead (Einbußen an Performance).

    Um den Einfluss des Overheads zu minimieren, versuchem Sie die Anzahl der OpenGL/OpenAL Aufrufe zu verringern. Eine gute Faustregel ist es nicht mehr als 300-500 OpenGL-Aufrufe per Frame zu haben. Dies kann durch Vermeidung des Immediate Mode zu Gunsten von Display Listen oder VBO's geschehen.

  3. Nutzen Sie ref Funktions-Überladungen für maximale Performance bei Mathe-Routinen
    Dies ist wichtig weil Vector3, Matrix3 etc. Strukturen und keine Klassen sind. Klassen-Objekte werden standardmäßig per Referenz übergeben.

    Vector3 v1 = Vector3.UnitX;
    Vector3 v2 = Vector3.UnitZ;
    Vector3 v3 = Vector3.Zero;
    v3 = v1 + v2;                        // requires three copies; slow.
    Vector3.Add(ref v1, ref v2, out v3); // nothing is copied; fast!

    Das gleiche trifft für das Aufrufen von OpenGL-Funktionen zu:

    GL.Vertex3(ref v1.X);  // pass a pointer to v1; fast!
    GL.Vertex3(v1);        // copy the whole v1 structure; slower!

Performance messen

  1. GameWindows besitzt einen eingebauten Frames-pro-Sekunde-Zähler. Wie auch immer, GLControl hat keinen.
  2. Eine einfache und bequeme Möglichkeit zur Messung der Performance Ihres Codes stellt die .NET 2.0 / Mono 1.2.4 Stopwatch Klasse dar. Benutzen Sie sie wie folgt:
    Stopwatch sw = new Stopwatch();
    sw.Start();
    // Your code goes here.
    sw.Stop();
    double ms = sw.Elapsed.TotalMilliseconds;

    Anmerkung: Vermeiden Sie die Nutzung von DateTime.Now oder anderer DateTime-basierte Methoden für Zeitspannen unter ein paar Sekunden, da die Präzisision bei 10ms oder schlecher liegt. (Das Gerücht besagt, dass von Zeit zu Zeit sogar schlechter wird!) DateTime für das Messen von langen Operationen (einige Sekunden) zu nutzen ist OK.

  3. Wenn Sie Windows nutzen, können Sie Fraps downloaden, um die Anzahl der Frames pro Sekunde zu messen, die in Ihrer Anwendung gezeichnet werden.

Referenzen:
http://www.gamedev.net/community/forums/topic.asp?topic_id=484756&whichp...

Kapitel 1: Installation

This page is the German Translation of Chapter 1: Installation
Prerequisites

Vorraussetzungen

OpenTK ist eine C# Library welches für .Net 2.0 geschrieben wurde. Um diese nutzen zu können, benötigen Sie die erforderlichen Hardware-Treiber für OpenGL (Grafik), OpenAL (Audio) und OpenCL (Computing), abhängig vom Verwendungszweck Ihres OpenTK Projektes:

Bei den meisten Betriebssysteme sind die Runtimes schon vorinstalliert. Bitte beachten Sie, dass OpenTK nicht mit .Net 1.1 oder .Net CF kompatibel ist.

Installation

OpenTK ist in Zwei Sorten unterteilt. Einmal als Windows-Installer und einmal als Zip-Archiv.

Windows Nutzer werden wohl den Installer bevorzugen. Führen Sie diesen einfach aus und befolgen Sie die angezeigten Anweisungen. Sie benötigen für den Installer keine Administrationsrechte, es sei denn Sie wählen bei der Installation OpenAL und/oder das GLSL-Plugin für Visual Studio Professional. Nachdem die Installation erfolgreich beendet ist, wird es Ihnen möglich sein OpenTK in dein Prjekt als Referenz hinzuzufügen (Klicken Sie dazu mit der Rechten Maustaste auf "Verweise"->"Verweis hinzufügen" und wählen Sie unterm ".Net"-Tab OpenTK heraus).

Unter Linux und Mac Os X entpacken Sie das Archiv in einen beliebegen Ordner und fügen Sie OpenTK.dll in Ihre Projek.st Referenz hinzu. Sie finden die Datei unter Binaries/OpenTK/Release. Daraufhin fügen Sie die OpenTK.dll.comfig in Ihr Projekt hinzu und geben beim IDE die Anweisung, es in Ihr Projektverzeichnis hinein zu kopieren. Dies ist für eine Fehlerfreie Funktionalität notwendig!

Erstellen

Sie können OpenTK wahlweise über ein Kommandofenster oder einem IDE erstellen:

  • Um es in einem Kommandofenster zu erstellen benötigen Sie entweder msbuild (.Net) oder xbuild (Mono). Um im msbuild zu erstellen, öffnen Sie einen "Visual Studio Kommando Fenster" , wechseln zum OpenTK verzeichnis und geben folgendes ein:
    msbuild OpenTK.sln /p:Configuration=Release
  • Stellen Sie unter xbuild sicher, dass Sie im OpenTK Verzeichnis sind und tippen Sie folgendes ein:
    xbuild OpenTK.sln /p:Configuration=Release
  • Um im IDE zu erstellen, benötigen Sie entweder Visual Studio 2010 oder Mono Develop 2.4. Drücken Sie dann einfach auf Erstellen, nachdem Sie OpenTK.sln geöffnet haben.

Achtung: Wenn Sie eine ältere Version von Mono nutzen als 2.6, wird OpenTK direkt vom Sourcecontroll erstellt. Um dies zu umgehen öffnen Sie im IDE OpenTK.sln und drücken mit der rechten Maustaste auf "Build.UpdateVersion" und wählen "Run". Nun können Sie OpenTK normal erstellen.

Fehlerdiagnose

This page is the German Translation of Troubleshooting.

Die meisten Probleme beim Ausführen von OpenTK-basierten Anwendungen haben etwas mit dem Fehlen der richtigen Treiber beim Zielsystem zu tun.

OpenTK benötigt die folgenden installierten Komponenten:

  • Mono oder .NET.
  • Ein OpenGL-Treiber für Ihre Grafikkarte.
  • Ein OpenAL-Treiber für Ihr Betriebssystem.

Nachfolgend finden Sie einige hilfreiche Links. Bemerkung: Einige der Seiten erfordern aktiviertes Javascript.

Mono

Novell (Linux, Mac & Windows) http://www.go-mono.com/mono-downloads/download.html

.Net

Microsoft (Windows) http://www.microsoft.com/downloads/details.aspx?displaylang=de&FamilyID=...

OpenAL

Bemerkung: Es ist egal von welchem Hersteller Ihre Soundkarte ist, wählen Sie einfach das passende Betriebssystem.

Creative Labs (Mac & Windows) http://www.openal.org/downloads.html
Strangesoft (Linux) http://kcat.strangesoft.net/openal.html

OpenGL

ATi (Linux, Mac & Windows) http://ati.amd.com/support/driver.html
NVIDIA (Linux & Windows) http://www.nvidia.com/Download/index.aspx?lang=de
Intel (Windows) http://downloadcenter.intel.com/
Intel (Linux) http://intellinuxgraphics.org/download.html
Mesa 3D (software rendering) http://sourceforge.net/project/showfiles.php?group_id=3

Wenn Sie einen Laptop mit Nvidia-Grafikkarte haben, können Sie aktuelle Treiber hier herbekommen: http://www.laptopvideo2go.com

Letze Aktualisierung dieser Links: März 2008

Tags for searches:
help problem error outdated trouble crash fail failure exception abort opengl openal driver ati intel nvidia

Linux

This page is the German Translation of Linux.

Mono installieren

Wenn Sie eine aktuelle Linux-Distribution nutzen, müssten bereits alle erforderlichen Abhängigkeiten für OpenTK Projekte erfüllt sein: Die Mono Laufzeitumgebung und der Mono Compiler. Führen Sie den Befehl "mono --version" und "gmcs --version" aus und überprüfen Sie, ob die Ausgabe ungefähr so aussieht:

$ mono --version
Mono JIT compiler version 1.2.6 (tarball)
Copyright (C) 2002-2007 Novell, Inc and Contributors. www.mono-project.com
        TLS:           __thread
        GC:            Included Boehm (with typed GC)
        SIGSEGV:       altstack
        Notifications: epoll
        Architecture:  amd64
        Disabled:      none
 
$ gmcs --version
Mono C# compiler version 1.2.6.0

Wenn einer oder beide der Befehle fehlschlagen, müssen Sie Mono nachinstallieren. Mono Pakete müssten in dem Paket-Manager Ihrer Distribution verfügbar sein, sodass folgende Zeile funktionieren müsste:

# Ubuntu und andere .deb-basierende Distributionen
sudo apt-get install mono mono-gmcs
# or
su -c "apt-get install mono mono-gmcs"
 
# Fedora Core und .rpm-basierende Distributionen
su -c "yum install mono mono-gmcs"

Wenn kein Mono Paket verfügbar ist oder es schlicht veraltet ist (mono --version gibt eine Version kleiner als 1.2.6 zurück), sollten Sie Mono selber kompilieren. Es gibt eine Nachricht im Support-Forum, die den Kompiliervorgang von Mono beschreibt.

Alternativ findet man ein Setup für alle gängigen Linux-Distributionen auf der Download-Seite des Mono-Projektes.

Nutzen eines binären Releases

Laden Sie das aktuellste OpenTK Release von der Sourceforge Projektseite herunter und entpacken Sie es:

tar -xvf opentk-0.3.13-mono.tar.gz

Es wird ein neues Verzeichnis mit fünf Unterordnern erzeugt: "Documentation", "Binaries", "Source", "Build" und "Quickstart". Versuchen Sie die Beispiele aus dem zweiten Ordner auszuführen, um zu überprüfen, ob alles funktioniert:

cd opentk-0.9/Binaries/Examples/mono
mono Examples.exe

Nun öffnet sich hoffentlich ein neues Fenster, welches alle verfügbaren Beispiele auflistet. Falls nicht, werfen Sie einen Blick auf den Bereich "Fehlerdiagnose".

Der "Binaries/Libraries" Ordner enthält die Haupt-OpenTK-Assembly (OpenTK.dll) sowie die OpenTK.dll.config Datei - das ist alles was man benötigt, um OpenTK Projekte zu starten. Wenn Sie MonoDevelop nutzen, schauen Sie auch in den "Quickstart" Ordner, um ein fertiges Projekt zu sehen. Als letztes sollte Sie nicht vergessen in die "Release Notes" im "Documentation" Ordner zu schauen.

Fehlerdiagnose

Der folgender Fehler wurde auf Fedora Core 8 beim Ausführen der Examples.exe gemeldet:

Unhandled Exception: System.TypeInitializationException: An exception was thrown by the type initializer for System.Windows.Forms.Form ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.TypeInitializationException: An exception was thrown by the type initializer for System.Drawing.GDIPlus ---> System.DllNotFoundException: gdiplus.dll
  at (wrapper managed-to-native) System.Drawing.GDIPlus:GdiplusStartup (ulong&,System.Drawing.GdiplusStartupInput&,System.Drawing.GdiplusStartupOutput&)
  at System.Drawing.GDIPlus..cctor () [0x00000] --- End of inner exception stack trace ---

Dies wird durch einen fehlenden Eintrag in "etc/mono/config" verursacht. Um das Problem zu beseitigen, öffnen Sie die eben genannte Datei (als root) und fügen Sie die folgende Zeile hinzu:

<dllmap dll="gdiplus.dll" target="/usr/lib/libgdiplus.so.0"

Nun müssten die Beispiele funktionieren.

OpenTK eigenhändig kompilieren

Das Build-System von OpenTK nutzt NAnt, daher muss dieses zuerst installiert werden:

# Ubuntu
sudo apt-get install nant
 
# Debian
su -c "apt-get install nant"
 
# Fedora
su -c "yum install nant"

Ist das einmal geschafft, kann man den Quellcode entpacken und in den Build-Ordner wechseln:

unzip opentk-0.9.zip
cd opentk-0.9/Build
mono Build.exe mono

Nach dem Ende der Kompilation befinden sich die Build-Produkte im "Binaries"-Ordner. Um die Debug-Version zu kompilieren, hängen Sie "debug" an den Befehl an, sodass der Befehl wie folgt aussieht:

mono Build.exe mono debug

Windows

This page is the German Translation of Windows.

OpenTK hat bisher kein Installationspaket bzw. ein Setup. Stattdessen lädt man die OpenTK Binaries herunter and fügt "OpenTK.dll" als Verweis zu seinem Visual Studio/SharpDevelop/MonoDevelop Projekt hinzu (Binaries zuerst entpacken).

Außerdem ist es eine gute Idee "OpenTK.dll.config" in sein Projekt aufzunehmen und die Eigenschaft "In Ausgabeverzeichnis kopieren" auf "Immer kopieren" zu setzen. Ohne diese Konfiguration wird die Anwendung zwar unter Windows, dafür aber nicht unter Linux oder Mac OS X laufen, daher dies bitte nicht vergessen!

Als letztens sollte man noch die Eigenschaft "Lokale Kopie" des OpenTK-Verweises auf "True" setzen, um die Distribution der Anwendung zu vereinfachen.

Kapitel 2: OpenTK Klassen

This page is the German Translation of Chapter 2: OpenTK Classes.

Zunächst einmal, was ist eigentlich OpenTK?

Einfachste Definition: OpenTK ist ein kostenfreies Projekt, dank dem man OpenGL, OpenGL|ES, OpenCL und OpenAL API`s in Managed-Programmiersprachen nutzen kann.

OpenTK wurde als Experementelle Spaltung vom Tao-Framework im Sommer 2006 ins Leben gerufen. Das Ursprüngliche Ziel war es, einen sauberen Wrapper als Tao.OpenGL zu schaffen, allerdings wurde ´später weitaus mehr in den Fokus genommen: Jetzt ist es möglich verschiedene Khronus und Creative API´s zu nutzen und es sorgt nun für die notwendigen Initialisierungen der Logik für jedes diese API´s. So ist OpenTK mit anderen API´s wie Tao, SlimDX, SDL oder GLFW am nächsten.

im Gegensatz zu den anderen Projekten, bietet OpenTK eine einheitliche Schnittstelle, welche für eine Überlegenheit in der verwalteten Laufzeit sorgt.
Statt Pointers verwendet OpenTK generics. Anstatt von Constants verwendet OpenTK Typenreine Enum´s. Statt Funktionslisten, trennt OpenTK pro Funktion die dazugehörige Kategory. Es ist eine allgemeine Mathematische Library integriert, die von den API´s direkt nutzbar ist.

Eigenschaften:

  • Es ist Plattformübergreifend in C# geschrieben und in allen Managed-Sprachen verwendbar (F#, Boo, VB.Net, C++/CLI)
  • Einheitlich, sehr sauberer Code und RAD-Developement tauglich
  • Als einfaches Fenster oder auch Winforms-, WPF-, Gtk#-Einbindungen möglich
  • Ohne Neukompilierung Plattformübergreifend in .Net und Mono verwendbar
  • Große Plattformkompatibilität: Windows, Linux, Mac Os X, iPhone (?), Androit (?)

Mit OpenTK sind Spiele, Visualisierungstechniken und alle Sorten von Software realisierbar, die hohe Grafik, Audio und/oder Computing Ansprüche haben. Die License macht es möglich, Ihre Endanwendungen Kostenfrei und Kommerziell zu vertreiben.

Entwickeln einer Windows.Forms + GLControl basierten Anwendung

This page is the German Translation of Building a Windows.Forms+GLControl based application.

Dieses Tutorial geht davon aus, dass Sie bereits Erfahrung bei der Entwicklung von Windows.Forms Anwendungen mit Visual Studio 2005/C# haben und dass Sie ein Grundwissen über OpenGL besitzen. Außerdem wird davon ausgegangen, dass man den Artikel von oben nach unten durchliest, es ist ein Tutorial und keine Referenz.

Zum Anfang ist zu sagen, dass die Herangehensweise bei der Entwicklung einer Anwendung bzw. eines Spiel, welches GLControl in einer Windows.Form nutzt gänzlich anders ist im Vergleich zur Nutzung von GameWindow. GLControl arbeitet auf einer niedrigeren Erbene als GameWindow, sodass man z.B. die Zeitangaben selber zeichnen muss. Beim GameWindow bekommt man mehr Funktionen!

Wenn Sie bisher Spiele mittels einer Hauptschleife (C/SDL/Allegro etc.) programmiert haben, sollten Sie jetzt umdenken. Nun muss man sich stattdessen überlegen: Welche Ereignisse muss ich abfangen und welche Events muss ich wann auslösen?

Warum Windows.Forms+GLControl an Stelle von GameWindows nutzen?

Die erste Frage, die man sicht stellen muss, ist:
"Benötige ich wirklich die zusätzliche Komplexität von Windows.Forms + eingebettetes GLControl im Vergleich mit einem GameWindow in einem Fenster?"

Hier sind einige Gründe, die dafür sprechen:

  1. Sie wollen eine GUI/RAD-Anwendung entwickeln, die Windows.Forms Steuerelemente nutzt. Level Editoren oder Model-Viewer fallen in diese Kategorie während in einem Fenster laufende Spiele eher auf eine GameWindow-Anwendung hindeuten.
  2. Sie wollen ein OpenGL Rendering-Element in eine existierende Windows.Forms-Anwendung einbetten.
  3. Sie wollen eine Möglichkeit etwas in die OpenGL-Grafik per Drag & Drop hinein zu ziehen, z.B. eine existierende Model-Datei in einen Model-Viewer.

Wenn Sie mindestens einen Grund gefunden haben eine Windows.Forms+GLControl basierte Anwendung zu entwickeln, dann finden Sie nachfolgend die nächsten Schritte.

Das GLControl in ein Windows.Form integrieren
(An dieser Stelle wird davon ausgegangen, dass Sie Visual Studio 2005 Express Edition nutzen. Die Integration könnte bei Ihnen variieren, falls Sie Visual Studio 2008 oder MonoDevelop nutzen - aber die folgenden Sektionen sollten gleich sein, egal wie Sie das GLControl einfügen.)

Zu Beginn erzeugen Sie eine Form (Fenster), in das Sie später das GLControl platzieren werden. Klicken Sie mit der rechten Maustaste in den leeren Bereich der Toolbox, wählen Sie dann "Elemente auswählen ..." und suchen Sie nach OpenTK.dll. Stellen Sie sicher, dass "GLControl" wie im Bild unten abgebildet unter ".NET Framework-Komponenten" aufgelistet ist.

Nun platzieren Sie das GlControl in ihre Form so wie jedes andere .NET Steuerelement auch. Eine GLControl-Variable namens glControl1 wird jetzt zu Ihrer Form hinzugefügt.

Das erste was Sie bemerken werden, ist die komische Grafik innerhalb von glControl1. Unter der Haube nutzt GlControl einen sogenannten GLContext zum Rendern, der allerdings nur zur Laufzeit und noch nicht im Designer erzeugt wird. Kümmern Sie sich nicht drum.

Reihenfolge der Erzeugung
Den Fakt, dass der GLContext von glControl1 erst zur Laufzeit erzeugt wird, sollte man sich gut merken, denn die Eigenschaften von glControl1 können erst abgefragt oder geändert werden, wenn der GLContext erzeugt wurde. Das gleiche gilt für alle GL.*-Funktionen (oder Glu). Die Reihenfolge ist wie folgt:

  1. Der Konstruktor der Windows.Form wird ausgeführt. Zugriff auf glControl/GL nicht erlaubt
  2. Dann wird das Load-Ereignis der Form ausgelöst. Zugriff auf glControl/GL erlaubt
  3. Nachdem die Load-Ereignisbehandlung abgeschlossen ist, darf jeder Event Handler auf glControl/GL zugreifen.

Ein Ansatz zur Lösung des Problem ist es eine Instanzvariable bool loaded = false; in der Form zu haben, die im Load-Ereignis auf true gesetzt wird:

  using OpenTK.OpenGL;
  using OpenTK.OpenGL.Enums;
 
  public partial class Form1 : Form
  {
    bool loaded = false;
 
    public Form1()
    {
      InitializeComponent();
    }
 
    private void Form1_Load(object sender, EventArgs e)
    {
      loaded = true;
    }
  }

Nun muss man in jeden Ereignishandler, in dem man glControl1 nutzt, zuerst eine Sicherheitsprüfung einbauen:

     private void glControl1_Resize(object sender, EventArgs e)
    {
      if (!loaded)
        return;
    }

Ein einfacher Weg ein Load-Ereignishandler zur Form hinzuzufügen, ist das Eigenschaften-Fenster zu nutzen:

  1. Auf die Titelleiste von Form1 im Designer klicken
  2. Sicherstellen die Eigenschaften von Form1 werden im Eigenschaften-Fenster angezeigt
  3. Auf den "Ereignisse"-Button klicken
  4. Doppelt auf die freie Zelle rechts vom Load-Handler klicken, um die Ereignisbehandlungsroutine zu erzeugen

Hallo Welt!
Um nun etwas zu sehen, muss man den folgenden kleinen Codeschnipsel zum Paint-Ereignishandler des glControl1-Controls hinzufügen:

     private void glControl1_Paint(object sender, PaintEventArgs e)
    {
      if (!loaded) // Play nice
        return;
 
      GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
      glControl1.SwapBuffers();
    }

Ein schwarzes Rechteck. Beachten Sie, dass das GLControl einen Farb- sowie einen Z-Buffer hat, den man mittels GL.Clear() leert.

Der nächste Schritt wäre das Setzen der Farbe, die genutzt wird, um den Farbbuffer zu leeren. Ein möglicher Platz für die Intialisierung ist das Load Ereignis:

     private void Form1_Load(object sender, EventArgs e)
    {
      loaded = true;
      GL.ClearColor(Color.SkyBlue); // Yey! .NET Colors can be used directly!
    }

Initialisierung des Darstellungsfelds

Um OpenGL korrekt zu nutzen, erzeugen wir zuerst eine orthografische Projektionsmatrix mit Hilfe von Gl.Ortho(). Außerdem müssen wir GL.Viewport() aufrufen.

Fürs Erste tun wir diese Aufrufe zu dem anderen Initialisierungscode in das Load-Ereignis -- dabei ignorieren wir momentan noch den Fakt, dass der Nutzer das Fenster GLControl möglicherweise in der Größe verändert will. Später wird näher auf das Vergrößern/Verkleinern eingegangen.

Die Initialisierung des Darstellungsfeldes ist hier in eine einzelne Methode ausgelager, um die Lesbarkeit zu erhöhen.

     private void Form1_Load(object sender, EventArgs e)
    {
      loaded = true;
      GL.ClearColor(Color.SkyBlue);
      SetupViewport();
    }
 
    private void SetupViewport()
    {
      int w = glControl1.Width;
      int h = glControl1.Height;
      GL.MatrixMode(MatrixMode.Projection);
      GL.LoadIdentity();
      GL.Ortho(0, w, 0, h, -1, 1); // Bottom-left corner pixel has coordinate (0, 0)
      GL.Viewport(0, 0, w, h); // Use all of the glControl painting area
    }

Und zwischen Clear() und SwapBuffers() nun unser gelbdes Dreieck:

     private void glControl1_Paint(object sender, PaintEventArgs e)
    {
      if (!loaded)
        return;
 
      GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
      GL.MatrixMode(MatrixMode.Modelview);
      GL.LoadIdentity();
      GL.Color3(Color.Yellow);
      GL.Begin(BeginMode.Triangles);
      GL.Vertex2(10, 20);
      GL.Vertex2(100, 20);
      GL.Vertex2(100, 50);
      GL.End();
 
      glControl1.SwapBuffers();
    }

Voila!

Tastatureingaben
Nun soll das Dreieck animiert werden durch die Interaktion mit dem Nuter. Immer wenn die Leertaste gedrück wird, soll das Dreieck ein Pixel nach rechts verschoben werden.

Es gibt zwei Ansätze, um Tastatureingaben in GLControl-Szenarien abzufangen. Entweder man nutzt Windows.Forms Tastatur-Ereignisse oder man nutzt KeyboardDevice von OpenTK. Aber da der Rest des Programms Windows.Forms nutzt (unser Fenster könnte nur ein sehr kleiner Teil der großen GUI sein), werden wir in diesem Tutorial die Windows.Forms-Ereignisse nutzen.

Wir haben eine int x=0; Variable, die wir im KeyDown-Ereignis inkrementieren. Wenn man diesesn Handler zum glControl1 und nicht zur Form hinzufügt, wird man das glControl1 fokussieren müssen, z.B. indem der Nutzer in das Steuerelement hineinklickt.

     int x = 0;
    private void glControl1_KeyDown(object sender, KeyEventArgs e)
    {
      if (e.KeyCode == Keys.Space)
        x++;
    }

Es wird ein GL.Translate()-Aufruf zum Paint-Ereignishandler hinzugefügt:

     private void glControl1_Paint(object sender, PaintEventArgs e)
    {
      if (!loaded)
        return;
 
      GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
      GL.MatrixMode(MatrixMode.Modelview);
      GL.LoadIdentity();
 
      GL.Translate(x, 0, 0); // position triangle according to our x variable
 
      ...
    }

Wenn wir das Programm nun laufen lassen, geschieht nichts. Der Grund ist, dass das glControl1 nicht neu gezeichnet wird; der Fenstermanager des Betriebssystems (Windows/X/OSX) stellt sicher, dass so wenig wie möglich Paint Ereignisse auftreten. Nur beim Vergrößern/Verkleinern und ein paar mehr Situationen wird das Paint Ereignis ausgelöst.

Was wir nun tuen müssen, ist dem Fenstermanager zu sagen: "Dieses Steuerelement muss neu gezeichnet werden, da die zu Grunde liegenden Daten geändert wurden". Das geht ganz einfach mit dem Aufruf der Invalidate() Methode:

     private void glControl1_KeyDown(object sender, KeyEventArgs e)
    {
      if (!loaded)
        return;
      if (e.KeyCode == Keys.Space)
      {
        x++;
        glControl1.Invalidate();
      }
    }

Fokus-Verhalten
Nun werden wir ein kleines Experiment machen, um heraus zu finden wie das Fokus-Verhalten ist.

Wir zeichnen ein gelbes Dreieck, wenn glControl1 fokussiert ist und ein blaues wenn es nicht den Fokus hat:

     private void glControl1_Paint(object sender, PaintEventArgs e)
    {
      ...
 
      if (glControl1.Focused) // Simple enough :)
        GL.Color3(Color.Yellow);
      else
        GL.Color3(Color.Blue);
      GL.Begin(BeginMode.Triangles);
 
      ...
    }

So wenn das Dreieck nun gelb ist, müsste das Drücken der Leertaste funktionieren und wenn das Dreieck blau eingefärbt ist, werden alle Tastatureingaben ignoriert.

Freiheit: Das Fenster vergrößern und verkleinern

Immer wenn sich die Größe eines Windows.Forms Steuerelement ändert, wird dessen Resize-Ereignis ausgelöst. Das gilt auch für glControl1. Das ist unser erster wichtiger Punkt.

Die zweite Frage ist, "Was müssen wir aktualisieren, wenn ein GLControl seine Größe verändert?" und die Antwort ist "Das Darstellungsrechteck und die Projektionsmatrix".

Wir können an dieser Stelle also nun wieder unsere nützliche Funktion SetupViewport nutzen. Fügen Sie also einen Ereignshandler zum Resize-Ereignis von glControl1 hinzu:

     private void glControl1_Resize(object sender, EventArgs e)
    {
      SetupViewport();
    }

Es gibt jedoch immer noch ein Problem: Wenn Sie das Fenster verkleinern, wird kein Paint-Ereignis ausgelöst (wenn man z.B. das Fenster mit dem Anfasser rechts unten verkleinert). Das kommt daher, weil der Fenstermanager annimmt der (0,0) Pixel eines Steuerelements liegt in der oberen linken Ecke. Verkleinert man ein Fenster von oben links aus wird das Dreieck daher korrekt neugezeichnet. Die Lösung des Problem ist einfach: Wir instruieren den Fenstermanager ein Paint-Ereignis manuell auszulösen:

     private void glControl1_Resize(object sender, EventArgs e)
    {
      SetupViewport();
      glControl1.Invalidate();
    }

Wir möchen eine Hauptschleife: Eine Animation mit Hilfe von Application.Idle
Was ist, wenn wir ein Dreiecke haben wollen, welches sich kontinuierlich dreht? Normalerweise würden wir eine Variable in unserer Hauptschleife inkrementieren bevor wir da Dreieck zeichnen.

Aber wir haben keine Schleife. Wir haben lediglich Ereignisse!

Um dem auszuhelfen, müssen wir Windows.Forms dazu zwingen sich so zu verhalten wie wir es möchten. Wir benötigen ein Ereignis, welches ständig ausgelöst wird, so oft dass wir einer Echtzeit-Animation erhalten.

Es gibt mehrere Wege dies zu erreichen. Man könnte einen Timer nutzen - eine Variable im Tick Ereignis des Timer inkrementieren. Eine andere Möglichkeit ist das nutzen eines Threads. Die erste Variante ist sehr high-level und langsam während die zweite wirklich low-level und schwer zu implementieren ist.

Wir werden einen weiteren Pfad einschlagen und ein Windows.Forms Ereignis nutzen, welches genau für diesen Anwendungsfall gedacht ist: Das Application.Idle-Ereignis.

Dieses Ereignis ist in vielerlei Hinsicht sehr speziell. Es ist nicht mit einem Fenster oder einem anderem Steuerelement verknüpft, stattdessen mit dem Programm als solches. Man kann es nicht vom GUI-Designer aus nutzen, sondern muss sich manuell beim Ereignis registrieren - z.B. im Load-Ereignis:

     private void Form1_Load(object sender, EventArgs e)
    {
      loaded = true;
      GL.ClearColor(Color.SkyBlue);
      SetupViewport();
      Application.Idle += new EventHandler(Application_Idle); // press TAB twice after +=
    }
 
    void Application_Idle(object sender, EventArgs e)
    {
    }

Ein Vorteil des Idle-Ereignis ist, dass die dazugehörigen Ereignishandler im Windows.Forms Thread aufgerufen werden. Das bedeutet wir können alle GUI-Steuerelemente nutzen ohne uns Gedanken über Threading-Probleme zu machen .

Damit inkrementieren wir nun einfach unsere rotation Variable im Idle Ereignishandler und rufen Invalidate() für glControl1 auf.

     float rotation = 0;
    void Application_Idle(object sender, EventArgs e)
    {
      // no guard needed -- we hooked into the event in Load handler
      rotation += 1;
      glControl1.Invalidate();
    }

Nun frischen wir unseren Code zum Zeichnen auf:

     private void glControl1_Paint(object sender, PaintEventArgs e)
    {
      ...
      if (glControl1.Focused)
        GL.Color3(Color.Yellow);
      else
        GL.Color3(Color.Blue);
      GL.Rotate(rotation, Vector3.UnitZ); // OpenTK has this nice Vector3 class!
      GL.Begin(BeginMode.Triangles);
      ...
    }

Es funktioniert! Siehe folgendes Bild:

Das Dreieck dreht sich langsamer wenn das Fenster groß ist! Wieso?
(Das könnte bei Ihnen eventuell nicht der Fall sein, wenn Sie einen superschnellen Computer mit einer aktuellen Grafikkarten haben; aber Sie wollen ja, dass ihr Spiel auch auf dem PC Ihres Nachbarn funktioniert, oder?)

Der Grund ist, dass 3D-Rending in Fenster meistens um einiges langsamer ist als Vollbild-Rendering.

Aber Sie können dieses Problem reduzieren, indem Sie eine Technik namens Frameraten-unabhängige Animation nutzen. Die Idee ist einfach: Inkrementieren Sie die rotation Variable nicht um 1, dafür um einen Wert der von der Zeichengeschwindigkeit abhängt (wenn die Geschwindigkeit langsam ist, um einen hohen Wert inkrementieren).

Wir müssen die Geschwindigkeit des Zeichnen messen, d.h. messen wir wie lange das Rendern eines Frames dauert.

Seit .NET 2.0 gibt es die Klasse StopWatch mit der man hoch-präzise Zeitmessungen durchführen kann. So funktioniert sie:

     Stopwatch sw = new Stopwatch();
    sw.Start();
    MyAdvancedAlgorithm();
    sw.Stop();
    double milliseconds = sw.Elapsed.TotalMilliseconds;

(Versuchen Sie nicht DateTime.Now zu nutzen - die Präzision liegt bei 10 oder mehr Millisekunden, ungefähr so lange wie man typischer Weise braucht, um einen Frame zu rendern ... nutzlos.)

Nun können wir die Zeit messen, die benötigt wird, um da glControl zu zeichnen. Es gibt jedoch einen eleganteren Weg: Wir messen die Zeit, die wir nicht im Application.Idle-Ereignis verbringen! Dann können wir sicher sein, dass es nicht nur das Zeichnen ist was wir messen, sondern alles, was seit dem letzten Idle-Ereignis ausgeführt wurde.

     Stopwatch sw = new Stopwatch(); // available to all event handlers
    private void Form1_Load(object sender, EventArgs e)
    {
      ...
      sw.Start(); // start at application boot
    }
 
    float rotation = 0;
    void Application_Idle(object sender, EventArgs e)
    {
      // no guard needed -- we hooked into the event in Load handler
 
      sw.Stop(); // we've measured everything since last Idle run
      double milliseconds = sw.Elapsed.TotalMilliseconds;
      sw.Reset(); // reset stopwatch
      sw.Start(); // restart stopwatch
 
      // increase rotation by an amount proportional to the
      // total time since last Idle run
      float deltaRotation = (float)milliseconds / 20.0f;
      rotation += deltaRotation;
 
      glControl1.Invalidate();
    }

Cool! Das Dreieck dreht sich unabhängig von der Fenstergröße immer mit der gleichen Geschwindigkeit.

FPS-Zähler

Einene FPS-Zähler zu implementieren ist recht einfach, da wir nun eine Stoppuhr (Stopwatch) haben.

Wir zählen wie oft das Idle-Ereignis in jeder Sekunde aufgerufen wird und aktualisieren mit diesem Wert ein Label-Steuerelement. Natürlich müssen wir wissen wann eine Sekunde vorbei ist, daher brauchen wir eine Variable, um alle "Zeitstücken" zu addieren.

Ziemlich viel Logik befand sich im Idle Ereignis, daher wurde der Code nun etwas aufgesplittet:

     void Application_Idle(object sender, EventArgs e)
    {
      double milliseconds = ComputeTimeSlice();
      Accumulate(milliseconds);
      Animate(milliseconds);
    }
 
    float rotation = 0;
    private void Animate(double milliseconds)
    {
      float deltaRotation = (float)milliseconds / 20.0f;
      rotation += deltaRotation;
      glControl1.Invalidate();
    }
 
    double accumulator = 0;
    int idleCounter = 0;
    private void Accumulate(double milliseconds)
    {
      idleCounter++;
      accumulator += milliseconds;
      if (accumulator > 1000)
      {
        label1.Text = idleCounter.ToString();
        accumulator -= 1000;
        idleCounter = 0; // don't forget to reset the counter!
      }
    }

Unser FPS-Zähler in all seiner Pracht:

Kompletter Quellcode

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using OpenTK.OpenGL;
using OpenTK.OpenGL.Enums;
using OpenTK.Math;
using System.Diagnostics;
 
namespace GLControlApp
{
  public partial class Form1 : Form
  {
    bool loaded = false;
 
    public Form1()
    {
      InitializeComponent();
    }
 
    Stopwatch sw = new Stopwatch(); // available to all event handlers
    private void Form1_Load(object sender, EventArgs e)
    {
      loaded = true;
      GL.ClearColor(Color.SkyBlue); // Yey! .NET Colors can be used directly!
      SetupViewport();
      Application.Idle += new EventHandler(Application_Idle); // press TAB twice after +=
      sw.Start(); // start at application boot
    }
 
    void Application_Idle(object sender, EventArgs e)
    {
      double milliseconds = ComputeTimeSlice();
      Accumulate(milliseconds);
      Animate(milliseconds);
    }
 
    float rotation = 0;
    private void Animate(double milliseconds)
    {
      float deltaRotation = (float)milliseconds / 20.0f;
      rotation += deltaRotation;
      glControl1.Invalidate();
    }
 
    double accumulator = 0;
    int idleCounter = 0;
    private void Accumulate(double milliseconds)
    {
      idleCounter++;
      accumulator += milliseconds;
      if (accumulator > 1000)
      {
        label1.Text = idleCounter.ToString();
        accumulator -= 1000;
        idleCounter = 0; // don't forget to reset the counter!
      }
    }
 
    private double ComputeTimeSlice()
    {
      sw.Stop();
      double timeslice = sw.Elapsed.TotalMilliseconds;
      sw.Reset();
      sw.Start();
      return timeslice;
    }
 
    private void SetupViewport()
    {
      int w = glControl1.Width;
      int h = glControl1.Height;
      GL.MatrixMode(MatrixMode.Projection);
      GL.LoadIdentity();
      GL.Ortho(0, w, 0, h, -1, 1); // Bottom-left corner pixel has coordinate (0, 0)
      GL.Viewport(0, 0, w, h); // Use all of the glControl painting area
    }
 
    private void glControl1_Paint(object sender, PaintEventArgs e)
    {
      if (!loaded)
        return;
 
      GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
      GL.MatrixMode(MatrixMode.Modelview);
      GL.LoadIdentity();
 
      GL.Translate(x, 0, 0);
 
      if (glControl1.Focused)
        GL.Color3(Color.Yellow);
      else
        GL.Color3(Color.Blue);
      GL.Rotate(rotation, Vector3.UnitZ); // OpenTK has this nice Vector3 class!
      GL.Begin(BeginMode.Triangles);
      GL.Vertex2(10, 20);
      GL.Vertex2(100, 20);
      GL.Vertex2(100, 50);
      GL.End();
 
      glControl1.SwapBuffers();
    }
 
    int x = 0;
    private void glControl1_KeyDown(object sender, KeyEventArgs e)
    {
      if (e.KeyCode == Keys.Space)
      {
        x++;
        glControl1.Invalidate();
      }
    }
 
    private void glControl1_Resize(object sender, EventArgs e)
    {
      SetupViewport();
      glControl1.Invalidate();
    }
  }
}

Faustregeln

This page is the German Translation of Rules of thumb.

Dieser Text ist gedacht für Leute, die bisher mit C/OpenGL gearbeitet haben.

Obwohl OpenTK automatisch alle GL/AL Funktionsaufrufe von C# nach C umwandelt, arbeiten einige Dinge in der Welt der verwalteten Sprachen etwas anders vergleichen mit simplem C.

Faustregeln

  1. Bevorzugen Sie Server-Speicher statt Klientspeicher

    Ein paar alte OpenGL-Funktionen nutzen Zeiger auf Speicher, der vom Nutzer verwaltet ist. Das bekannteste Beispiel sind Vertex Arrays - die Familie der GL.*Pointer-Funktionen.

    Dieser Ansatz kann in einer Garbage Collector Umgebung (wie .NET es ist) nicht genutzt werden, da der Garbage Collector (GC) die Inhalte des Buffers eventuell verschiebt. Es ist sehr empfehlenswert alte Vertex Arrays mit Vertex Buffer Objects zu ersetzen, welche dieses Problem nicht haben.

    Anders als OpenGL 2.1, wird OpenGL 3.0 keinerlei solcher Funktionen zur Nutzung des Klientspeichers beinhalten.

  2. Versuchen Sie die Anzahl der OpenGL-Funktionsaufrufe per Frame zu minimieren

    Dies ist für jede Programmierumgebung wichtig, die OpenGL nutzt, für OpenTK jedoch sogar noch ein wenig wichtiger: Obwohl die OpenGL/AL Bindings ziemlich optimiert sind, kostet der Übergang von der verwalteten zur unverwalteten Welt einen kleinen, aber messbaren, Overhead (Einbußen an Performance).

    Um den Einfluss des Overheads zu minimieren, versuchem Sie die Anzahl der OpenGL/OpenAL Aufrufe zu verringern. Eine gute Faustregel ist es nicht mehr als 300-500 OpenGL-Aufrufe per Frame zu haben. Dies kann durch Vermeidung des Immediate Mode zu Gunsten von Display Listen oder VBO's geschehen.

  3. Nutzen Sie ref Funktions-Überladungen für maximale Performance bei Mathe-Routinen
    Dies ist wichtig weil Vector3, Matrix3 etc. Strukturen und keine Klassen sind. Klassen-Objekte werden standardmäßig per Referenz übergeben.

    Vector3 v1 = Vector3.UnitX;
    Vector3 v2 = Vector3.UnitZ;
    Vector3 v3 = Vector3.Zero;
    v3 = v1 + v2;                        // requires three copies; slow.
    Vector3.Add(ref v1, ref v2, out v3); // nothing is copied; fast!

    Das gleiche trifft für das Aufrufen von OpenGL-Funktionen zu:

    GL.Vertex3(ref v1.X);  // pass a pointer to v1; fast!
    GL.Vertex3(v1);        // copy the whole v1 structure; slower!

Performance messen

  1. GameWindows besitzt einen eingebauten Frames-pro-Sekunde-Zähler. Wie auch immer, GLControl hat keinen.
  2. Eine einfache und bequeme Möglichkeit zur Messung der Performance Ihres Codes stellt die .NET 2.0 / Mono 1.2.4 Stopwatch Klasse dar. Benutzen Sie sie wie folgt:
    Stopwatch sw = new Stopwatch();
    sw.Start();
    // Your code goes here.
    sw.Stop();
    double ms = sw.Elapsed.TotalMilliseconds;

    Anmerkung: Vermeiden Sie die Nutzung von DateTime.Now oder anderer DateTime-basierte Methoden für Zeitspannen unter ein paar Sekunden, da die Präzisision bei 10ms oder schlecher liegt. (Gerüchte besagen, dass es von Zeit zu Zeit sogar schlechter wird!) DateTime für das Messen von langen Operationen (einige Sekunden) zu nutzen ist OK.

  3. Wenn Sie Windows nutzen, können Sie Fraps downloaden, um die Anzahl der Frames pro Sekunde zu messen, die in Ihrer Anwendung gezeichnet werden.

Referenzen:
http://www.gamedev.net/community/forums/topic.asp?topic_id=484756&whichp...

OpenGL

Um OpenGL-Funktionen zu nutzen, muss Ihr System entsprechende Treiber zur Hardware-Beschleunigung besitzen.

Das OpenGL Red Book ist ein von Silicon Graphics Ingenieuren verfasstes Buch und wird Sie in die Grafikprogrammierung einführen. Es ist sehr empfehlenswert sich dieses Buch anzusehen und die essentiellen Konzepte von OpenGL zu erlernen.

Diese Seiten sind eher auf OpenTK spezifische Änderungen zur C-API ausgerichtet und darauf wie man die OpenTK.Utility Klassen nutzt.