NeHe - Lektion 07 - Texturfilter, Beleuchtung und Tastatureingaben

Lektion 7



In diesem Tutorial bringe ich Ihnen bei, wie Sie drei verschiedene Textur-Filter verwenden. Ich werde Ihnen beibringen, wie man ein Objekt mittels Tasten auf der Tastatur bewegt und ich werde Ihnen auch beibringen, wie Sie eine einfache Beleuchtung in Ihre OpenGL Szene einfügen. Ziemlich viel Stoff in diesem Tutorial, wenn Sie also noch mit den vorherigen Tutorials Probleme haben, gehen Sie zurück und schauen Sie sie nochmal an. Es ist sehr witchtig ein gutes Verständnis der Grundlagen zu haben, bevor man mit dem folgenden Code weitermacht.

Wir werden den Code aus Lektion eins erneut modizifizieren. Wie immer, werde ich bei grundlegenden Änderungen den kompletten Codeabschnitt, der geändert wurde, hinschreiben. Wir fangen damit an, ein paar neue Variablen zum Programm hinzuzufügen.

#include    <windows.h>                            // Header Datei für Windows
#include    <stdio.h>                            // Header Datei für Standard Ein-/Ausgabe ( HINZUGEFÜGT )
#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 standardmäßig auf TRUE gesetzt
bool        fullscreen=TRUE;                        // Fullscreen Flag

Die folgenden Zeilen sind neu. Wir fügen drei boolean Variablen ein. BOOL bedeutet, dass die Variable nur TRUE (Wahr) oder FALSE (Falsch) sein kann. Wir erzeugen eine Variable names light, um zu verfolgen, ob die Beleuchtung an- oder ausgeschaltet ist. Die Variablen lp und fp werden benutzt, um zu sehen, ob die Taste 'L' oder 'F' gedrückt wurde. Ich werde später im Code erklären, warum wir diese Variablen benötigen. Zum jetzigen Zeitpunkt langt es, zu wissen, dass sie wichtig sind.

BOOL    light;                                    // Beleuchtung AN / AUS
BOOL    lp;                                    // L gedrückt?
BOOL    fp;                                    // F gedrückt?

Nun führen wir fünf Variablen ein, die den Winkel auf der X-Achse (xrot), den Winkel auf der Y-Achse (yrot), die Geschwindigkeit mit der die Kiste auf der X-Achse rotiert (xspeed) und die Geschwindigkeit mit der die Kiste auf der Y-Achse rotiert (yspeed) kontrollieren. Wir erzeugen dann noch eine Variable namens z welche kontrolliert, wie tief die Kiste im Screen (auf der Z-Achse) ist.

GLfloat    xrot;                                    // X Rotation
GLfloat    yrot;                                    // Y Rotation
GLfloat xspeed;                                    // X Rotationsgeschwindigkeit
GLfloat yspeed;                                    // Y Rotationsgeschwindigkeit
GLfloat    z=-5.0f;                                // Tiefe in den Screen hinein

Nun erzeugen wir die Arrays, die zur Erzeugung der Beleuchtung verwendet werden. Wir werden zwei verschiedene Arten Licht verwenden. Die erste Art wird ambientes Licht genannt. Ambientes Licht ist Licht, welches nicht aus einer bestimmten Richtung kommt. Alle Objekte in der Szene werden durch das ambiente Licht beleuchtet. Die zweite Art wird diffuses Licht genannt. Diffuses Licht wird durch Ihre Lichtquelle erzeugt und wird von der Oberfläche der Objekte in Ihrer Szene relektiert. Jede Oberfläche eines Objekts, welche direkt von dem Licht beleuchtet wird, wird sehr hell sein und die Flächen, die nicht direkt getroffen werden, dunkler. Das erzeugt einen netten Shading-Effekt an den Seiten unserer Kiste.

Licht wird genauso erzeugt wie Farben erzeugt werden. Wenn die erste Zahl 1.0f ist und die nächste beiden 0.0f, erhalten wir ein helles rotes Licht. Wenn die dritte Zahl 1.0f ist und die ersten beiden 0.0f, erhalten wir ein helles blau. Die letzte Zahl ist der Alpha-Wert. Wir lassen diesen erstmal auf 1.0f.

