NeHe - Lektion 24 - Token, Scissor Testing, TGA

Lektion 24



Dieses Tutorial ist weit davon entfernt grafisch anspruchsvoll zu sein, aber Sie werden definitiv ein paar neue Dinge lesen, wenn Sie es durchlesen. Eine ganze Menge Leute haben mich nach Extensions gefragt und wie man herausfindet, welche Extensionen von einer bestimmten Grafikkarte unterstützt werden. Diese Tutorial wird Ihnen zeigen, wie man herausfindet, welche Extensionen auf jeglicher 3D Grafikkarte unterstützt werden.

Ich werde Ihnen ebenfalls zeigen, wie man einen Teil des Screens scrollt ohne irgendwelche Grafiken drumherum zu beeinflussen, indem wir Scissor Testing verwenden. Sie werden auch lernen, wie man Line Strips zeichnet und am wichtigsten: in diesem Tutorial verzichten wir auf die AUX Library, zusammen mit Bitmap Images. Ich werde Ihnen auch zeigen, wie man Targa (TGA) Bilder als Texturen verwendet. Nicht nur, dass Targe Dateien einfach zu handahaben und zu erzeugen sind, sie unterstützen auch den ALPHA Kanal, was es Ihnen erlaubt ein paar ziemlich coole Effekte in Ihren zukünftigen Projekten zu verwenden!

Das erste was Sie im folgenden Code beachten sollten, ist, dass wir nicht länger die glaux Header-Datei (glaux.h) einbinden. Es ist auch wichtig anzumerken, dass die glaux.lib Datei weggelassen werden kann! Wir werden nicht mehr mit Bitmaps arbeiten, weshalb es keinen Grund gibt, eine der Dateien in unserem Projekt einzubinden.

Außerdem habe ich immer, wenn ich glaux verwendet habe, eine Warnung vom Compiler bekommen. Ohne glaux sollten null Fehler und null Warnungen auftreten.

#include    <windows.h>                                // Header Datei für Windows
#include    <stdio.h>                                // Header Datei für Standard Input / Output
#include    <stdarg.h>                                // Header Datei für Variable Parameter in Routinen
#include    <string.h>                                // Header Datei für String Management
#include    <gl\gl.h>                                // Header Datei für die OpenGL32 Library
#include    <gl\glu.h>                                // Header Datei für die GLu32 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 standardmäßig auf TRUE gesetzt
bool        fullscreen=TRUE;                            // Fullscreen Flag ist standardmäßig auf TRUE gesetzt

Als erstes müssen wir ein paar Variablen einfügen. Die erste Variable scroll wird benutzt, um einen Teil des Screens hoch und runter zu scrollen. Die zweite Variable maxtokens wird dazu verwendet, um zu zählen, wieviele Tokens (Extensionen) von der Grafikkarte unterstützt werden.

base wird die Font Display-Liste enthalten.

swidth und sheight werden verwendet, um die aktuelle Fenster-Größe zu ermitteln. Wir benutzen diese beiden Variablen, um später die Scissor Koordinaten zu berechnen.

int        scroll;                                    // wird verwendet um den Screen zu scrollen
int        maxtokens;                                // enthält die Anzahl der unterstützten Extensionen
int        swidth;                                    // Scissor Breite
int        sheight;                                // Scissor Höhe

GLuint        base;                                    // Basis Display Liste für den Font

Nun erzeugen wir eine Struktur, die die TGA Informationen enthalten wird, wenn wir diese erst einmal geladen haben. Die erste Variable imageData wird einen Zeiger auf die Daten enthalten, aus denen das Bild besteht. bpp wird die Bits pro Pixel enthalten, die in der TGA Datei verwendet werden (dieser Wert sollte 24 oder 32 Bits sein, abhängig davon, ob es einen Alpha-Kanal gibt oder nicht). Die dritte Variable width wird die Breite des TGA Images enthalten. height wird die Höhe des Bildes enthalten und texID wird für die Texturen gebraucht, wenn diese erst einmal erzeugt wurden. Die Struktur wird TextureImage heißen.

Die Zeile direkt nach der Struktur (TextureImage textures[1]) bietet Platz für die eine Textur, die wir in diesem Programm verwenden werden.

typedef    struct                                        // erzeuge eine Struktur
{
    GLubyte    *imageData;                                // Image Daten (bis zu 32 Bits)
    GLuint    bpp;                                    // Bild Farbtiefe in Bits pro Pixel
    GLuint    width;                                    // Image Breite
    GLuint    height;                                    // Image Höhe
    GLuint    texID;                                    // Textur ID, wird verwendet, um eine Textur auszuwählen
} TextureImage;                                        // Structur-Name

TextureImage    textures[1];                                // Speicherplatz für eine Textur

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

Nun zum spaßigen Teil! Dieser Codeabschnitt lädt eine TGA Datei und konvertiert sie in eine Textur, die wir in unserem Programm benutzen können. Es sei anzumerken, dass dieser Code nur 24 oder 32 Bit unkromprimierte TGA Dateien laden kann. Ich hatte genügend damit zu tun, den Code funktional für 24 und 32 Bit TGA's zu halten :) Ich habe niemals gesagt, ich sei ein Genie. Ich möchte noch darauf hinweisen, dass ich den Code nicht ganz alleine geschrieben habe. Viele wirklich gute IOdeen habe ich erhalten, als ich diverse Seiten im Internet gelesen haben. Ich habe mir diese guten Ideen genommen und sie als Code kombiniert, der mit OpenGL funktioniert. Weder einfach noch extrem schwierig!

Wir übergeben diesem Codeabschnitt zwei Parameter. Der erste Parameter zeigt auf einen Speicherbereich, wo wir die Textur speichern können (*texture). Der zweite Parameter ist der Name der Datei, die wir laden wollen (*filename).

Die erste Variable TGAheader[ ] enthält 12 Bytes. Wir vergleichen diese Bytes mit den ersten 12 Bytes, die wir aus der TGA-Datei lesen, um sicher zu gehen, dass es sich wirklich um eine TGA Datei handelt und nicht irgend ein anderes Format.

TGAcompare wird die ersten 12 Bytes enthalten, die wir aus der TGA Datei auslesen. Die Bytes in TGAcompare werden dann mit den Bytes aus TGAheader verglichen, um sicher zu stellen, dass alles passt.

header[ ] wird die ersten 6 WICHTIGEN Bytes aus dem Dateiheader enthalten (Breite, Höhe und Bits pro Pixel).

Die Variable bytesPerPixel wird das Ergebniss enthalten, nachdem wir Bits pro Pixel durch 8 dividiert haben, was uns die Anzahl der Bytes pro verwendeten Pixel zurückliefert.

imagesize wird die Anzahl der Bytes enthalten, aus denen das Bild besteht (Breite * Höhe * Bytes pro Pixel). temp ist eine temporäre Variable die wir zum Tauschen von Bytes später im Programm verwenden werden.

