NeHe - Lektion 06 - Textur Mapping

Lektion 6



Man hat viele Vorteile, wenn man weiß, wie man texturiert. Stellen Sie sich vor, Sie wollen eine Rakete durch den Screen fliegen lassen. Mit den bisherigen Tutorials würden wir die Rakete wahrscheinlich aus Polygonen und lauter Farben machen. Mit Textur Mapping können Sie ein Foto einer Rakete nehmen und dieses Foto durch den Screen fliegen lassen. Was meinen Sie, was sieht besser aus? Ein Foto oder ein Objekt bestehend aus Dreiecken und Quadraten? Indem Sie Textur Mapping verwenden, wird es nicht nur besser aussehen, Ihr Programm wird auch schneller laufen. Die texturierte Rakete wäre nur ein einzelnes Quadrat, was sich über den Screen bewegt. Eine Rakete aus Polygonen könnte aus hunderten oder tausenden Polygonen bestehen. Das einzelne texturierte Quadrat wird wesentlich weniger Prozessorleistung benötigen.

Fangen wir mit dem hinzufügen einiger weniger Zeilen Code am Anfang von Lektion eins an. Die erste neue Zeile ist #include <stdio.h>. Nachdem wir diesen Header hinzugefügt haben, können wir mit Dateien arbeiten. Um später im Code fopen() verwenden zu können, müssen wir diese Zeile einfügen. Dann fügen wir drei neue Fließkommavariablen ein... xrot, yrot und zrot. Diese Variablen werden verwendet, um den Würfel auf der X, Y und Z-Achse zu rotieren. Die letzte Zeile GLuint texture[1] initialisiert den Speicher für eine Textur. Wenn Sie mehr als eine Textur laden wollen, würden Sie die Zahl eins in die Anzahl der zu ladenden Texturen umwandeln.

#include    <windows.h>                            // Header Datei für Windows
#include    <stdio.h>                            // Header Datei für die Standard Ein-/Ausgabe
#include    <gl\gl.h>                            // Header Datei für die OpenGL32 Library
#include    <gl\glu.h>                            // Header Datei für die GLu32 Library
#include    <gl\glaux.h>                            // Header Datei für die GLaux Library

HDC        hDC=NULL;                            // Privater GDI Device Context
HGLRC        hRC=NULL;                            // Permanenter Rendering Context
HWND        hWnd=NULL;                            // Enthält unser Fenster-Handle
HINSTANCE    hInstance;                            // Enthält die Instanz der Applikation

bool        keys[256];                            // Array das für die Tastatur Routine verwendet wird
bool        active=TRUE;                            // Fenster Aktiv Flag ist standardmäßig auf TRUE gesetzt
bool        fullscreen=TRUE;                        // Fullscreen Flag

GLfloat        xrot;                                // X Rotation ( NEU )
GLfloat        yrot;                                // Y Rotation ( NEU )
GLfloat        zrot;                                // Z Rotation ( NEU )

GLuint        texture[1];                            // Speicher für eine Textur ( NEU )

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);                // Deklaration für WndProc

Nun, direkt nach dem obigen Code und bevor ReSizeGLScene() wollen wir folgenden Codeabschnitt einfügen. Dieser Code lädt eine Bitmap-Datei. Wenn die Datei nicht existiert, wird NULL zurückgegeben, was bedeutet, dass die Textur nicht geladen werden konnte. Bevor ich anfange den Code zu erklären, gibt es ein paar SEHR wichtige Dinge, die Sie über die Bilder wissen müssen, die Sie als Textur verwenden wollen. Die Bild-Höhe MUSS zur Basis 2 sein. Die Breite und Höhe müssen mindesten 64 Pixel haben und aus kompatibilitäts Gründen sollten sie nicht mehr als 256 Pixel haben. Wenn das Bild, dass Sie verwenden wollen, nicht 64, 128 oder 256 Pixel in der Breite oder Höhe hat, ändern Sie die Größe mit einem Grafikprogramm. Es gibt zwar Wege, um diese Beschränkungen zu umgehen, aber wir bleiben erst einmal bei den Standard Textur Größen.