In der folgenden Zeile speichern wir also die Werte für ein weißes ambientes Licht mit halber Intensität (0.5f). Da alle Zahlen gleich 0.5f sind, bekommen wir einen Wert, der direkt zwischen gar nichts (schwarz) und voller Helligkeit (weiß) liegt. Rot, blau und grün, mit den selben Werten gemischt, erzeugen eine Schattierung von schwarz (0.0f) zu weiß (1.0f). Ohne ein ambientes Licht erscheinen Stellen, wo kein diffuses Licht ist, sehr dunkel.

GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };                 // Ambiente Lichtwere ( NEU )

In der nächsten Zeile speichern wir die Werte für ein super helles, mit voller Intensität, diffuses Licht. Alle Werte sind gleich 1.0f. Das bedeutet, dass das Licht so hell wie möglich ist. Ein diffuses Licht, das so hell ist, beleuchtet die Vorderseite der Kiste, sehr schön.

GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };                 // Diffuse Lichtwerte ( NEU )

Letztendlich speichern wir die Position des Lichts. Die ersten drei Zahlen sind die selben wie die drei glTranslate Zahlen. Die erste Zahl ist zum links und rechts bewegen auf der X-Ebene, die zweite Zahl ist zum hoch und runter bewegen auf der Y-Ebene und die dritte Zahl ist zum nach vorne und hinten bewegen auf der Z-Ebene. Da wir unser Licht direkt auf die Vorderseite der Kiste strahlen lassen wollen, bewegen wir uns weder nach links noch nach rechts, weshalb der erste Wert gleich 0.0f ist (keine Bewegung auf der X-Achse), wir wollen uns auch nicht nach oben und unten bewegen, weshalb auch der zweite Wert gleich 0.0f ist. Für den dritten Wert wollen wir sicher gehen, dass das Licht immer vor der Kiste ist. Deshalb positionieren wir das Licht außerhalb des Bildschirms zum Betrachter hin. Sagen wir, dass das Glas Ihres Monitors bei 0.0f auf der Z-Ebene ist. Wir positionieren unser Licht bei 2.0f auf der Z-Ebene. Wenn Sie das Licht tatsächlich sehen könnten, würde es sich vor Ihrem Monitor befinden. Dadurch kann das Licht nicht hinter der Kiste sein, es sei denn, die Kiste wäre ebenfalls vor dem Glas Ihres Monitors. Selbstverständlich würden Sie die Kiste nicht mehr sehen, wenn die Kiste sich nicht mehr hinter dem Glas Ihres Monitors befinden würde, darum ist es dann auch egal wo das Licht ist. Leuchtet das ein?

Es gibt keinen wirklichen einfachen Weg, um den dritten Parameter zu erklären. Sie sollten wissen, dass -2.0f näher zum Betrachter ist, als -5.0f und -100.0f wäre WEIT weg in den Screen hinein. Wenn Sie erst einmal bei 0.0f sind, wird das Bild so groß, dass es den gesamten Bildschirm ausfüllt. Wenn Sie dann erst einmal in den positiven Bereich kommen, wird das Bild nicht mehr auf dem Bildschirm erscheinen, da es "hinter den Bildschirm" ist. Das meinte ich mit, aus dem Screen hinaus. Das Objekt ist immer noch da, Sie können es nur nicht mehr sehen.

Lassen Sie die letzte Zahl auf 1.0f. Damit teilen Sie OpenGL mit, dass die benannten Koordinaten die Position der Lichtquelle ist. Mehr darüber in einem späteren Tutorial.

GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };                 // Lichtposition ( NEU )

Die filter Variable wird benutzt, um zu verfolgen, welche Textur angezeigt wird. Die erste Textur (Textur 0) wird gl_nearest verwenden (keine Weichzeichnung). Die zweite Textur (Textur 1) benutzt gl_linear Filterung, was das Bild etwas weicher zeichnet. Die dritte Textur (Textur 2) verwendet MipMapped Texturen, was eine sehr gut aussehende Textur erzeugt. Die Variable filter wird gleich 0, 1 oder 2 sein, abhänfig von der Textur, die wir verwenden wollen. Wir fangen mit der ersten Textur an.