Die letzte Variable type ist eine Variable, die ich dazu verwende, um die korrekten Textur-Erzeugungs-Parameter zu wählen, abhängig davon, ob das TGA 24 oder 32 Bit ist. Wenn die Textur 24 Bit ist, müssen wir den GL_RGB-Modus verwenden, wenn wir die Textur erzeugen. Wenn das TGA 32 Bit ist, müssen wir eine Alpha Komponente einfügen, was bedeutet, dass wir GL_RGBA zu verwenden haben (Standardmäßig nehme ich an, dass das Bild 32 Bit ist, weshalb type am Anfang gleich GL_RGBA ist).

bool LoadTGA(TextureImage *texture, char *filename)                    // Lädt eine TGA Datei in den Speicher
{
    GLubyte        TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};            // Unkomprimierter TGA Header
    GLubyte        TGAcompare[12];                            // wird verwendet um den TGA Header zu vergleichen
    GLubyte        header[6];                            // die ersten 6 nützlichen Bytes aus dem Header
    GLuint        bytesPerPixel;                            // enthält die Anzahl der Bytes pro Pixel, die in der TGA Datei verwendet werden
    GLuint        imageSize;                            // wird verwendet, um die Bildgröße zu speichern
    GLuint        temp;                                // Temporäre Variable
    GLuint        type=GL_RGBA;                            // Setze den Standard GL Modus auf RBGA (32 BPP)

Die folgende erste Zeile öffnet die TGA Datei zum lesen. file ist das Handle, welches wir verwenden werden, um auf die Daten innerhalb der Datei zu zeigen. Der Befehl fopen(filenam, "rb") öffnet die Datei filename und "rb" teilt unserem Programm mit, dass sie zum lesen im binär Modus geöffnet werden soll!

Das if-Statement übernimmt mehrere Aufgaben. Als erstes wird überprüft, ob die Daten überhaupt Daten enthält, wenn es keine Daten gibt, wird NULL zurückgegeben, die Datei wird mit fclose(file) geschlossen und wir geben false zurück.

Wenn die Datei Informationen enthält, versuchen wir die ersten 12 Bytes der Datei in TGAcompare einzulesen. Wir teilen die Zeile wie folgt auf: fread liest sizeof(TGAcompare) (12 Bytes) aus der Datei nach TGAcompare. Dann überprüfen wir, ob die Anzahl der gelesenen Bytes gleich sizeof(TGAcompare) ist, was 12 Bytes sein sollten. Wenn wir keine 12 Bytes in TGAcompare einlesen konnten, wird die Datei geschlossen und false zurückgegeben.

Wenn bisher alles glatt verlief, vergleichen wir die 12 Bytes die wir nach TGAcompare eingelesen haben, mit den 12 Bytes, die wir in TGAheader gespeichert haben. Wenn die Bytes nicht übereinstimmen, wird die Datei geschlossen und false zurückgegeben.

Als letztes, wenn bisher alles glatt verlief, versuchen wir 6 weitere Bytes in den Header zu lesen (die wichtigen Bytes). Wenn keine 6 Bytes verfügbar waren, wird die Datei geschlossen und das Programm liefert false zurück.

    FILE *file = fopen(filename, "rb");                        // Öffne die TGA Datei

    if(    file==NULL ||                                // Existiert die Datei überhaupt?
        fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) ||    // Sind da 12 Bytes zum lesen?
        memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0        ||    // Stimmt der Header überein?
        fread(header,1,sizeof(header),file)!=sizeof(header))            // wenn ja, lese die nächsten 6 Header-Bytes
    {
        if (file == NULL)                            // Existiert die Datei überhaupt? *Hinzugefügt von Jim Strong*
            return false;                            // gebe False zurück
        else
        {
            fclose(file);                            // wenn irgendwas fehl schlug, schließe die Datei
            return false;                            // und gebe False zurück
        }
    }

Wenn alles so verlief wie es sollte, haben wir genügend Informationen beisammen, um einige wichtige Variablen zu definieren. Die erste Variable die wir definieren wollen, ist width. Width soll gleich der Breite des TGAs sein. Wir können die TGA Breite herausfinden, indem wir den Wert, der in header[1] gespeichert ist, mit 256 multiplizieren. Wir addieren dann das LowByte, welches in header[0] gespeichert ist.

Die Höhe (height) wird auf die selbe Weise berechnet, aber anstatt die Werte aus header[0] und header[1] zu nehmen, benutzen wir die Werte, die in header[2] und header[3] gespeichert sind.

Nachdem wir die Breite und Höhe berechnet haben, überprüfen wir, ob entweder die Breite oder die Höhe kleiner gleich 0 ist. Wenn eine der beiden Variablen kleiner gleich 0 ist, wird die Datei geschlossen und false zurückgegeben.

Wir wollen ebenfalls überprüfen, ob das TGA ein 24 oder 32 Bit Image ist. Dazu überprüfen wir den Wert, der in header[4] gespeichert ist. Wenn der Wert weder 24 noch 32 (Bit) ist, wird die Datei geschlossen und false zurückgegeben.

Für den Fall, dass Sie es noch nicht mitbekommen haben. Ein Rückgabewert gleich false veranlasst das Programm mit der Meldung "Initialization Failed" abzubrechen. Stellen Sie sicher, dass Ihr TGA ein unkomprimiertes 24 oder 32 Bit Image ist!

    texture->width  = header[1] * 256 + header[0];                    // bestimmt die TGA Breite    (highbyte*256+lowbyte)
    texture->height = header[3] * 256 + header[2];                    // bestimmt die TGA Höhe    (highbyte*256+lowbyte)

     if(    texture->width    <=0    ||                        // Ist die Breite kleiner gleich 0
        texture->height    <=0    ||                        // Ist die Höhe kleiner gleich 0
        (header[4]!=24 && header[4]!=32))                    // Ist das TGA 24 oder 32 Bit?
    {
        fclose(file);                                // wenn irgendwas fehl schlug, schliße die Datei
        return false;                                // gebe False zurück
    }

Nun wo wir die Bildbreite und Höhe berechnet haben, müssen wir die Bits pro Pixel berechnen, die Bytes pro Pixel und die Bildgröße.

Der Wert in header[4] enthält die Bits pro Pixel. Darum setzen wir bpp gleich header[4].

Wenn Sie auch nur irgendwas über Bits und Bytes wissen, wissen Sie, dass ein Byte aus 8 Bits besteht. Um herauszufinden wieviele Bytes pro Pixel das TGA verwendet, müssen wir lediglich die Bits pro Pixel durch 8 teilen. Wenn das Bilder 32 Bit hat, wird bytesPerPixel gleich 4 sein. Wenn das Bild 24 Bit hat, wird bytesPerPixel gleich 3 sein.

Um die Bildgröße zu berechnen, multiplizieren wir width * height * bytesPerPixel. Das Ergebnis wird in imageSize gespeichert. Wenn das Bild 100x100x32 Bit wäre, würde unsere Bildgröße gleich 1ßß * 100 * 32/8 sein was 1000 * 4 oder 40000 Bytes entsprechen würde!

    texture->bpp    = header[4];                            // ermittle die Bits Pro Pixel (24 oder 32) des TGA
    bytesPerPixel    = texture->bpp/8;                        // Dividiere durch 8 um die Bytes pro Pixel zu erhalten
    imageSize    = texture->width*texture->height*bytesPerPixel;            // berechne den benötigten Speicher für die TGA Daten

