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!

Kapitel 1: Installation

This page is the German Translation of Chapter 1: Installation

Um OpenTK zu nutzen, benötigt man entweder die .Net 2.0 Laufzeitumgebung (Windows) oder das aktuellste 1.2.x Mono Release (Linux/Mac OS X/Windows). Wenn Sie Mono bereits nutzen, empfehlen wir auf das neueste Release upzugraden, da Versionen vor 1.2.6 Bugs beinhalten, welche die Stabilität von OpenTK gefährden.

Wenn Sie vor haben den Quellcode zu kompilieren, benötigen Sie das Build-Werkzeug Nant (diese Abhängigkeit wird in Zukunft entfernt).

Nicht zuletzt sollten Sie sich die Sourceforge Projektseite ansehen und OpenTK runterladen. OpenTK Releases sind einfache zip/7z Archive, bei der Installation entpacken Sie das Archiv einfach auf die Festplatte - im Moment ist es noch nicht möglich OpenTK in den Global Assembly Cache (GAC) zu installieren.

Um OpenTK zu nutzen, referenzieren Sie in Ihrem Projekt einfach die OpenTK.dll. Zusätzlich sollten Sie die OpenTK.dll.config in den Binary-Ordner Ihres Projektes kopieren. Somit können Sie Ihr Projekt einmal kompilieren und überall ausführen.

Auf den folgenden Seiten finden Sie plattformspezifische Anweisungen für die Installation.

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.

Todo:

2. OpenTK Classes
2.1 OpenTK.Input
2.2 OpenTK.Math
2.3 Rendering Context
2.4 OpenTK.Fonts & Timing
2.5 OpenTK.OpenGL
2.6 OpenTK.OpenAL [75%]

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.