GLuint texture[3] erzeugt Speicherplatz für die drei verschiedenen Texturen. Die Texturen werden in texture[0], texture[1] und texture[2] gespeichert.

GLuint    filter;                                    // welcher Filter benutzt werden soll
GLuint    texture[3];                                // Speicherplatz für 3 Texturen

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

Nun laden wir ein Bitmap und erzeugen daraus drei verschiedene Texturen. Dieses Tutorial verwendet die glaux Library, um das Bitmap zu laden, stellen Sie also sicher, dass Sie die glaux Library inkludieren, bevor Sie versuchen, den Code zu compilieren. Ich weiß, dass Delphi und Visual C++ jeweils die glaux Library haben. Ich bin mir nicht sicher, was andere Sprachen angeht. Ich werde nur erklären, was die neuen Codezeilen machen, wenn Sie eine Zeile sehen, die ich nicht kommentiert habe und Sie sich wundern, was diese macht, schauen Sie in Tutorial sechs nach. Es erklärt das Laden und erzeugen von Texturen aus Bitmap-Images im Detail.

Direkt nach dem obigen Code und bevor ReSizeGLScene() wollen wir folgenden Codeabschnitt einfügen. Das ist der selbe Code, den wir in Lektion 6 zum Laden einer Bitmap-Datei verwendet haben. Da hat sich nichts geändert. Wenn Sie bei irgend einer Zeile nicht sicher sind, was diese macht, lesen Sie es in Tutorial sechs nach. Dieses erklärt den folgenden Code im Detail.

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

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

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

    if (File)                                // Existiert die Datei?

    {
        fclose(File);                            // Schließe das Handle
        return auxDIBImageLoad(Filename);                // Lädt das Bitmap und gebe einen Zeiger zurück
    }
    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 drei Texturen. Status wird verwendet, um zu verfolgen ob eine Textur geladen und erzeugt wurde oder nicht.