Nun, da wir wissen, wieviel Bytes unser Bild benötigt, müssen wir den entsprechenden Speicher allozieren. Das macht die erste folgende Zeile. imageData wird auf einen Speicherabschnitt zeigen, der groß genug ist, um unser Bild aufzunehmen. malloc(imagesize) alloziert den Speicher (reserviert Speicher für uns, den wir verwenden können) basierend auf der Speichermenge (imageSize) die wir angefordert haben.

Das "if" Statement hat mehrere Aufgaben. Als erstes überprüft es, ob der Speicher korrekt alloziert wurde. wenn nicht, ist imageData gleich NULL, die Datei wird geschlossen und false wird zurückgegeben.

Wenn der Speicher alloziert wurde, versuchen wir die Image-Daten aus der Datei in den allozierten Speicher zu lesen. Die Zeile fread(texture->imageData, 1, imageSize, file) macht das. imageData zeigt auf den Speicherbereich, wo wir die Daten speichern wollen. 1 ist die Größe der Daten, die wir in Bytes einlesen wollen (wir wollen 1 Byte zur Zeit einlesen). imageSize ist die Gesamtanzahl der Bytes, die wir lesen wollen. Da imageSize gleich der absoluten Menge des Speichers ist, die benötigt wird, um das Bild zu speichern, lesen wir somit das gesamte Bild ein. file ist das Handle unser geöffneten Datei.

Nachdem die Daten eingelesen wurden, überprüfen wir, ob die eingelesene Menge Daten gleich dem Wert ist, der in imageSize gespeichert ist. Wenn die Menge der eingelesenen Daten und der Wert aus imageSize nicht gleich sind, ist etwas schief gelaufen. Wenn irgendwelche Daten eingelesen wurden, geben wir diese frei (den Speicher, den wir alloziert haben). Die Datei wird geschlossen und false wird zurückgegeben.

    texture->imageData=(GLubyte *)malloc(imageSize);                // Reserviere Speicher, wo wir unsere TGA Daten speichern können

    if(    texture->imageData==NULL ||                        // Existiert der Speicherplatz?
        fread(texture->imageData, 1, imageSize, file)!=imageSize)        // Stimmt die Bildgröße mit dem reservierten Speicher überein?
    {
        if(texture->imageData!=NULL)                        // Wurden Bild Daten geladen?
            free(texture->imageData);                    // wenn ja, gebe die Bild-Daten wieder frei

        fclose(file);                                // Schließe die Datei
        return false;                                // gebe False zurück
    }

Wenn die Daten korrekt geladen wurden, wird alles gut :) Alles was wir nun noch machen müssen, ist, die Rot und Blau Bytes vertauschen. In OpenGL verwenden wir RGB (rot, grün, blau). Die Daten in einer TGA Datei sind als BGR (blau, grün, rot) gespeichert. Wenn wir die Rot und Blau Bytes nicht tauschen würden, wäre alles, was im Bild rot sein sollte blau und alles was blau sein sollte, wäre rot.

Als erstes erzeugen wir eine Schleife (i), die von 0 bis imageSize läuft. Dadurch können wir durch die gesamten Bilddaten iterieren. Unsere Schleife wird in 3er-Schritten inkrementiert (0, 3, 6, 9, etc), wenn die TGA Datei 24 Bit hat beziehungsweise um 4 (0, 4, 8, 12, etc.) wenn das Bild 32 Bit ist. Der Grund, warum wir um diese Schritte inkrementieren, ist der, dass i somit immer das erste Byte ([b]laues Byte) in unserer Gruppe von 3 oder 4 Bytes ist.

Innerhalb der Schleife speichern wir das [b]laue Byte in unserer temporären Variable. Wir holen dann das rote Byte, welches an der Position texture->imageData[i+2] gespeichert ist (denken Sie daran, das TGAs die Farben als BGR[A] speichert. B ist gleich i+0, G ist gleich i+1 und R ist gleich i+2) und speichern es, wo das [b]laue Byte war.

Als letztes speichern wir das [b]laue Byte, welches wir in der temporären Variable gespeichert haben, an dem Ort, wo vorher das [r]ote Byte war (i+2) und schließen dann die Datei mit fclose(file).

Wenn alles glatt verlief, sollte das TGA nun im Speicher gespeichert sein und als OpenGL Textur verwendbar sein!

    for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel)                // iteriere durch die Bilddaten
    {                                        // vertausche das erste und dritte Byte ('R'ot und 'B'lau)
        temp=texture->imageData[i];                        // speichere den Wert 'i' der Bilddaten temporär
        texture->imageData[i] = texture->imageData[i + 2];            // Setze das erste Byte gleich dem Wert des dritten Bytes
        texture->imageData[i + 2] = temp;                    // Setze das dritte Byte gleich dem Wert aus 'temp' (Wert des ursprünglich ersten Bytes)
    }

    fclose (file);                                    // Schließe die Datei

Nun da wir für uns nützliche Daten haben, ist es an der Zeit, eine Textur daraus zu machen. Wir fangen damit an, indem wir OpenGL mitteilen, dass wir eine Textur im Speicher erstellen wollen und zwar da, wo &texture[0].texID hinzeigt.

Es ist wichtig, dass Sie einige Dinge verstehen, bevor wir weiter machen. Im InitGL() Code rufen wir LoadTGA() auf und übergeben der Funktion zwei Parameter. Der erste Parameter ist &textures[0]. In LoadTGA() referenzieren wir diesen Parameter nicht. Wir referenzieren &texture[0] (kein 's' am Ende). Wenn wir &texture[0] modifizieren, modifizieren wir eigentlich textures[0]. texture[0] nimmt die Identität von textures[0] an. Ich hoffe das macht Sinn.

Wenn wir eine zweite Textur erzeugen wollten, würden wir als Paramtere &textures[1] übergeben. In LoadTGA() würden wir weiterhin texture[0] modifizieren und damit gleichzeitig textures[1]. Wenn wir &textures[2] übergeben würden, würde texture[0] die Identität von &textures[2] annehmen, etc.

Schwierig zu erklären, einfach zu verstehen. Natürlich wäre ich nicht vorher glücklich, bis ich das wirklich geklärt habe :) Letztes Beispiel: sagen wir ich hatte eine Box. Ich nenne sie Box #10. Ich gab sie meinem Freund und bat ihn, diese zu füllen. Mein Freund kümmert sich nicht darum, welche Nummer sie hat. Für ihn ist es nur eine Box. Deswegen füllt er das, was er "nur eine Box" nennt. Er gibt sie mir zurück. Für mich füllte er nur Box #10 für mich. Für ihn füllte er lediglich eine Box. Wenn ich ihm eine andere Box namens Box #11 geben würden und sagen würde: hey, könntest du diese für mich füllen. Er würde wieder nur denken, es sein nur eine "Box". Er füllt sie und gibt sie mir gefüllt zurück. Für mich würde er Box #11 füllen.