Als erstes erzeugen wir ein Datei Handle. Ein Handle ist ein Wert, der eine Ressource identifiziert, so dass unser Programm zugriff darauf hat. Wir setzen das Handle anfangs auf NULL.

AUX_RGBImageRec *LoadBMP(char *Filename)                    // Lädt ein Bitmap
{
    FILE *File=NULL;                            // Datei Handle

Als nächstes überprüfen wir, dass ein Dateiname übergeben wurde. Die Person kann LoadBMP() vielleicht ohne die zu ladende Datei aufgerufen haben, weshalb wir das lieber überprüfen. Wir wollen ja nicht versuchen, nichts zu laden :)

    if (!Filename)                                // gehe sicher, dass ein Dateiname übergeben wurde
    {
        return NULL;                            // wenn nicht, gebe NULL zurück
    }

Wenn ein Dateiname übergeben wurde, überprüfen wir, ob die Datei existiert. Die folgende Zeile versucht die Datei zu öffnen.

    File=fopen(Filename,"r");                        // überprüfe, ob die Datei existiert.

Wenn wir die Datei öffnen konnten wird diese wohl auch existieren. Wir schließen die Datei mit fclose(File) und geben die Bilddaten zurück. auxDIBImageLoad(Filename) liest die Daten ein.

    if (File)                                // Existiert die Datei?
    {
        fclose(File);                            // Schließe das Handle
        return auxDIBImageLoad(Filename);                // Lädt das Bitmap und gibt einen Zeiger zurück
    }

Wenn wir die Datei nicht öffnen konnten, geben wir NULL zurück. Das bedeutet, die Datei konnte nicht geladen werden. Später überprüfen wir im Programm, ob die Datei geladen wurde. Wenn nicht, werden wir das Programm mit einer Fehlermeldung schließen.

    return NULL;                                // Wenn das Laden fehl schlug, gebe NULL zurück
}

Dieser Codeabschnitt lädt das Bitmap (indem der obige Code aufgerufen wird) und konvertiert es in eine Textur.