int LoadGLTextures()                                // Lade Bitmaps und konvertiere in Texturen
{
    int Status=FALSE;                            // Status Indikator

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

    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/Crate.bmp") ruft unseren LoadBMP() Code auf. Die Datei names Crate.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/Crate.bmp"))
    {
        Status=TRUE;                            // Setze Status auf TRUE

Nun, da wir die Bild-Daten nach TextureImage[0] geladen haben, benutzen wir die Daten um 3 Texturen zu erzeugen. Die folgende Zeile teilt OpenGL mit, dass wir drei Texturen erzeugen wollen und wir wollen die Texturen in texture[0], texture[1] und texture[2] speichern.

        glGenTextures(3, &texture[0]);                    // erzeuge 3 Texturen

In Tutorial sechs haben wir linear gefilterte Textur Maps verwendet. Diese benötigen recht viel Rechnerleistung, dafür sehen sie recht gut aus. Die erste Art an Textur, die wir in diesem Tutorial erzeugen werden, benutzt GL_NEAREST. Grundsätzlich hat diese Art an Textur überhaupt keine Filterung. Sie benötigt recht wenig Rechnerleistung und sieht auch ziemlich schlecht aus. Wenn Sie jemals ein Spiel gespielt haben, wo die Texturen alle klobig aussehen, wird dieses wahrscheinliche dies Art Textur verwenden. Der einzige Vorteil dieser Textur-Art ist, dass Projekte, die diese Textur-Art verwenden in der Regel auch auf langsamen Rechnern recht gut laufen.

Sie werden bemerken, dass wir GL_NEAREST sowohl für MIN als auch MAG verwenden. Sie können GL_NEAREST mit GL_LINEAR mischen und die Textur wird etwas besser aussehen, da wir aber in Geschwindigkeit interessiert sind, werden wir für beides eine niedrige Qualität verwenden. MIN_FILTER ist der Filter, der verwendet wird, wenn ein Bild kleiner gezeichnet wird, als die original Textur-Größe. MAG_FILTER wird benutzt, wenn das Bild größer als die original Textur-Größe ist.

        // erzeuge Nearest Filtered Textur
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); // ( NEU )
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // ( NEU )
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Die nächste Textur die wir erzeugen, ist die selbe Art Textur, die wir in Tutorial sechs verwendet haben. Linear gefilterte. Die einzige Sache, die sich geändert hat, ist, dass wir die Textur in texture[1] anstatt in texture[0] speichern, da es unsere zweite Textur ist. Wenn wir sie wie oben in texture[0] speichern, würde es die GL_NEAREST Textur (die erste Textur) überschreiben.

        // erzeuge Linear Filtered Textur
        glBindTexture(GL_TEXTURE_2D, texture[1]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Nun zu einem anderen Weg Texturen zu erzeugen. Mipmapping! Sie werden bemerkt haben, dass wenn Sie ein Bild sehr winzig auf dem Screen machen, dass viele kleine Details verschwinden. Muster die eigentlich recht gut aussehen, fangen an recht schlecht auszusehen. Wenn Sie OpenGL mitteilen, eine mipmapped Textur zu erzeugen, versucht OpenGL verschieden große hochqualitative Texturen zu erzeugen. Wenn Sie eine mipmapped Textur auf den Screen zeichnen, wird OpenGL die am BESTEN aussehendste Textur aus den erzeugten wählen (Textur mit den meisten Details) und sie auf den Screen zeichnen anstatt das Original in der Größe zu verändern (was ein Detailverlust nach sich zieht).

Ich habe in Tutorial sechs gesagt, dass es einen Weg um die 64,128,256,etc. Grenze gibt, die OpenGL Textur-Breite und Höhe auferlegt. Und zwar mit gluBuild2DMipmaps. Von dem was ich gefunden habe, können Sie jedes Bitmap-Image verwenden, das sie wollen (jede Breite und Höhe), wenn Sie mipmapped Texturen erzeugen. OpenGL wird diese automatisch der entsprechenden Breite und Höhe anpassen.

Da dies Textur Nummer drei ist, werden wir diese Textur in texture[2] speichern. Nun haben wir texture[0], welche keine Filterung hat, texture[1] welche lineare Filterung verwendet und texture[2] welche mipmapped Texturen verwendet. Damit haben wir die Texturen für dieses Tutorial erzeugt.

        // erzeuge MipMapped Textur
        glBindTexture(GL_TEXTURE_2D, texture[2]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); // ( NEU )

Die folgende Zeile erzeugt die Mipmapped Textur. Wir erzeugen eine 2D Textur, indem wir 3 Farben (rot, grün, blau) verwenden. TextureImage[0]->sizeX ist die Bitmap-Breite, TextureImage[0]->sizeY ist die Bitmap-Höhe, GL_RGB bedeutet, dass wir Rot, Grün, Blau Farben, in dieser Reihenfolge, verwenden. GL_UNSIGNED_BYTE bedeutet, dass die Daten, aus denen die Textur besteht, aus Bytes besteht und TextureImage[0]->data zeigt auf die Bitmap-Daten, aus denen wir die Textur erzeugen.

        gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); // ( NEU )
    }

Nun geben wir jeglichen Speicher frei, den wir zum Speichern der Bitmap-Daten verwendet haben. Wir überprüfen, ob die Bitmap-Daten in TextureImage[0] gespeichert wurden. Wenn ja, überprüfen wir, ob Daten gespeichert wurden. Wenn Daten gespeichert wurden, löschen wir diese. Dann geben wir die Image-Struktur frei, um sicherzustellen, dass jeder benutzte 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
    }

Letztendlich geben wir status zurück. Wenn alles OK verlief, wird die Variable Status gleich TRUE sein. Wenn etwas schief ging, wird Status gleich FALSE sein.

    return Status;                                // gebe Status zurück
}