Wenn ich LoadTGA &textures[1] übergebe, denkt die Funktion lediglich an &texture[0]. Sie füllt es mit den Textur-Informationen und wenn das getan ist, erhalte ich eine funktionierende textures[1]. Wenn ich LoadTGA &textures[2] übergebe, denkt sie wieder nur an &texture[0]. Sie füllt sie mit Daten und ich erhalte eine funktionierende textures[2]. Macht Sinn :)

Wie dem auch sei... zurück zum Code. Wir teilen LoadTGA() mit, unsere Textur zu erzeugen. Wir binden die Textur und teilen OpenGL mit, wir wollen diese mit einem linearen Filter.

    // erzeuge eine Textur aus Daten
    glGenTextures(1, &texture[0].texID);                        // erzeuge OpenGL Textur IDs

    glBindTexture(GL_TEXTURE_2D, texture[0].texID);                    // Binde unsere Textur
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);        // Linearer Filter
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);        // Linearer Filter

Nun überprüfen wir, ob die TGA Datei 24 oder 32 Bit hat. Wenn das TGA 24 Bit hat, setzen wir die Art auf GL_RGB (kein Alpha-Kanal). Wenn wir das nicht machen würden, würde OpenGL versuchen, eine Textur mit Alpha-Kanal zu erstellen. Die Alpha-Informationen wären nicht da und das Programm würde wahrscheinlich abstürzen oder eine Fehlermeldung ausgeben.

    if (texture[0].bpp==24)                                // Hat das TGA 24 Bits
    {
        type=GL_RGB;                                // wenn ja, setze 'type' auf GL_RGB
    }

Nun erzeugen wir unsere Textur, auf die selbe Art und Weise, wie wir es immer getan haben. Aber anstatt die Art direkt anzugeben (RL_RGB oder GL_RGBA), geben wir die Variable type an. Auf diese Weise, wird die Art GL_RGB sein, wenn das Programm erkannt hat, dass es sich bei dem TGA um 24 Bit handelt.

Nachdem die Textur erzeugt wurde, geben wir true zurück. Damit weißt der InitGL() Code, dass alles in Ordnung war.

    glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);

    return true;                                    // Textur Erzeugung verlief in Ordnung, gebe True zurück
}

Der folgende Code ist unsere Standard-Erzeugung-eines-Fonts-aus-einer-Textur-Code. Sie werden alle diesen Code schonmal gesehen haben, wenn Sie sich die bisherigen Tutorials angesehen haben. Nichts wirklich neues hier, aber ich dacht, ich füge den Code hier nochmal ein, damit es einfacher ist, dem Programm zu folgen.

Der einzige wirkliche Unterschied ist, dass ich textures[0].texID binde. Diese zeigt auf die Font Textur. Der einzige wirklich Unterschied ist, dass .texID hinzugefügt wurde.

GLvoid BuildFont(GLvoid)                                // erzeuge unsere Font Display Liste
{
    base=glGenLists(256);                                // erzeuge 256 Display Listen
    glBindTexture(GL_TEXTURE_2D, textures[0].texID);                // wähle unsere Font Textur aus
    for (int loop1=0; loop1// iteriere durch alle 256 Listen
    {
        float cx=float(loop1%16)/16.0f;                        // X Position des aktuellen Zeichens
        float cy=float(loop1/16)/16.0f;                        // Y Position des aktuellen Zeichens

        glNewList(base+loop1,GL_COMPILE);                    // fange an, eine Liste zu erzeugen
            glBegin(GL_QUADS);                        // benutze einen Quad für jedes Zeichen
                glTexCoord2f(cx,1.0f-cy-0.0625f);            // Texturkoordinate (unten links)
                glVertex2d(0,16);                    // Vertexkoordinate (unten links)
                glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f);        // Texturkoordinate (unten rechts)
                glVertex2i(16,16);                    // Vertexkoordinate (unten rechts)
                glTexCoord2f(cx+0.0625f,1.0f-cy-0.001f);        // Texturkoordinate (oben rechts)
                glVertex2i(16,0);                    // Vertexkoordinate (oben rechts)
                glTexCoord2f(cx,1.0f-cy-0.001f);            // Texturkoordinate (oben links)
                glVertex2i(0,0);                    // Vertexkoordinate (oben links)
            glEnd();                            // fertig mit dem Erzeugen unseres Quad (Zeichen)
            glTranslated(14,0,0);                        // bewege sich rechts neben den Buchstaben
        glEndList();                                // fertig mit dem erzeugen der Display Liste
    }                                        // durchlaufe so lange bis alle 256 erzeugt wurden
}

KillFont ist immer noch das Selbe. Wir erzeugen 256 Display Listen, weshalb wir 256 Display Listen zerstören müssen, wenn wir das Programm schließen.

GLvoid KillFont(GLvoid)                                    // lösche den Font aus dem Speicher
{
    glDeleteLists(base,256);                            // lösche alle 256 Display Listen
}

Der glPrint() Code hat sich nur ein klein wenig verändert. Die Buchstaben sind alle auf der Y-Achse gestreckt. Dadurch werden die Buchstaben sehr hoch. Ich habe den Rest des Code in anderen Tutorials bereits erklärt. Die Streckung wird durch den glScalef(x,y,z) Befehl realisiert. Wir belassen das Verhältnis bei 1.0 auf der X-Achse, wir verdoppeln die Größe auf der Y-Achse (2.0) und wir lassen sie bei 1.0 auf der Z-Achse.

GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...)                // Wo die Textausgabe geschieht
{
    char    text[1024];                                // enthält unseren String
    va_list    ap;                                    // Zeiger auf die Argumentenliste

    if (fmt == NULL)                                // wenn kein Text vorhanden ist
        return;                                    // mache nichts

    va_start(ap, fmt);                                // Parse den String für Variablen
        vsprintf(text, fmt, ap);                            // und konvertiere Symbole in Zahlen
    va_end(ap);                                    // Ergebnisse werden in text gespeichert

    if (set>1)                                    // hat der Benutzer einen ungültigen Zeichensatz ausgewählt?
    {
        set=1;                                    // wenn dem so ist, wähle Set 1 (kursiv)
    }

    glEnable(GL_TEXTURE_2D);                            // aktiviere Texturmapping
    glLoadIdentity();                                // Resette die Modelview Matrix
    glTranslated(x,y,0);                                // Positioniere  den Text (0,0 - oben links)
    glListBase(base-32+(128*set));                            // wähle den Font Satz (0 oder 1)

    glScalef(1.0f,2.0f,1.0f);                            // Mache den Text um den Faktor 2 größer

    glCallLists(strlen(text),GL_UNSIGNED_BYTE, text);                // gebe den Text auf dem Screen aus
    glDisable(GL_TEXTURE_2D);                            // deaktiviere Texturmapping
}