int LoadGLTextures()                                // Lade Bitmaps und konvertiere in Texturen
{

Wir initialisieren eine Variable namens Status. Wir werden diese Variable dazu verwenden, um zu verfolgen, ob wir das Bitmap laden und in eine Textur verwandeln können. Wir setzen den Standardwert von Status auf FALSE (was bedeutet, dass bisher nichts geladen oder erzeugt wurde).

    int Status=FALSE;                            // Status Indikator

Nun erzeugen wir ein Bild Feld, wo wir unser Bitmap drin speichern können. Das Feld wird die Bitmap Breite, Höhe und Daten enthalten.

    AUX_RGBImageRec *TextureImage[1];                    // erzeuge Speicherplatz für die Textur

Wir löschen das Bild Feld, nur um sicher zu gehen, dass es leer ist.

    memset(TextureImage,0,sizeof(void *)*1);                // Setze den Zeiger auf NULL

Nun laden wir das Bitmap und konvertieren es in eine Textur. TextureImage[0]=LoadBMP("Data/NeHe.bmp") ruft unseren LoadBMP() Code auf. Die Datei names NeHe.bmp im Data Verzeichnis wird geladen. Wenn alles glatt läuft, werden die Bild Daten in TextureImage[0] gespeichert, Status auf TRUE gesetzt und wir fangen an, unsere Textur zu erzeugen.

    // Lade das Bitmap, prüfe auf Fehler, wenn Bitmap nicht gefunden wurde, beende
    if (TextureImage[0]=LoadBMP("Data/NeHe.bmp"))
    {
        Status=TRUE;                            // Setze Status auf TRUE

Nun, da wir die Bild-Datei nach TextureImage[0] geladen haben, erzeugen wir eine Textur aus diesen Daten. Die erste Zeile glGenTextures(1, &texture[0]) teilt OpenGL mit, dass wir einen Textur Namen generieren wollen (erhöhen Sie die Nummer, wenn Sie mehr als eine Textur laden wollen). Erinnern Sie sich, dass wir ganz am Anfang des Tutorials Platz für eine Textur erzeugt haben und zwar mit der Zeile GLuint texture[1]. Obwohl Sie sich vielleicht denken, dass die erste Textur in &texture[1] gespeichert wird, anstatt in &texture[0], so ist dem nicht so. Der tatsächliche erste Speicherplatz ist 0. Wenn wir zwei Texturen haben wollen, würden wir GLuint texture[2] benutzen und die zweite Textur würde in texture[1] gespeichert werden.

Die zweite Zeile, glBindTexture(GL_TEXTURE_2D, texture[0]) teilt OpenGL mit, die genannte Textur texture[0] an ein Textur-Ziel zu binden. 2D Texturen haben sowohl beides, Höhe (auf der Y-Achse) und Breite (auf der X-Achse). Die Hauptfunktion von glBindTexture ist es, einen Textur-Namen mit Textur-Daten zu verbinden. In diesem Fall teilen wir OpenGL mit, dass Speicher in &texture[0] verfügbar ist. Wenn wir eine Textur erzeugen, wird diese in dem Speicher gespeichert, welchen &texture[0] referenziert.

        glGenTextures(1, &texture[0]);                    // erzeuge die Textur

        // typische Textur Erzeugung indem Daten aus dem Bitmap verwendet werden
        glBindTexture(GL_TEXTURE_2D, texture[0]);

Als nächste erzeugen wir die aktuelle Textur. Die folgende Zeile teilt OpenGL mit, dass die Textur eine 2D Textur sein wird (GL_TEXTURE_2D). Null repräsentiert das Level an Details des Bildes und wird in der Regel auf Null gesetzt. Drei ist die Anzahl der Daten-Komponenten. Da das Bild aus Rot-Daten, Grün-Daten und Blau-Daten besteht, gibt es drei Komponenten. TextureImage[0]->sizeX ist die Breite der Textur. Wenn Sie die Breite kennen, können Sie sie hier angeben, aber es ist einfacher, den Computer das für Sie herausfinden zu lassen. TextureImage[0]->sizey ist die Höhe der Textur. Null ist die Grenze. Wird normalerweise auf 0 gelassen. GL_RGB teilt OpenGL mit, dass die Bilddaten, die wir verwenden, aus Rot, Grün und Blau Daten bestehen und zwar in dieser Reihenfolge. GL_UNSIGNED_BYTE bedeutet, dass die Daten des Bildes aus vorzeichenlosen (unsigned) Bytes bestehen und letztlich... TextureImage[0]->data teilt OpenGL mit, wo die Textur-Daten liegen.
In diesem Fall zeigt es auf die Daten die in TextureImage[0] gespeichert sind.

        // Generiere die Textur
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Die nächsten beiden Zeilen teilen OpenGL mit, welche Art von Filter verwendet werden soll, wenn das Bild größer (GL_TEXTURE_MAG_FILTER) oder gestreckter aks die original Textur auf dem Bildschirm erscheint oder wenn es kleiner (GL_TEXTURE_MIN_FILTER) als die eigentliche Textur auf dem Screen wird. Ich benutze in der Regel für beides GL_LINEAR. Das lässt die Textur für beides weich aussehen, sowohl in der Ferne als auch, wenn Sie nahe am Screen ist. Das verwenden von GL_LINEAR benötigt viel Arbeit vom Prozessor/der Grafikkarte, so dass, wenn Ihr System langsam ist, Sie vielleicht GL_NEAREST verwenden wollen. Eine Textur die mit GL_NEAREST gefiltert wird, erscheint klobig, wenn Sie gestreckt wird. Sie können auch eine Kombination der beiden ausprobieren. Dadurch werden Dinge in der Nähe gefiltert, nicht aber in der Distanz.

        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);    // Linear Filtering
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);    // Linear Filtering
    }