Nun laden wir die Texturen und initialisieren die OpenGL Eigenschaften. Die erste Zeile aus InitGL lädt die Texturen, indem der obige Code verwendet wird. Nachdem die Texturen erzeugt wurden, aktivieren wir 2D Textur-Mapping mittels glEnable(GL_TEXTURE_2D). Der Schattierungs-Modus wird auf Smooth-Shading gesetzt. Die Hintergrundfarbe wird auf schwarz gesetzt, wir aktivieren Depth-Testing und dann aktivieren wir hübsche Perspektiven Berechnungen.

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

    glEnable(GL_TEXTURE_2D);                        // aktiviere Textur Mapping
    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

Nun erzeugen wir die Beleuchtung. Die folgende Zeile wird die Menge an ambienten Licht bestimmen, die light1 von sich geben wird. Am Anfang dieses Tutorials haben wir die Menge des ambienten Lichts in LightAmbient gespeichert. Es werden die Werte benutzt, die wir im Array gespeichert haben (halb intensives ambientes Licht).

    glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);                // initialisiere das ambiente Licht

Als nächstes setzen wir die Menge an diffusen Licht, was Licht Nummer eins von sich geben wird. Wir haben die Menge an diffusen Licht in LightDiffuse gespeichert. Es werden die Werte benutzt, die wir in dem Array gespeichert haben (volle Intensität weißes Licht).

    glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);                // initialisiere das diffuse Licht

Nun setzen wir die Position des Lichts. Wir haben die Position in LightPosition gespeichert. Es werden die Werte aus dem Array benutzt, die wir gespeichert haben (rechts im Zentrum der Vorderseite, 0.0f auf X, 0.0f auf Y und 2 Einheiten zum Betrachter hin {aus dem Screen heraus} auf der Z-Ebene).

    glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);            // Position des Lichts

Letztendlich aktivieren wir Licht Nummer eins. Wir haben allerdings noch nicht GL_LIGHTING aktiviert, weshalb Sie noch keine Beleuchtung sehen werden. Das Licht wird erzeugt und positioniert und sogar aktiviert, aber solange GL_LIGHTNING nicht aktivieren, wird das Licht nicht funktionieren.

    glEnable(GL_LIGHT1);                            // aktiviere Licht  eins
    return TRUE;                                // Initialisierung war OK
}

Im nächsten Codeabschnitt werden wir den texturierten Würfel zeichnen. Ich werde einige der Zeilen kommentieren, weil diese neu sind. Wenn Sie nicht sicher sind, was die unkommentierten Zeilen bedeuten, schauen Sie in Tutorial sechs nach.

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();                            // Resettet die View

Die nächsten drei Codezeilen positionieren und rotiren den texturierten Würfel. glTranslatef(0.0f,0.0f,z) bewegt den Würfel um den Wert z auf der Z-Ebene (vom Betrachter weg und zu ihm hin). glRotatef(xrot,1.0f,0.0f,0.0f) benutzt die Variable xrot, um den Würfel auf der X-Achse zu rotieren. glRotatef(yrot,0.0f,1.0f,0.0f) benutzt die Variable yrot, um den Würfel auf der Y-Achse zu rotieren.

    glTranslatef(0.0f,0.0f,z);                        // Translatiere in den Screen hinein/heraus um z

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

Die nächste Zeile ist ähnlich der Zeile, die wir in Tutorial sechs verwendet haben, aber statt texture[0] zu binden, binden wir texture[filter]. Jedes Mal wenn wir die 'F'-Taste drücken, wird der Wert in filter erhöht. Wenn der Wert größer als zwei ist, wird die Variable filter zurück auf null gesetzt. Wenn das Programm startet, ist der filter auf null gesetzt. Das ist das selbe wie glBindTexture(GL_TEXTURE_2D, texture[0]). Wenn wir 'F' ein weiteres Mal drücken, ist die Variable filter gleich eins, was das Selbe wie glBindTexture(GL_TEXTURE_2D, texture[1]) ist. Indem wir die Variable filter verwenden, können wir jede der drei Texturen auswählen, die wir erzeugt haben.

    glBindTexture(GL_TEXTURE_2D, texture[filter]);                // wähle eine Textur basierend auf dem Filter aus

    glBegin(GL_QUADS);                            // fange an Quads zu zeichnen