ReSizeGLScene() setzt eine orthogonale Sicht. Nichts wirklich neues. 0,1 ist die obere linke Ecke des Screens. 639,480 ist unten rechts. Dadurch erhalten wir exakte Screen-Koordinaten mit einer Auflösung von 640 x 480. Beachten Sie, dass wir den Wert von swidth gleich der aktuellen Fenster-Breite setzen und wir setzen den Wert von sheight gleich dem Wert der aktuellen Fenster-Höhe. Wann immer das Fenster in der Größe geändert oder verschoben wird, werden sheight und swidth aktualisiert.

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)                    // verändert die Größe und initialisiert das GL-Fenster
{
    swidth=width;                                    // Setze Scissor Breite gleich der Fensterbreite
    sheight=height;                                    // Setze Scissor Höhe gleich der Fensterhöhe
    if (height==0)                                    // verhindere eine Division durch 0, indem
    {
        height=1;                                // die Höhe auf 1 gesetzt wird
    }
    glViewport(0,0,width,height);                            // Resette den aktuellen Viewport
    glMatrixMode(GL_PROJECTION);                            // wähle die Projektions Matrix aus
    glLoadIdentity();                                // Resette die Projektions Matrix
    glOrtho(0.0f,640,480,0.0f,-1.0f,1.0f);                        // erzeuge Ortho 640x480 View (0,0 ist oben links)
    glMatrixMode(GL_MODELVIEW);                            // wähle die Modelview Matrix aus
    glLoadIdentity();                                // Resette die Modelview Matrix
}

Der Init Code ist recht minimal. Wir laden unsere TGA-Datei. BEachten Sie, dass der erste übergebene Parameter &textures[0] ist. Der zweite Parameter ist der Name der Datei, die wir laden wollen. In diesem Fall, wollen wir die Datei Font.TGA laden. Wenn LoadTGA() aus irgendwelche Gründen false zurückgibt, wird das if Statement ebenfalls flase zurückgeben, was das PRogramm veranlasst, mit der Nachricht "initialization failed" sich zu beenden.

Wenn Sie eine zweite Textur laden wollten, könnte Sie folgenden Code verwenden: if ((!LoadTGA(&textures[0],"image1.tga")) || (!LoadTGA(&textures[1],"image2.tga"))) { }

Nachdem wir das TGA geladen haben (unsere Textur erzeugt haben), erzeugen wir unseren Font, setzen Shading auf smooth, setzen die Hintergrundfarbe auf schwarz, aktivieren die Löschung des Depth Buffers und wählen unsere Font-Textur aus (binden sie).

Als letztes geben wir true zurück, damit unser Programm weiß, dass die Initialisierung glatt verlief.

int InitGL(GLvoid)                                    // Der ganze Setup Kram für OpenGL kommt hier rein
{
    if (!LoadTGA(&textures[0],"Data/Font.TGA"))                    // Lade die Font Textur
    {
        return false;                                // wenn das Laden fehl schlug, gebe False zurück
    }

    BuildFont();                                    // erzeuge den Font

    glShadeModel(GL_SMOOTH);                            // aktiviere Smooth Shading
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);                        // schwarzer Hintergrund
    glClearDepth(1.0f);                                // Depth Buffer Setup
    glBindTexture(GL_TEXTURE_2D, textures[0].texID);                // wähle unsere Font Textur aus

    return TRUE;                                    // Initialisierung war in Ordnung
}

Der Zeichnen-Code ist komplett neu :) Wir fangen damit an, eine char-Variable namens token zu erzeugen. Token wird den geparseten Text später enthalten.

Wir haben eine weitere Variable namens cnt. Ich benutzen diese Variable sowohl zum Zählen der Anzahl der unterstützten Extensionen als auch zur Positionierung des Textes auf dem Screen. cnt wird bei jedem Aufruf von DrawGlScene auf null gesetzt.

Wir löschen den Screen und den Depth Buffer und setzen die Farbe auf ein helles Rot (volle Rot-Intensität, 50% grün und 50% blau). An der Position 50 auf der X-Achse und 16 auf der Y-Achse geben wir das Wort "Renderer" aus. Wir geben ebenso "Vendor" und "Version" am oberen Rand des Screens aus. Der Grund warum nicht alle Wörter bei 50 auf der X-Achse anfangen ist, dass ich die Wörter rechtsbündig ausgerichtet habe (sie schließen also zusammen an der rechten Seite ab).

int DrawGLScene(GLvoid)                                    // Hier kommt der Zeichnen-Kram hin
{
    char    *token;                                    // Speicherplatz für unseren Token
    int    cnt=0;                                    // Lokale Zählervariable

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                // lösche Screen und Depth Buffer

    glColor3f(1.0f,0.5f,0.5f);                            // Setze Farbe auf helles Rot
    glPrint(50,16,1,"Renderer");                            // gebe Renderer aus
    glPrint(80,48,1,"Vendor");                            // gebe Vendor Name aus
    glPrint(66,80,1,"Version");                            // gebe Version aus

Nun, da wir den Text auf dem Screen haben, ändern wir die Farbe auf Orange und ermitteln den Renderer, Vendor (Hersteller) und Versionsnummer der Grafikkarte. Wir machen das, indem wir GL_RENDERER, GL_VENDOR & GL_VERSION an glGetString() übergeben. glGetString gibt den angeforderten Renderer Namen, Vendor Namen und Versionsnummer zurück. Die zurückgegebenen Informationen werden Text sein, so dass wir die zurückgelieferten Informationen von glGetString lediglich als char casten müssen. Das bedeutet, dass wir dem Programm mitteilen, dass wir die zurückgelieferten Informationen als Zeichen (Text) zurückgeliefert bekommen wollen. Wenn Sie das (char *) nicht einfügen würden, würden Sie eine Fehlermeldung erhalten. Wir geben Text aus, weshalb wir auch Text zurückgeliefert bekommen müssen. Wir ermitteln alle drei Teile der Informationen und schreiben die Informationen, die wir ermittelt haben, rechts neben den vorherigen Text.

Die Informationen, die wir von glGetString(GL_RENDERER) erhalten, werden neben den roten Text "Renderer" geschrieben werden, die Informationen, die wir von glGetString(GL_VENDOR) erhalten, werden rechts neben "Vendor" ausgegeben, etc.

Ich würde das casten gerne genauer erklären, aber ich kenne keinen wirklich guten Weg, es zu erklären. Wenn jemand eine gute Erklärung hat, möge er sie einsenden und ich werde meine Erklärung entsprechend verändern.

Nachdem wir die Renderer Information, Vendor Information und Versionsnummer auf den Screen gebracht haben, ändern wir die Farbe auf ein helles blau und gebe "NeHe Productions" am unteren Rand des Screens aus :) Natürlich können Sie das beliebig ändern, wenn Sie wollen.

    glColor3f(1.0f,0.7f,0.4f);                            // Setze Farbe auf Orange
    glPrint(200,16,1,(char *)glGetString(GL_RENDERER));                // zeige  Renderer an
    glPrint(200,48,1,(char *)glGetString(GL_VENDOR));                // zeige Vendor Name an
    glPrint(200,80,1,(char *)glGetString(GL_VERSION));                // zeige Version an

    glColor3f(0.5f,0.5f,1.0f);                            // Setze Farbe auf helles blau
    glPrint(192,432,1,"NeHe Productions");                        // gebe NeHe Productions am unteren Rand des Screens aus