Nun geben wir jeglichen Speicher frei, den wir verwendet haben, um die Bitmap-Daten zu speichern. Wir überprüfen, ob die Bitmap-Daten in TextureImage[0] gespeichert wurden. Wenn dem so ist, überprüfen wir, ob Daten gespeichert wurden. Wenn Daten gespeichert wurden, löschen wir diese. Dann geben wir die Bild-Struktur frei, um sicher zu gehen, dass jeglicher benutzter Speicher freigegeben wird.

    if (TextureImage[0])                            // Wenn Textur existiert
    {
        if (TextureImage[0]->data)                    // Wenn Textur-Image existiert
        {
            free(TextureImage[0]->data);                // gebe Textur-Image Speicher frei
        }

        free(TextureImage[0]);                        // gebe die Image-Struktur frei
    }

Wir geben den Status zurück. Wenn alles OK ist, wird die Variable Status gleich TRUE sein. Wenn irgendwas schief lieft, wird Status gleich FALSE sein.

    return Status;                                // gebe Status zurück
}

Ich habe ein paar Codezeilen in InitGL eingefügt. Ich schreibe hier nochmal den ganzen Code-Abschnitt nieder, so dass es einfach zu verstehen ist, welche Zeilen ich hinzugefügt habe und wo sie hinkommen. Die erste Zeile if (!LoadGLTextures()) ruft die obige Routine zum Laden des Bitmaps auf und erzeugt eine Textur daraus. Wenn LoadGLTextures() aus irgend einem Grund fehl schlagen sollte, liefert die nächste Codezeile FALSE zurück. Wenn alls OK war und die Textur erzeugt wurde, aktivieren wir 2D Textur Mapping. Wenn Sie vergessen Textur Mapping zu aktivieren, wird Ihr Objekt komplett in weiß erscheinen, was definitiv nicht gut ist.

int InitGL(GLvoid)                                // Der ganze Setup Kram für OpenGL kommt hier rein
{
    if (!LoadGLTextures())                            // Rufe Textur Lade Routine auf ( NEU ) 
    {
        return FALSE;                            // wenn Textur nicht geladen wurde, gebe FALSE zurück ( NEU )
    }

    glEnable(GL_TEXTURE_2D);                        // aktiviere Textur Mapping ( NEU )
    glShadeModel(GL_SMOOTH);                        // aktiviere Smooth Shading
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);                    // schwarzer Hintergrund
    glClearDepth(1.0f);                            // Depth Buffer initialisieren
    glEnable(GL_DEPTH_TEST);                        // aktiviere Depth Test
    glDepthFunc(GL_LEQUAL);                            // Die Art des auszuführenden Depth Test
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // wirklich nette Perspektiven Berechnungen
    return TRUE;                                // Initialisierung war OK

}

Nun zeichnen wir den texturierten Würfel. Sie können den DrawGLScene Code mit dem folgenden Code ersetzen oder Sie können die neuen Code Zeilen in den original Code von Lektion 1 einfügen. Dieser Abschnitt wird gut kommentiert sein, so das er einfach zu versetehen ist. Die ersten beiden Codezeilen glClear() und glLoadIdentity() sind aus dem original Code der Lektion eins. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) löscht den Bildschirm auf die Farbe, die wir in InitGL() ausgewählt haben. In diesem Fall wird der Screen auf schwarz gelöscht. Der Depth Buffer wird ebenso geleert. Die View wird dann mit glLoadIdentity() resettet.

int DrawGLScene(GLvoid)                                // Hier kommt der ganze Zeichnen-Kram hin
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // Lösche den Bildschirm und den Depth-Buffer
    glLoadIdentity();                            // Resette die aktuelle Matrix
    glTranslatef(0.0f,0.0f,-5.0f);                        // Bewege 5 Einheiten in den Screen hinein

Die folgenden drei Codezeilen lassen den Würfel auf der X-Achse, dann auf der Y-Achse und letzlich auf der Z-Achse rotieren. Um wieviel auf der jeweiligen Achse rotiert wird, hängt vom Wert ab, der in xrot, yrot und zrot gespeichert ist.

    glRotatef(xrot,1.0f,0.0f,0.0f);                        // Rotiere auf der X Achse
    glRotatef(yrot,0.0f,1.0f,0.0f);                        // Rotiere auf der Y Achse
    glRotatef(zrot,0.0f,0.0f,1.0f);                        // Rotiere auf der Z Achse