glNormal3f ist neu in meinen Tutorials. Ein Normalenvektor (engl.: normal) ist eine Linie die senkrecht (im 90 Grad Winkel) direkt aus der Mitte eines Polygons herauszeigt. Wenn Sie Beleuchtung benutzen, müssen Sie einen Normalenvektor spezifizieren. Der Normalenvektor teilt OpenGL mit, in welche Richtung das Polygon zeigt... was nach oben zeigt. Wenn Sie die Normalenvektoren nicht angeben, passieren die komischtens Sachen. Seiten, die nicht beleuchtet werden sollen, werden beleuchtet, die falsche Seite eines Polygons wird beleuchtet, etc. Der Normalenvektor sollte aus dem Polygon herauszeigen.

Wenn Sie auf die Vorderseite schauen, werden Sie bemerken, dass der Normalenvektor auf der positiven Z-Achse liegt. Das bedeutet, dass der Normalenvektor zum Betrachter zeigt. Exakt die Richtung, in die er zeigen soll. Auf der Hinterseite zeigt der Normalenvektor vom Betrachter weg, in den Screen hinein. Wieder genau das was wir wollen. Wenn der Würfel entweder auf der X oder Y-Achse um 180 Grad gedreht wurde, zeigt die Vorderseit in den Screen hinein und die Hinterseite zeigt zum Benutzer. Egal welche Seite zum Betrachter zeigt, der Normalenvektor der entsprechenden Seite wird ebenfalls zum Betrachter zeigen. Da sich das Licht beim Betrachter befindet, wird jedes mal, wenn der Normalenvektor zum Betrachter zeigt, ebenfalls zum Licht zeigen. Wenn das passiert, wird die Seite ausgeleuchtet. Je mehr ein Normalenvektor zum Licht zeigt, umso heller wird die Seite. Wenn Sie sich ins Zentrum des Würfels bewegen, werden Sie bemerken, dass es dunkel ist. Die Normalenvektoren zeigen hinaus, nicht hinein, weshalb es kein Licht innerhalb der Kiste gibt, genauso wie es sein soll.

        // Vorderseite
        glNormal3f( 0.0f, 0.0f, 1.0f);                    // Normalenvektor zeigt in Richtung Betrachter
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);    // Punkt 1 (vorne)
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);    // Punkt 2 (vorne)
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);    // Punkt 3 (vorne)
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);    // Punkt 4 (vorne)
        // Hinterseite
        glNormal3f( 0.0f, 0.0f,-1.0f);                    // Normalenvektor zeigt vom Betrachter weg
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);    // Punkt 1 (hinten)
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);    // Punkt 2 (hinten)
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);    // Punkt 3 (hinten)
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);    // Punkt 4 (hinten)
        // obere Seite
        glNormal3f( 0.0f, 1.0f, 0.0f);                    // Normalenvektor zeigt nach oben
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);    // Punkt 1 (oben)
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);    // Punkt 2 (oben)
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);    // Punkt 3 (oben)
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);    // Punkt 4 (oben)
        // untere Seite
        glNormal3f( 0.0f,-1.0f, 0.0f);                    // Normalenvektor zeigt nach unten
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);    // Punkt 1 (unten)
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);    // Punkt 2 (unten)
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);    // Punkt 3 (unten)
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);    // Punkt 4 (unten)
        // rechte Seite
        glNormal3f( 1.0f, 0.0f, 0.0f);                    // Normalenvektor zeigt nach rechts
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);    // Punkt 1 (rechts)
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);    // Punkt 2 (rechts)
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);    // Punkt 3 (rechts)
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);    // Punkt 4 (rechts)
        // linke Seite
        glNormal3f(-1.0f, 0.0f, 0.0f);                    // Normalenvektor zeigt nach links
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);    // Punkt 1 (links)
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);    // Punkt 2 (links)
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);    // Punkt 3 (links)
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);    // Punkt 4 (links)
    glEnd();                                // fertig mit Zeichnen der Quads

Die nächsten beiden Zeilen inkrementieren xrot und yrot um den Wert, der in xspeed und yspeed gespeichert ist. Wenn der Wert in xspeed oder yspeed hoch ist, werden xrot und yrot sehr schnell inkrementiert. Je schneller xrot oder yrot inkrementiert werden, um so schneller dreht sich der Würfel auf der entsprechenden Achse.

    xrot+=xspeed;                                // Addiere xspeed zu xrot
    yrot+=yspeed;                                // Addiere yspeed zu yrot
    return TRUE;                                // weiter geht's
}