Nun zeichnen wir einen netten weißen Rand um den Screen und den Text herum. Wir fangen an, die Modelview Matrix zu resetten. Da wir Text ausgegeben haben und nicht unbedingt bei 0,0 sind, ist es besser das zu machen.

Dann setzen wir die Farbe auf weiß und fangen an unseren Rahmen zu zeichnen. Ein Line Strip ist eigentlich recht einfach zu verwenden. Sie teilen OpenGL mit, dass Sie ein Line Strip zeichne wollen und zwar mit glBegin(GL_LINE_STRIP). Dann setzen wir den ersten Vertex. Unser erster Vertex auf der äußersten rechten Seite sein und etwa 63 Pixel über dem unteren Rand des Screen (639 auf der X-Achse, 417 auf der Y-Achse). Dann setzen wir den zweiten Vertex. Wir bleiben auf der selben Position auf der Y-Achse (417), aber wir bewegen uns auf die äußerste linke Seite des Screens auf der X-Achse (0). Eine Linie wird von der rechten Seite des Screens (639,417) zur linken Seite des Screens (0,417) gezeichnet.

Sie benötigen mindestens zwei Vertices, um eine Linie zu zeichnen (sollte Sinn machen). Von der linken Seite des Screens bewegen wir uns nach unten, recht und dann nach oben (128 auf der Y-Achse).

Wir zeichnen dann einen weiteren Line Strip und zeichnen eine zweite Box am oberen Rand des Screens. Wenn Sie VIELE verbundene Linien zeichnen müssen, können Line Strips definitiv die Codemenge minimieren, die benötigt wird, im gegensatz zu regulären Linien (GL_LINES).

    glLoadIdentity();                                // Resette die ModelView Matrix
    glColor3f(1.0f,1.0f,1.0f);                            // Setze die Farbe auf Weiß
    glBegin(GL_LINE_STRIP);                                // fange an Line Strips zu zeichnen (etwas neues)
        glVertex2d(639,417);                            // oben rechts der unteren Box
        glVertex2d(  0,417);                            // oben links der unteren Box
        glVertex2d(  0,480);                            // unten links der unteren Box
        glVertex2d(639,480);                            // unten rechts der unteren Box
        glVertex2d(639,128);                            // hoch nach oben rechts der oberen Box
    glEnd();                                    // fertig mit dem ersten Line Strip
    glBegin(GL_LINE_STRIP);                                // fange an, einen weiteren Line Strip zu zeichnen
        glVertex2d(  0,128);                            // unten links der oberen Box
        glVertex2d(639,128);                            // unten rechts der oberen Box        
        glVertex2d(639,  1);                            // oben rechts der oberen Box
        glVertex2d(  0,  1);                            // oben links der oberen Box
        glVertex2d(  0,417);                            // zurück nach oben links der unteren Box
    glEnd();                                    // fertig mit dem zweiten Line Strip

Nun zu etwas Neuem. Ein wunderbarer GL-Befehl namens glScissor(x,y,w,h). Dieser Befehl erzeugt für Sie fast das, was Sie Fenster nennen würden. Wenn GL_SCISSOR_TEST aktiviert ist, ist der einzige Teil des Screens, den Sie verändern können, der Teil innerhalb des Scissor Fensters. Die erste folgende Zeile erzeugt ein Scissor Fenster, welches mit 1 auf der X-Achse beginnt und 13.5% (0.135...f) auf dem Weg nach unten des Screens auf der Y-Achse. Das Scissor Fenster wird 638 Pixel breit (swidth-2) und 59.7% (0.597...f) hoch sein.

In der nächsten Zeile aktivieren wir Scissor Testing. Alles was wir AUßERHALB des Scissor Fensters zeichnen, wird nicht zu sehen sein. Sie könnten ein RIESIGES Quadrat auf dem Screen zeichnen, von 0,0 bis 639,480 und Sie würden nur innerhalb des Scissor Fensters das Quadrat sehen, der Rest des Screens würde nicht beeinflusst werden. Sehr netter Befehl!

Die dritte Codezeile erzeugt eine Variable namens text, die die Buchstaben enthalten wird, die von lGetString(GL_EXTENSIONS) zurückgegeben werden. malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1) alloziert genügend Speicher, um den gesamten zurückgegebenen String +1 zu speichern (wenn der String 50 Buchstaben enthalten würde, wäre text in der Lage alle 50 Zeichen zu speichern).

Die nächste Zeile kopiert die GL_EXTENSIONS Information nach text. Wenn wir die GL_EXTENSIONS Information direkt modifizieren, würden große Probleme auftauchen, deshalb kopieren wir die Informationen nach text und manipulieren die Informationen, die in text gespeichert sind. Eigentlich nehmen wir nur eine Kopie und speichern sie in der Variable text.

    glScissor(1    ,int(0.135416f*sheight),swidth-2,int(0.597916f*sheight));    // Definiere die  Scissor Region
    glEnable(GL_SCISSOR_TEST);                            // aktiviere Scissor Testing

    char* text=(char*)malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1);        // Alloziere Speicher für unseren Extension String
    strcpy (text,(char *)glGetString(GL_EXTENSIONS));                // ermittle die Extension Liste, speicher sei in Text

Nun zu etwas Neuem. Nehmen wir an, dass, nachdem wir die Extension Information von der Grafikkarte ermittelt haben, die Variable text den folgenden String gespeichert hätte..."GL_ARB_multitexture GL_EXT_abgr GL_EXT_bgra". strtok(TextToAnalyze,TextToFind) wird die Variable text so lange scannen, bis ein " " (Leerzeichen) gefunden wird. Wenn ein Leerzeichen gefunden wurde, wird der Text BIS zum Leerzeichen in die Variable token kopiert. In unserem kleinen Beispiel wäre token dann gleich "GL_ARB_multitexture". Das Leerzeichen wird dann mit einem Marker ersetzt. Mehr darüber in einer Minute.

Als nächstes erzeugen wir eine Schleife, die anhält, sobald keine weitere Informationen in text enthalten sind. Wenn es keine Informationen in text gibt, wird token gleich nichts (NULL) sein und die Schleife anhalten.