Die nächste Codezeile wählt die Textur aus, die wir verwenden wollen. Wenn Sie mehr als eine Textur verwenden wollen, sollten Sie die Textur mit glBindTexture(GL_TEXTURE_2D, texture[nummer der Textur die sie verwenden wollen]) auswählen. Wenn Sie Texturen ändern wollen, würden Sie neue Texturen binden. Eine Sache, die man erwähnen sollte, ist, dass Sie Texturen NICHT innerhalb von glBegin() und glEnd() binden können, dass müssen Sie davor oder danach machen. Beachten Sie, wie wir glBindTextures verwenden, um anzugeben welche Textur erzeugt werden soll und eine bestimmte Textur ausgewählt werden soll.

    glBindTexture(GL_TEXTURE_2D, texture[0]);                // wähle unsere Textur aus

Um ein Qudrat richtig zu texturieren, müssen Sie sicher stellen, dass die obere rechte Ecke der Textur auf die obere rechte Ecke des Quadrats gemapped wird. Die obere linke Ecke der Textur wird auf die obere linke Ecke des Qudrats gemapped, die untere rechte Ecke der Textur wird auf die untere rechte Ecke des Quadrats gemapped und letztendlich wird die untere linke Ecke der Textur auf die untere linke Ecke des Quadrats gemapped. Wenn die Ecken der Textur nicht mit denen des Quadrats übereinstimmen, erscheint das Bild vielleicht auf dem Kopf, spiegelverkehrt oder gar nicht.

Der erste Wert von glTexCoord2f ist die X Koordinate. 0.0f ist die linke Seite der Textur. 0.5f ist die Mitte der Textur und 1.0f ist die rechte Seite der Textur. Der zweite Wert von glTexCoord2f ist die Y Koordinat. 0.0f ist die untere Seite derTextur. 0.5f ist die Mitte der Textur und 1.0f ist die obere Seite der Textur.

So, nun wissen wir, dass die obere linke Koordinate einer Textur 0.0f auf der X-Achse und 1.0f auf der Y-Achse ist und der obere linke Vertex eines Quadrats ist -1.0f auf der X-Achse und 1.0f auf der Y-Achse. Alles was Sie jetzt noch machen müssen, ist das Anpassen der anderen drei Textur Koordinaten auf die restlichen drei Ecken des Quadrats.

Spielen Sie etwas mit den X und Y Wert von glTexCoord2f. Ändert man 1.0f in 0.5f wird nur die linke Hälfte der Textur von 0.0f (links) bis 0.5f (Mitte der Textur) gezeichnet. Ändert man 0.0f in 0.5f wird nur die rechte Hälfte der Textur von 0.5f (Mitte) bis 1.0f (rechts) gezeichnet.

    glBegin(GL_QUADS);
        // vordere Seite
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);    // Unten links der Textur und des Quadrats
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);    // Unten rechts der Textur und des Quadrats
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);    // Oben rechts der Textur und des Quadrats
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);    // Oben links der Textur und des Quadrats
        // hintere Seite
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);    // Unten rechts der Textur und des Quadrats
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);    // Oben rechts der Textur und des Quadrats
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);    // Oben links der Textur und des Quadrats
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);    // Unten links der Textur und des Quadrats
        // obere Seite
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);    // Oben links der Textur und des Quadrats
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);    // Unten links der Textur und des Quadrats
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);    // Unten rechts der Textur und des Quadrats
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);    // Oben rechts der Textur und des Quadrats
        // untere Seite
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);    // Oben rechts der Textur und des Quadrats
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);    // Oben links der Textur und des Quadrats
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);    // Unten links der Textur und des Quadrats
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);    // Unten rechts der Textur und des Quadrats
        // rechte Seite
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);    // Unten rechts der Textur und des Quadrats
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);    // Oben rechts der Textur und des Quadrats
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);    // Oben links der Textur und des Quadrats
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);    // Unten links der Textur und des Quadrats
        // linke Seite
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);    // Unten links der Textur und des Quadrats
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);    // Unten rechts der Textur und des Quadrats
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);    // Oben rechts der Textur und des Quadrats
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);    // Oben links der Textur und des Quadrats
    glEnd();