Nun gehen wir runter zur WinMain(). Wir fügen den Code zum ein- und ausschalten der Beleuchtung ein, zum rotieren der Kiste, zum ändern des Filters und um die Kiste in den Screen hinein und hinaus zu bewegen. Zum Ende der WinMain() werden Sie den Befehl SwapBuffers(hDC) finden. Direkt hinter dieser Zeile fügen Sie den folgenden Code ein.

Dieser Code überprüft, ob der Buchstabe 'L' auf der Tastatur gedrückt wurde. Die erste Zeile überprüft, ob 'L' gedrückt wurde. Wenn 'L' gedrückt wurde, aber lp nicht False ist, was bedeutet, dass 'L' bereits einmal gedrückt wurde oder die Taste festgehalten wird, wird nichts passieren.

                SwapBuffers(hDC);                // Swap Buffers (Double Buffering)
                if (keys['L'] && !lp)                // L wurde gedrückt und nicht festgehalten?
                {

Wenn lp auf False gesetzt war, was bedeutet, dass die Taste 'L' noch nicht gedrückt wurde oder sie losgelassen wurde, wird lp auf True gesetzt. Das zwingt den Benutzer, die 'L'-Taste loszulassen, bevor der Code erneut ausgeführt werden kann. Wenn wir nicht überprüfen, ob die Taste gehalten wird, würde die Beleuchtung ständig an- und ausgehen, da das Programm denken würde, dass Sie die 'L'-Taste immer wieder drücken würden, wenn man diesen Codeabschnitt erreicht.

Wenn lp einmal auf True gesetzt wurde, wird dem Computer somit mitgeteilt, dass 'L' gedrückt wird und wir die Beleuchtung an, bzw. aus schalten. Die Variable light kann nur auf true oder false gesetzt werden. Wenn wir also light=!light sagen, sagen wir also light ist NICHT gleich light. Was auf deutsch bedeutet, dass wenn light gleich true ist, dass wir es ungleich true machen (auf false setzen) und wenn light gleich false ist, wir light ungleich false machen (auf true setzen). Wenn light also true war, setzen wir es auf false und wenn light false war, setzen wir es auf true.

                    lp=TRUE;                // lp wird TRUE
                    light=!light;                // wechsel Light zuTRUE/FALSE

Nun überprüfen wir, was mit der Beleuchtung geschehen soll. Die erste Zeile heißt auf gut deutsch: wenn light gleich false ist. Wenn Sie also alles zusammenfügen, machen die Zeilen folgendes: Wenn light gleich false ist, deaktiviere Beleuchtung. Das schaltet die Beleuchtung aus. Der Befehl else bedeutet: wenn es nicht gleich false war. Wenn light also nicht gleich false war, muss es gleich true sein, weshalb wir die Beleuchtung anschalten.

                    if (!light)                // wenn keine Beleuchtung
                    {
                        glDisable(GL_LIGHTING);        // deaktiviere Beleuchtung
                    }
                    else                    // ansonsten
                    {
                        glEnable(GL_LIGHTING);        // aktiviere Beleuchtung
                    }
                }

Die folgende Zeile überprüft, ob wir aufgehört haben, die 'L'-Taste zu drücken. Falls ja, setzen wir die Variable lp auf False, was bedeutet, dass die 'L'-Taste nicht gedrückt wird. Wenn wir nicht überprüfen, ob die Taste losgelassen wurde, sind wir nur einmal in der Lage die Beleuchtung anzuschalten, da der Computer denkt, dass 'L' gedrückt ist und uns sie nicht mehr ausschalten lässt.

                if (!keys['L'])                    // wurde die L Taste losgelassen?
                {
                    lp=FALSE;                // wenn ja, wird lp gleich FALSE
                }