Wir inkrementieren die Zählervariable (cnt) um eins und überprüfen dann, ob der Wert in cnt größer als der Wert in maxtokens ist. Wenn cnt größer als maxtokens ist, setzen wir maxtokens gleich cnt. Auf diese Weise wird maxtokens gleich 20 sein, sobald der Zähler gleich 20 ist. So können wir uns ganz leicht den maximalen Wert von cnt merken.

    token=strtok(text," ");                                // Parse 'text' nach Wörtern, die durch " " (Leerzeichen) getrennt sind
    while(token!=NULL)                                // so lange der Token nicht NULL ist
    {
        cnt++;                                    // inkrementiere den Zähler
        if (cnt>maxtokens)                            // Ist 'maxtokens' kleiner als 'cnt'
        {
            maxtokens=cnt;                            // wenn dem so ist, setze 'maxtokens' gleich 'cnt'
        }

Nun haben wir die erste Extension aus unserer Liste von Extensionen in der Variable token gespeichert. Als nächstes setzen wir die Farbe auf ein helles grün. Wir geben dann die Variable cnt auf der linken Seite des Screens aus. Beachten Sie, dass wir bei 0 auf der X-Achse ausgeben. Das sollte den linken (weißen) Rahmen löschen, den wir gezeichnet haben, da aber Scissor Testing aktiviert ist, werden Pixel, die bei 0 gezeichnet werden, nicht modifiziert werden. Der Rahmen kann nicht übergezeichnet werden.

Die Variable wird auf der äußersten linken Seite des Screens (0 auf der X-Achse) gezeichnet. Wir fangen mit dem Zeichnen bei 96 auf der Y-Achse an. Um den Text nicht nicht auf den selben Punkt auf den Screen zu zeichnen, addieren wir (cnt*32) zu 96. Wenn wir also die erste Extension anzeigen, wird cnt gleich 1 sein und der Text wird bei 96+(32*1) (128) auf der Y-Achse gezeichnet. Wenn wir die zweite Extension anzeigen, wird cnt gleich 2 sein und der Text wird bei 96+(32*2) (160) auf der Y-Achse gezeichnet.

Beachten Sie, dass ich ebenfalls scroll subtrahiere. Beim Start des Programms wird scroll gleich 0 sein. Deshalb wird unsere erste Textzeile bei 96+(32*1)-0 gezeichnet. Wenn Sie die Pfeil nach unten Taste drücken, wird scroll um 2 inkrementiert. Wenn Scroll 4 wäre, würde der Text bei 96+(32*1)-4 gezeichnet werden. Das bedeutet, dass der Text bei 124 anstatt 128 auf der Y-Achse gezeichnet werden würde, da scroll gleich 4 wäre. Das obere Ende unseres Scissor Fensters ended bei 128 auf der Y-Achse. Jeder Teil des Textes, der in den Zeilen 124-127 auf der Y-Achse gezeichnet wird, wird nicht auf dem Screen erscheinen.

Das selbe gilt für den unteren Teil des Screens. Wenn cnt gleich 11 wäre und scroll gleich 0, würde der Text bei 96+(32*11)-0 was gleich 448 wäre, auf der Y-Achse ausgegeben werden. Da das Scissor Fenster es uns nur erlaubt, bis zu Zeile 416 auf der Y-Achse zu zeichnen, würde der Text überhaupt nicht angezeigt werden.

Das finale Ergebnisse, welches wir erhalten, ist ein scrollbares Fenster, welches es uns erlaubt nur 288/32 (9) Textzeilen anzusehen. 288 ist die Höhe unseres Scissor Fensters. 32 ist die Höhe des Textes. Indem wir den Wert von scroll ändern, können wir den Text hoch und runter scollen (offset des Textes).

Der Effekt ähnelt dem eines Filprojektors. Der Film rollt an der Linse vorbei und alles was Sie sehen können, ist den aktuellen Frame. Sie sehen weder den Teil über oder unterhalb des Frames. Die Linse fungiert als ein Fenster, ähnlich dem Fenster, dass durch den Scissor Test erzeugt wird.



        glColor3f(0.5f,1.0f,0.5f);                        // Setze die Farbe auf ein helles grün
        glPrint(0,96+(cnt*32)-scroll,0,"%i",cnt);                // gebe die aktuelle Extensions Nummer aus

Nachdem wir den aktuellen Zähler auf dem Screen ausgegeben haben, ändern wir die Farbe auf gelb, bewegen uns 50 Pixel nach rechts auf der X-Achse und wir bringen den Text, der in der Variable Token gespeichert, auf den Screen.

Um nochmal auf das obige Beispiel zurückzukommen: die erste Textzeile die auf dem Screen ausgegeben werden würde, würde wie folgt aussehen:

1 GL_ARB_multitexture

        glColor3f(1.0f,1.0f,0.5f);                        // Setze die Farbe auf gelb
        glPrint(50,96+(cnt*32)-scroll,0,token);                    // gebe den aktuellen Token aus (Parsed Extension Name)

Nachdem wir den Wert von Token auf dem Screen ausgegeben haben, müssen wir die Variable text überprüfen, um zu sehen, ob weitere Extensionen unterstützt werden. Anstat token=strtok(text," ") zu verwenden, wie wir es weiter oben gemacht haben, ersetzen wir text mit NULL. Das teilt dem Befehl strtok mit, dass beginnend beim letzten marker bis zum NÄCHSTEN Leerzeichn im String (text) gesucht werden soll.

In unserem obigen Beispiel ("GL_ARB_multitexturemarkerGL_EXT_abgr GL_EXT_bgra") würde es einen Marker nach dem Text "GL_ARB_multitexture" geben. Die folgende Zeile fängt mit der Suche AB dem marker an bis zum nächsten Leerzeichen. Alles vom Marker bis zum nächsten Leerzeichen wird in token gespeichert. token sollte danach gleich "GL_EXT_abgr" sein, und text wäre gleich "GL_ARB_multitexturemarkerGL_EXT_abgrmarkerGL_EXT_bgra".

Wenn strtok() keinen Text mehr hat, der in token gespeichert werden kann, wird token gleich NULL und die Schleife stoppt.

        token=strtok(NULL," ");                            // Suche nach dem nächsten Token
    }

Nachdem alle Extensionen aus der Variable text geparsed wurde, können wir Scissor Testing deaktivieren und die Variable text wieder freigeben. Damit wird der Speicher freigegeben, den wir benutzt haben, um die Informationen zu speichern, die wir von glGetString(GL_EXTENSIONS) erhalten haben.

Das nächste Mal, dass DrawGLScene() aufgerufen wird, wird neuer Speicher alloziert. Eine aktuelle Kopie der von glGetStrings(GL_EXTENSIONS) zurückgegebenen Informationen werden in die Variable text kopiert und der gesamte Prozess fängt von vorne an.

    glDisable(GL_SCISSOR_TEST);                            // deaktiviere Scissor Testing

    free (text);                                    // gebe allozierten Speicher frei

Die nächste folgende Zeile ist nicht unbedingt notwendig, aber ich dachte, es wäre eine gute Idee, darüber zu sprechen, so dass jeder weiß, dass es diesen Befehl gibt. Der Befehl glFlush() teilt OpenGL mit, das es das beenden soll, was es gerade macht. Wenn Sie jemals ein Flackern in Ihren Programmen bemerkt haben (Quads die verschwinden, etc.), versuchen Sie den Flush Befehl am Ende von DrawGLScene einzufügen. Damit wird die Rendering Pipeline geflusht. Sie werden ggf. ein Flackern bemerken, wenn Ihr Programm nicht genügend Zeit hat, das Rendern der Szene rechtzeitig zu beenden.

Als letztes geben wir true zurück, um zu zeigen, dass alle glatt verlief.

    glFlush();                                    // Flushe die Rendering Pipeline
    return TRUE;                                    // alles war in Ordnung
}