Nun erhöhen wir die Werte von xrot, yrot und zrot. Versuchen Sie die Zahlen jeder Variablen zu verändern, um die die Variablen erhöht werden, um den Würfel schneller oder langsamer rotieren zu lassen oder versuchen Sie aus einem + ein - zu machen, um den Würfel in die andere Richtung rotieren zu lassen.

    xrot+=0.3f;                                // X Achsen Rotation
    yrot+=0.2f;                                // Y Achsen Rotation
    zrot+=0.4f;                                // Z Achsen Rotation
    return true;                                // weiter gehts
}

Sie sollten nun ein besseres Verständnis von Textur Mapping haben. Sie sollten in der Lage sein, die Fläche eines jeden Quadrats mit dem Bild Ihrer Wahl zu texturieren. Wenn Sie das Gefühl haben, 2D Textur Mapping verstanden zu haben, versuchen Sie sechs verschiedene Texturen für den Würfel zu verwenden.

Textur Mapping ist nicht so schwer zu verstehen, wenn man erst einmal die Textur Koordinaten verstanden hat. Wenn Sie Probleme haben, irgend einen Teil dieses Tutorials zu verstehen, lassen Sie es mich wissen. Entweder ich schreibe diesen Teil des Tutorials um oder ich werde Ihnen per E-Mail antworten. Viel Spaß dabei, eigene texturierte Szenen zu erzeugen :)

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code für diese Lektion.

* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Christian Kindahl )
* DOWNLOAD C# Code für diese Lektion. ( Conversion by Sabine Felsinger )
* DOWNLOAD VB.Net CsGL Code für diese Lektion. ( Conversion by X )
* DOWNLOAD Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* DOWNLOAD Cygwin Code für diese Lektion. ( Conversion by Stephan Ferraro )
* DOWNLOAD D Language Code für diese Lektion. ( Conversion by Familia Pineda Garcia )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Dan )
* DOWNLOAD Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD Game GLUT Code für diese Lektion. ( Conversion by Milikas Anastasios )
* DOWNLOAD GLUT Code für diese Lektion. ( Conversion by Kyle Gancarz )
* DOWNLOAD Irix Code für diese Lektion. ( Conversion by Lakmal Gunasekara )
* DOWNLOAD Java Code für diese Lektion. ( Conversion by Jeff Kirby )
* DOWNLOAD Jedi-SDL Code für diese Lektion. ( Conversion by Dominique Louis )
* DOWNLOAD JoGL Code für diese Lektion. ( Conversion by Kevin J. Duling )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux Code für diese Lektion. ( Conversion by Richard Campbell )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by Mihael Vrbanec )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Ti Leggett )
* DOWNLOAD LWJGL Code für diese Lektion. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS Code für diese Lektion. ( Conversion by Anthony Parker )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* DOWNLOAD MASM Code für diese Lektion. ( Conversion by Nico (Scalp) )
* DOWNLOAD Visual C++ / OpenIL Code für diese Lektion. ( Conversion by Denton Woods )
* DOWNLOAD Power Basic Code für diese Lektion. ( Conversion by Angus Law )
* DOWNLOAD Pelles C Code für diese Lektion. ( Conversion by Pelle Orinius )
* DOWNLOAD Python Code für diese Lektion. ( Conversion by John Ferguson )
* DOWNLOAD REALbasic Code für diese Lektion. ( Conversion by Thomas J. Cunningham )
* DOWNLOAD Scheme Code für diese Lektion. ( Conversion by Brendan Burns )
* DOWNLOAD Solaris Code für diese Lektion. ( Conversion by Lakmal Gunasekara )
* DOWNLOAD Visual Basic Code für diese Lektion. ( Conversion by Ross Dawson )
* DOWNLOAD Visual Basic Code für diese Lektion. ( Conversion by Peter De Tagyos )
* DOWNLOAD Visual Fortran Code für diese Lektion. ( Conversion by Jean-Philippe Perois )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Grant James )



Deutsche Übersetzung: Joachim Rohde
Der original Text ist hier zu finden.
Die original OpenGL Tutorials stammen von NeHe's Seite.