Nun machen wir etwas ähnliches mit der 'F'-Taste. Wenn die Taste gedrückt ist und sie nicht festgehalten wird oder sie niemals zuvor gedrückt wurde, wird die Variable fp auf True gesetzt, was bedeutet, dass die Taste nun gedrückt wird. Dann wird die Variable namens filter inkrementiert. Wenn filter größer als 2 ist (was texture[3] sein würde und diese Textur existiert nicht), setzen wir die Variable filter zurück auf null.

                if (keys['F'] && !fp)                // wurde die F Taste gedrückt?
                {
                    fp=TRUE;                // fp wird TRUE
                    filter+=1;                // filter Wert um eins erhöhen
                    if (filter>2)                // Ist Wert größer als 2?
                    {
                        filter=0;            // Wenn ja, setze filter auf 0
                    }
                }
                if (!keys['F'])                    // wurde die F Taste losgelassen?
                {
                    fp=FALSE;                // wenn ja, wird fp gleich FALSE
                }

Die nächsten vier Zeilen überprüfen, ob wir die 'Seite hoch' Taste drücken. Wenn ja, wird die Variable z dekrementiert. Wenn diese Variable dekrementiert wird, wird sich der Würfel weiter weg, in die Ferne bewegen, wegen des glTranslatef(0.0f,0.0f,z) Befehls, der in der DrawGLScene Prozedure verwendet wird.

                if (keys[VK_PRIOR])                // Wurde die Seite nach oben Taste gedrückt?
                {
                    z-=0.02f;                // wenn ja, bewege in den Screen hinein
                }

Diese vier Zeilen überprüfen, ob die 'Seite runter' Taste gedrückt wurde. Wenn ja, wird die Variable z inkrementiert und der Würfel bewegt sich in Richtung des Betrachters, wegen des glTranslatef(0.0f,0.0f,z) Befehls, der in der DrawGLScene Prozedur verwendet wird.

                if (keys[VK_NEXT])                // Wurde die Seite nach unten Taste gedrückt?
                {
                    z+=0.02f;                // wenn ja, bewege in Richtung Betrachter
                }

Nun müssen wir alle Pfeil-Tasten überprüfen. Indem links oder rechts gedrückt wird, wird xspeed inkrementiert oder dekrementiert. Indem hoch oder runter gedrückt wird, wird yspeed inkrementiert oder dekrementiert. Erinnern Sie sich daran, dass ich vorher in diesem Tutorial gesagt habe, dass, je höher der Wert in xspeed oder yspeed ist, desto schneller rotiert der Würfel. Je länger Sie eine Pfeil-Taste drücken, desto schneller wird der Würfel in diese Richtung rotieren.

                if (keys[VK_UP])                // Wurde die Pfeil nach oben Taste gedrückt?
                {
                    xspeed-=0.01f;                // wenn ja, dekrementierexspeed
                }
                if (keys[VK_DOWN])                // Wurde die Pfeil nach unten Taste gedrückt?
                {
                    xspeed+=0.01f;                // wenn ja, inkrementierexspeed
                }
                if (keys[VK_RIGHT])                // wurde die rechte Pfeiltaste gedrückt?
                {
                    yspeed+=0.01f;                // wenn ja, inkrementiere yspeed
                }
                if (keys[VK_LEFT])                // wurde die linke Pfeiltaste gedrückt?
                {
                    yspeed-=0.01f;                // wenn ja, inkrementiereyspeed
                }

Wie in allen vorangegangen Tutorials, stellen wir sicher, dass der Titel des Fensters korrekt ist.

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

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

Nach diesem Tutorial sollten Sie in der Lage sein, hochqualitative, realistisch aussehende texturierte Objekte aus Quadraten zu erzeugen und mit ihnen zu interagieren. Sie sollten die Vorteile der drei Filter, die in diesem Tutorial verwendet wurden, verstanden haben. Indem Sie die bestimmten Tasten auf der Tastatur drücken, sollten Sie mit dem/n Objekt(en) auf dem Screen interagieren können und Sie sollten wissen, wie man einfache Beleuchtung in einer Szene einsetzt, um diese realistischer wirken zu lassen.

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 Brian Holley )
* 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 Andy Restad )
* 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 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 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.