Das einzige was zu KillGLWindow() anzumerken ist, dass ich am Ende KillFont() eingefügt habe. Auf diese Weise wird der Font freigeben, sobald das Fenster gekillt wird.

GLvoid KillGLWindow(GLvoid)                                // Entferne das Fenster korrekt
{
    if (fullscreen)                                    // Sind wir im Fullscreen Modus?
    {
        ChangeDisplaySettings(NULL,0);                        // Wenn ja, wechsle zurück zum Desktop
        ShowCursor(TRUE);                            // Zeige den Maus-Zeiger
    }

    if (hRC)                                    // Haben wir einen Rendering Context?
    {
        if (!wglMakeCurrent(NULL,NULL))                        // Können wir den DC und RC Kontext freigeben?
        {
            MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        }

        if (!wglDeleteContext(hRC))                        // Können wir den RC löschen?
        {
            MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        }
        hRC=NULL;                                // Setze RC auf NULL
    }

    if (hDC && !ReleaseDC(hWnd,hDC))                        // Können wir DC freigeben?
    {
        MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hDC=NULL;                                // Setze DC auf NULL
    }

    if (hWnd && !DestroyWindow(hWnd))                        // Können wir das Fenster zerstören?
    {
        MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hWnd=NULL;                                // Setze hWnd auf NULL
    }

    if (!UnregisterClass("OpenGL",hInstance))                    // Können wir die Klasse de-registrieren?
    {
        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hInstance=NULL;                                // Setze hInstance auf NULL
    }

    KillFont();                                    // Kill den Font
}

CreateGLWindow() und WndProc() bleiben gleich.

Die erste Änderung in WinMain() ist der Titel, der oben im Fenster erscheint. Wir ändern ihn auf "NeHe's Extensions, Scissoring, Token & TGA Loading Tutorial"

int WINAPI WinMain(    HINSTANCE    hInstance,                    // Instanz
            HINSTANCE    hPrevInstance,                    // vorherige Instanz
            LPSTR        lpCmdLine,                    // Kommandozeilen Parameter
            int        nCmdShow)                    // Fenster Anzeige Status
{
    MSG    msg;                                    // Windows Nachrichten Struktur
    BOOL    done=FALSE;                                // Bool Variable um die Schleife zu beenden

    // Frage den Benutzer, in welchen Modus er starten will
    if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;                            // Fenster-Modus

    }

    // erzeuge unser OpenGL Fenster
    if (!CreateGLWindow("NeHe's Token, Extensions, Scissoring & TGA Loading Tutorial",640,480,16,fullscreen))
    {
        return 0;                                // Beende, wenn Fenster nicht erzeugt wurde
    }

    while(!done)                                    // Schleife die so lange läuft, wie done=FALSE
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))                // Wartet eine Nachricht?
        {
            if (msg.message==WM_QUIT)                    // Haben wir eine Nachricht zum beenden erhalten?
            {
                done=TRUE;                        // Wenn ja done=TRUE
            }
            else                                // Wenn nicht, bearbeite die Fenster-Nachrichten
            {
                DispatchMessage(&msg);                    // bearbeite die Nachricht
            }
        }
        else                                    // Wenn da keine Nachrichten sind
        {
            // Zeichne die Szene. Schau nach der ESC-Taste und Beendigungs-Nachrichten von DrawGLScene()
            if ((active && !DrawGLScene()) || keys[VK_ESCAPE])        // Programm aktiv?
            {
                done=TRUE;                        // ESC oder DrawGLScene signalisieren die Beendigung
            }
            else                                // Es ist noch nicht Zeit zum beenden, zeichne Screen neu
            {
                SwapBuffers(hDC);                    // Swap Buffers (Double Buffering)

                if (keys[VK_F1])                    // Wurde F1 gedrückt?
                {
                    keys[VK_F1]=FALSE;                // Wenn ja, setze Taste auf FALSE
                    KillGLWindow();                    // Kill unser aktuelles Fenster
                    fullscreen=!fullscreen;                // Wechsel zwischen Fullscreen und Fester-Modus
                    // Erzeuge unser OpenGL Fenster erneut
                    if (!CreateGLWindow("NeHe's Token, Extensions, Scissoring & TGA Loading Tutorial",640,480,16,fullscreen))
                    {
                        return 0;                // beende, wenn das Fenster nicht erzeugt wurde
                    }
                }

Der folgende Code überprüft, ob die Pfeile nach oben Taste gedrückt wurde und ob scroll größer als 0 ist und wenn dem so ist, wird scroll um 2 dekrementiert. Dadurch wird der Text auf dem Screen nach unten bewegt.

                if (keys[VK_UP] && (scroll>0))                // wurde die Pfeil nach oben Taste gedrück?
                {
                    scroll-=2;                    // wenn ja, dekrementiere 'scroll' und bewege den SCreen nach unten
                }

Wenn die Pfeil nach unten-Taste gedrückt wurde und scroll kleiner als (32*(maxtokens-9)) ist, wird scroll um 2 inkrementiert und der Text auf dem Screen wird nach oben bewegt.

32 ist die Anzahl der Linien, die jeder Buchstabe benötigt. Maxtokens ist die absoulte Anzahl der Extensionen, die Ihre Grafikkarte unterstützt. Wir subtrahieren 9, da 9 Zeilen auf einmal auf dem Screen angezeigt werden können. Wenn wir nicht 9 subtrahieren würden, könnten wir über das Ende der Liste hinaus scrollen, womit die Liste komplett vom Screen verschwinden würden. Versuchen Sie das -9 wegzulassen, wenn Sie sich nicht sicher sind, verstanden zu haben, was ich meine.

                if (keys[VK_DOWN] && (scroll// wurde Pfeil nach Unten Taste gedrückt?
                {
                    scroll+=2;                    // wenn ja, inkrementiere 'scroll' und bewege den Screen nach oben
                }
            }
        }
    }

    // Shutdown
    KillGLWindow();                                    // Kill das Fenster
    return (msg.wParam);                                // Beende das Programm
}

Ich hoffe, dass Sie dieses Tutorial interessant fanden. Am Ende dieses Tutorials sollten Sie wissen, wie man den Herrsteller-Namen und die Versionsnummer Ihrer Grafikkarte ausliest. Sie sollten auch wissen, wie man herausfindet, welche Extensionen unterstützt werden. Sie sollten wissen, was Scissor Testing ist und wie es in Ihren eigenen OpenGL Projekten verwendet werden kann und zu guter Letzt sollten Sie wissen, wie man TGA Bilder anstatt von Bitmaps laden kann, um diese als Texturen zu verwenden.

Wenn Sie Probleme mit diesem Tutorial haben oder irgend etwas nicht verstehen, lassen Sie es mich wissen. Ich möchte die Tutorials so gut wie möglich machen. Ihr Feedback ist wichtig!

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 Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* 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 GLut Code für diese Lektion. ( Conversion by Ashley Harvey )
* DOWNLOAD JoGL Code für diese Lektion. ( Conversion by Abdul Bezrati )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux Code für diese Lektion. ( Conversion by Jay Groven )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by DarkAlloy )
* DOWNLOAD LWJGL Code für diese Lektion. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* 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.