NeHe - Lektion 26 - Clipping, Reflektionen, Stencil

Lektion 26



Willkommen zu einem weiteren aufregendem Tutorial. Der Code für dieses Tutorial wurde von Banu Octavian geschrieben. Das Tutorial wurden natürlich von mir (NeHe) geschrieben. In diesem Tutorial werden Sie lernen wie Sie eine EXTREM realistische Reflexion erstellen. Nichts gefaktes hier! Die Objekte die reflektiert werden, werden nicht unter dem Boden erscheinen oder auf der anderen Seite der Wand. Wirkliche Reflektion!

Eine sehr wichtige Sache, die über dieses Tutorial anzumerken ist: Da die Voodoo 1, 2 und einige andere Karten den Stencil Buffer nicht unterstützen, wird dieses Demo NICHT auf diesen Karten laufen. Es wird NUR auf Karten laufen, die den Stencil Buffer unterstützen. Wenn Sie sich nicht sicher sind, ob Ihre Karte den Stencil Buffer unterstützt, laden Sie den Code runter und versuchen Sie das Demo laufen zu lassen. Außerdem benötigt dieses Demo einen ziemlich guten Prozessor und eine gute Grafikkarte. Selbst auf meiner GeForce 1 habe ich manchmal eine Verlangsamung bemerkt. Dieses Demo läuft am besten im 32 Bit Farb-Modus!

Da Grafikkarten besser werden und Prozessoren schneller, kann ich bereits sehen, dass der Stencil Buffer populärer wird. Wenn Sie die entsprechende Hardware haben und Sie bereit zum reflektieren sind, lesen Sie weiter!

Der erste Teil des Codes ist ziemlicher Standard. Wir inkludieren alle benötigten Header-Dateien und setzen unseren Device Context, Rendering Context, etc.

#include    <windows.h>                            // Header Datei für Windows
#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
#include    <stdio.h>                            // Header Datei für Standard Input / Output

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

Als nächstes haben wir die Standard-Variablen, um die Tastaturdrücke zu verfolgen (keys[ ]), ob das Programm aktiv ist oder nicht (active) und ob wir den Fullscreen-Modus benutzen sollen oder den Fenster-Modus (fullscreen).

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 nächstes definieren wir unsere Beleuchtungs-Variablen. LightAmb[ ] wird unser ambientes Licht setzen. Wir werden 70% rot, 70% grün und 70% blau verwenden, um ein 70% helles weiß zu erzeugen. LightDif[ ] wird unsere diffuse Beleuchtung setzen (die Menge des Lichts das gerade von der Oberfläche unseres Objektes reflektiert). In diesem Fall wollen wir Licht mit voller Intensität reflektieren. Als letztes haben wir LightPos[ ], was wir zur Positionierung unseres Lichtes verwenden werden. In diesem Fall wollen wir das Licht 4 Einheiten nach rechts, 4 Einheiten nach oben und 6 Einheiten zum Betrachter hin. Wenn wir das Licht tatsächlich sehen könnten, würde es vor der oberen rechte Ecke unseres Screens schweben.

// Light Parameters
static GLfloat    LightAmb[] = {0.7f, 0.7f, 0.7f, 1.0f};                // Ambientes Licht
static GLfloat    LightDif[] = {1.0f, 1.0f, 1.0f, 1.0f};                // Diffuses Licht
static GLfloat    LightPos[] = {4.0f, 4.0f, 6.0f, 1.0f};                // Licht Position

Wir führen eine Variable namens q für unser quadratisches Objekt ein und xrot und yrot für die Rotation. xrotspeed und yrotspeed kontrollieren die Geschwindigkeit, mit der unsere Objekte rotieren. zoom wird zum rein und rauszoomen der Szene benutzt (wir beginnen bei -7, was uns die gesamte Szene zeigt) und height ist die Höhe des Balls über dem Boden.

Dann haben wir mit texture[3] noch Speicherplatz für unsere 3 Texturen und definieren WndProc().

GLUquadricObj    *q;                                // Quadrat um eine Sphere zu zeichnen For Drawing A Sphere

GLfloat        xrot        =  0.0f;                    // X Rotation
GLfloat        yrot        =  0.0f;                    // Y Rotation
GLfloat        xrotspeed    =  0.0f;                    // X Rotationsgeschwindigkeit
GLfloat        yrotspeed    =  0.0f;                    // Y Rotationsgeschwindigkeit
GLfloat        zoom        = -7.0f;                    // Tiefe in den Screen hinein
GLfloat        height        =  2.0f;                    // Höhe des Balls vom Boden aus

GLuint        texture[3];                            // 3 Texturen

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);                // Declaration For WndProc

Der ReSizeGLScene() und LoadBMP() Code hat sich nicht geändert, weshalb ich beide Codeabschnitte überspringen werde.

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)                // verändert die Größe und initialisiert das GL-Fenster

AUX_RGBImageRec *LoadBMP(char *Filename)                    // Lädt ein Bitmap Image

Der Textur-Lade-Code ist ziemlicher Standard. Sie haben ihn viele Male in den vorherigen Tutorials verwendet. Wir schaffen Platz für 3 Texturen, dann laden wir drei Images und erzeugen linear gefilterte Texturen aus den Image-Daten. Die Bitmap-Dateien, die wir benutzen, liegen im DATA Verzeichnis.

int LoadGLTextures()                                // Lade Bitmaps und konvertiere in Texturen
{
    int Status=FALSE;                                // Status Indikator
    AUX_RGBImageRec *TextureImage[3];                        // erzeuge Speicherplatz für die Texturen
    memset(TextureImage,0,sizeof(void *)*3);                    // Setze den Zeiger auf NULL
    if ((TextureImage[0]=LoadBMP("Data/EnvWall.bmp")) &&            // Lade die Boden-Textur
        (TextureImage[1]=LoadBMP("Data/Ball.bmp")) &&                // Lade die Licht-Textur
        (TextureImage[2]=LoadBMP("Data/EnvRoll.bmp")))                // Lade die Wand-Textur
    {
        Status=TRUE;                            // Setze Status auf TRUE

        glGenTextures(3, &texture[0]);                    // erzeuge die Textur
        for (int loop=0; loop// durchlaufe die 5 Texturen
        {
            glBindTexture(GL_TEXTURE_2D, texture[loop]);
            glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        }
        for (loop=0; loop// durchlaufe die 5 Texturen
        {
            if (TextureImage[loop])                    // Wenn Textur existiert
            {
                if (TextureImage[loop]->data)            // Wenn Textur-Image existiert
                {
                    free(TextureImage[loop]->data);        // gebe Textur-Image Speicher frei
                }
                free(TextureImage[loop]);            // gebe die Image-Struktur frei
            }
        }
    }
    return Status;                                // gebe Status zurück
}

Ein neuer Befehl namens glClearStencil wird im Init-Code eingeführt. Indem 0 als Parameter übergeben wird, teilen wir OpenGL mit, dass das Löschen des Stencil Buffer deaktiviert wird. Sie sollten mit dem Rest des Codes vertraut sein. Wir laden unsere Texturen und aktivieren Smooth Shading. Die Löschfarbe wird auf ein blau gesetzt und die Löschtiefe wird auf 1.0f gesetzt. Der Stencil Löschwert wird auf 0 gesetzt. Wir aktivieren Depth Testing und setzen den Depth Test Wert auf 'weniger als' oder 'gleich'. Unsere Perspektiven Korrektur wird auf 'nicest' gesetzt (sehr gute Qualität) und 2D Texturmapping wird aktiviert.

int InitGL(GLvoid)                                // Der ganze Setup Kram für OpenGL kommt hier rein
{
    if (!LoadGLTextures())                            // wenn Textur nicht geladen wurde,
    {
        return FALSE;                            // gebe FALSE zurück
    }
    glShadeModel(GL_SMOOTH);                        // aktiviere Smooth Shading
    glClearColor(0.2f, 0.5f, 1.0f, 1.0f);                    // Hintergrund
    glClearDepth(1.0f);                            // Depth Buffer Setup
    glClearStencil(0);                            // lösche den Stencil Buffer auf 0
    glEnable(GL_DEPTH_TEST);                        // aktiviere Depth Testing
    glDepthFunc(GL_LEQUAL);                            // Die Art des auszuführenden Depth Test
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // wirklich nette Perspektiven Berechnungen
    glEnable(GL_TEXTURE_2D);                        // aktiviere 2D Textur-Mapping

Nun ist es an der Zeit Light 0 zu initialisieren. Die erste folgende Zeile teilt OpenGL mit, die Werte, die in LightAmb gespeichert sind, für's ambiente Licht zu verwenden. Wenn Sie sich an den Anfang des Codes erinnern, da wurden die rgb Werte für LightAmb alle auf 0.7f gesetzt, was uns ein weißes Licht mit 70% Intensität gibt. Wir setzen dann die diffuse Beleuchtung, indem wir die Werte, die in LightDif gespeichert sind, verwenden und positionieren das Licht an der Position x,y,z, dessen Werte in LightPos gespeichert sind.

Nachdem wir das Licht initialisiert haben, können wir dieses mit glEnable(GL_LIGHT0) aktivieren. Doch selbst wenn wir das Licht aktiviert haben, werden Sie es nicht sehen, bis wir die Beleuchtung mit der letzten Codezeile aktiviert haben.

Anmerkung: Wenn wir alle Lichter in der Szene ausschalten wollten, würden wir glDisable(GL_LIGHTNING) verwenden. Wenn wir nur eins unserer Lichter deaktivieren wollen, würden wir glDisable(GL_LIGHT{0-7}) verwenden. Das gibt uns recht viel Kontrolle über unsere Beleuchtung und welche Licht an und aus sind. Denken Sie nur daran, dass wenn GL_LIGHTING deaktiviert ist, dass Sie keine Lichter sehen werden!

    glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);                // Setze die ambiente Beleuchtung für Light0
    glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDif);                // Setze die diffuse Beleuchtung für Light0
    glLightfv(GL_LIGHT0, GL_POSITION, LightPos);                // Setze die Position für Light0

    glEnable(GL_LIGHT0);                            // aktiviere Light 0
    glEnable(GL_LIGHTING);                            // aktiviere Beleuchtung

In der ersten folgenden Zeile erzeugen wir ein neues Quadratic Objekt. Die zweite Zeile teilt OpenGL mit, weiche Normalenvektoren für unser Quadratic Objekt zu erzeugen und die dritte Zeile teilt OpenGL mit, dass es die Texturkoordinaten für unser Quadratic generieren soll. Ohne die zweite und dritte Codezeile würde unser Objekt Flad Shading verwenden und wir wären nicht in der Lage es zu texturieren.

Die fünfte und sechste Zeile teilt OpenGL mit, dass der Sphere Mapping Algorithmus zum erzeugen der Texturkoordinaten verwendet werden soll. Dies erlaubt uns, das Quadratic Objekt zu 'Sphere mappen'.

    q = gluNewQuadric();                            // erzeuge ein neuesQuadratic
    gluQuadricNormals(q, GL_SMOOTH);                    // erzeuge weiche Normalenvektoren für den Quad
    gluQuadricTexture(q, GL_TRUE);                        // aktiviere Texturkoordinaten für den Quad

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);            // Initialisiere Sphere Mapping
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);            // Initialisiere Sphere Mapping

    return TRUE;                                // Initialisierung verlief OK
}

Der folgende Code wird unser Objekt zeichnen (was ein cool aussehender 'environment mapped' (Anm. des Übersetzers: die Umgebung wird auf das Objekt gemappt) Strandball ist).

Wir setzen die Farbe auf ein weiß mit voller Intensität und binden unsere BALL Textur (die Ball Textur besteht aus eine Reihe von roten, weißen und Blauen Streifen).

Nachdem unsere Textur selektiert wurde, zeichnen wir eine Quadratic Sphere mit einem Radius von 0.35f, 32 Scheiben und 16 'Stapeln' (hoch und runter).

void DrawObject()                                // zeichne unseren Ball
{
    glColor3f(1.0f, 1.0f, 1.0f);                        // Setze Farbe auf weiß
    glBindTexture(GL_TEXTURE_2D, texture[1]);                // wähle Textur 2 (1) aus
    gluSphere(q, 0.35f, 32, 16);                        // zeichne erste Sphere

Nachdem die erste Sphere gezeichnet wurde, wählen wir eine neue Textur (EnvRoll), setzen den Alphawert auf 50% und aktivieren Blending, basierend auf dem Quell-Alphawert. glEnable(GL_TEXTURE_GEN_S) und glEnable(GL_TEXTURE_GEN_T) aktivieren Sphere Mapping.

Nachdem all das getan ist, zeichnen wir die Sphere erneut, deaktivieren Sphere Mapping und deaktivieren Blending.

Das finale Ergebniss ist eine Reflektion, die fast so aussieht, als ob helle Lichtpunkte auf den Strand-Ball gemappt werden. Da wir Sphere Mapping aktiviert haben, ist die Textur immer zum Betrachter hin ausgerichtet, selbst wenn der Ball rotiert. Wir blenden, so dass die neue Textur die alte Textur nicht überdeckt (eine Art der Multitexturierung).

    glBindTexture(GL_TEXTURE_2D, texture[2]);                // wähle Textur 3 (2) aus
    glColor4f(1.0f, 1.0f, 1.0f, 0.4f);                    // Setze Farbe auf weiß mit 40% Alpha
    glEnable(GL_BLEND);                            // aktiviere Blending
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);                    // Setze Blending Modus auf Mischung basierend auf SRC Alpha
    glEnable(GL_TEXTURE_GEN_S);                        // aktiviere Sphere Mapping
    glEnable(GL_TEXTURE_GEN_T);                        // aktiviere Sphere Mapping

    gluSphere(q, 0.35f, 32, 16);                        // Zeichne eine weitere Sphere mit einer neuen Textur
                                        // Texturen werden gemischt, was einen MultiTexture Effekt (Reflektion) erzeugt
    glDisable(GL_TEXTURE_GEN_S);                        // deaktiviere Sphere Mapping
    glDisable(GL_TEXTURE_GEN_T);                        // deaktiviere Sphere Mapping
    glDisable(GL_BLEND);                            // deaktiviere Blending
}

Der folgende Code zeichnet den Boden, über dem unser Ball schwebt. Wir wählen die Boden-Textur (EnvWall) aus und zeichnen einen einzelnen texturierten Quad auf der Z-Achse. Ziemlich einfach!

void DrawFloor()                                // zeichne den Boden
{
    glBindTexture(GL_TEXTURE_2D, texture[0]);                // wähle Textur 1 (0)
    glBegin(GL_QUADS);                            // fange an ein Quad zu zeichnen
        glNormal3f(0.0, 1.0, 0.0);                    // Normalenvektor zeigt nach oben
        glTexCoord2f(0.0f, 1.0f);                    // unten links der Textur
        glVertex3f(-2.0, 0.0, 2.0);                    // untere linke Ecke des Bodens

        glTexCoord2f(0.0f, 0.0f);                    // oben links der Textur
        glVertex3f(-2.0, 0.0,-2.0);                    // obere linke Ecke des Bodens

        glTexCoord2f(1.0f, 0.0f);                    // oben rechts der Textur
        glVertex3f( 2.0, 0.0,-2.0);                    // obere rechte Ecke des Bodens

        glTexCoord2f(1.0f, 1.0f);                    // unten rechts der Textur
        glVertex3f( 2.0, 0.0, 2.0);                    // untere rechte Ecke des Bodens
    glEnd();                                // fertig mit dem Zeichnen des Quad
}

Nun zum spaßigen Teil. Hier kombinieren wir alle Objekte und Images, um unsere Reflektions-Szene zu erzeugen.

Wir fangen mit der Löschung des Screens (GL_COLOR_BUFFER_BIT) auf unsere Standard-Lösch-Farbe (blau) an. Die Depth (GL_DEPTH_BUFFER_BIT) und Stencil (GL_STENCIL_BUFFER_BIT) Buffers werden ebenfalls gelöscht. Stellen Sie sicher, dass Sie den Stencil Buffer Code eingefügt haben, welcher neu und einfach zu überschauen ist! Es ist wichtig anzumerken, dass wir diesen, wenn wir ihn löschen, mit 0'len füllen.

Nachdem der Screen und die Buffer gelöscht wurden, definieren wir unsere Clipping-Ebenen-Gleichung. Die Ebenen-Gleichung wird zum clippen des reflektierten Bildes verwendet.

Die Gleichung eqr[]={0.0f,-1.0f, 0.0f, 0.0f} wird verwendet, wenn wir das reflektierte Bild zeichnen. Wie Sie sehen können, ist der Wert für die Y-Ebene ein negativer Wert. Das bedeutet, dass wir nur dann Pixel sehen werden, wenn diese unter dem Boden gezeichnet werden oder an einem negativen Wert auf der Y-Achse. Alles was über dem Boden gezeichnet wird, wird nicht zu sehen sein, solange wir diese Gleichung verwenden.

Mehr über Clipping später... lesen Sie weiter.

int DrawGLScene(GLvoid)                                // zeichne alles
{
    // lösche Screen, Depth Buffer & Stencil Buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Clip Ebenen Gleichung
    double eqr[] = {0.0f,-1.0f, 0.0f, 0.0f};                // Ebenen Gleichung, die für die reflektierten Objekte benutzt wird

So, wir haben den Screen gelöscht und unsere Clipping-Ebenen definiert. Nun zum spaßigen Teil!

Wir fangen mit dem resetten der Modelview Matrix an. Dadurch werden natürlich alle Zeichen-Vorgänge im Zentrum des Screens gestartet. Wir translatieren dann 0.6f Einheiten runter (um eine kleine perspektivische Neigung zum Boden hinzuzufügen) und, basierend auf dem Wert von zoom, in den Screen hinein. Um besser erklären zu können, warum wir 0.6f Einheiten herunter translatieren, werde ich ein kleines Beispiel zum erklären verwenden. Wenn Sie sich ein Blatt Papier von der Seite, direkt auf Augenhöhe anschauen, könnten Sie es kaum sehen. Es wäre eher, als wenn Sie eine dünne Linie vor sich hätten. Wenn Sie das Papier etwas herunter bewegen, wäre es nicht länger eine Linie. Sie würden mehr von dem Papier sehen, da Ihre Augen auf das Papier herunterschauen würden, anstatt direkt auf die Kante des Papiers zu schauen.

    glLoadIdentity();                            // Resette die Modelview Matrix
    glTranslatef(0.0f, -0.6f, zoom);                    // Zoome und positioniere Kamera über dem Boden (0.6 Einheiten darüber)

Als nächstes setzen wir die Farbmaske. Etwas neues in diesem Tutorial! Die 4 Werte für die Farbmaske repräsentieren rot, grün, blau und Alpha. Standardmäßig sind all diese Werte auf GL_TRUE gesetzt.

Wenn der Rot-Wert von glColorMask({red},{green},{blue},{alpha}) auf GL_TRUE gesetzt wurde und alle anderen Werte auf 0 (GL_FALSE), wäre die einzige Farbe, die auf dem Screen sichtbar wäre, rot. Wenn der Wert für rot auf 0 (FL_FALSE) gesetzt wurde, aber all die anderen Werte auf GL_TRUE, würde jede Farbe außer rot auf den Screen gezeichnet werden.

Wir wollen zur Zeit nichts auf den Screen zeichnen, mit allen Werten auf 0 (GL_FALSE) gesetzt, werden keine Farben auf den Screen gezeichnet.

    glColorMask(0,0,0,0);                            // Setze Farbmaske

Nun noch mehr spaßige Sachen... Initialisierung des Stencil Buffers und Stencil Testing!

Wir fangen mit der Aktivierung des Stencil Testing an. Wenn Stencil Testing aktiviert wurde, sind wir in der Lage den Stencil Buffer zu modifizieren.

Es ist sehr schwierig die folgenden Befehle zu erklären, seien Sie also nachsichtig mit mir und wenn Sie eine bessere Erklärung haben, lassen Sie es mich bitte wissen. Im folgenden Code initialisieren wir einen Test. Die Zeile glStencilFunc(GL_ALWAYS, 1, 1) teilt OpenGL mit, welche Art Test wir für jeden Pixel machen wollen, wenn ein Objekt auf den Screen gezeichnet wird.

GL_ALWAYS teilt OpenGL einfach mit, dass der Test immer durchlaufen wird. Der zweite Parameter (1) ist ein Referenz Wert, den wir in der dritten Codezeile testen werden und der dritte Parameter ist eine Maske. Die Maske ist ein Wert, der mit dem Referenzwert durch eine logische AND Verknüpfung verknüpft wird und im Stencil Buffer gespeichert wird, wenn der Test durchlaufen wurde. Ein Referenzwert von 1 geANDed mit einem Maskenwert von 1 ist 1. Wenn der Test also korrekt verläuft und wir das OpenGL mitteilen, wird eine eins in den Stencil Buffer plaziert (reference&mask=1).

Kurze Anmekrung: Stencil Testing ist er pro Pixel Test der jedes Mal ausgeführt wird, wenn ein Objekt auf den Screen gezeichnet wird. Der Referenzwert der mit dem Maskenwert geANDed wird, wird auf den aktuellen Stencilwert getestet, der wiederrum mit dem Maskenwert geANDed wird.

Die dritte Codezeile testet auf vier verschiedene Konditionen basierend auf der Stencil-Funktion die wir benutzen. Die ersten beiden Parameter sind GL_KEEP und der dritte ist GL_REPLACE.

Der erste Parameter teilt OpenGL mit, was zu machen ist, wenn der Test fehl schlägt. Da der erste Parameter GL_KEEP ist und der Test schief laufen würde (was er nicht kann, da wir die Funktion auf GL_ALWAYS gesetzt haben), würden wir den Stencilwert so lassen, wie er gerade ist.

Der zweite Parameter teilt OpenGL mit, was zu tun ist, wenn der Stencil Test bestanden wird, aber der Depth Test fehl schlägt. Im folgenden Code werden wir den Depth Test eventuell deaktivieren, weshalb dieser Parameter ignoriert werden kann.

Der dritte Parameter ist der wichtigste. Er teilt OpenGL mit was gemacht werden soll, wenn die Tests bestanden werden! In unsersm Code teilen wir OpenGL mit, dass der Wert im Stencil Buffer ersetzt (GL_REPLACE) werden soll. Der Wert, den wir in den Stenicl Buffer plazieren, ist unser Referenzwert geANDed mit unserem Maskenwert, welcher 1 ist.

Nachdem wir die Art des Testens gesetzt haben, deaktivieren wir Depth Testing und springen zu dem Code, der unseren Boden zeichnet.

In einfachem deutsch werde ich versuchen, nochmal alles zusammenzufassen, was dieser Code bisher macht...

Wir teilen OpenGL mit, dass keine Farben auf den Screen gezeichnet werden sollen. Das bedeutet, dass wenn wir den Boden zeichnen, dieser nicht auf dem Screen zu sehen sein wird. ABER... jeder Punkt auf dem Screen wo unser Objekt (unser Boden) sein sollte, wenn wir ihn sehen könnten, wird basierend auf der Art des Stencil Testing, getestet. Der Stencil Buffer ist am Anfang voller Nullen (leer). Wir wollen den Stencilwert auf 1 setzen wo immer unser Objekt gezeichnet werden würde, wenn wir es sehen könnten. Deshalb teilen wir OpenGL mit, dass uns der Test nicht interessiert. Wenn ein Pixel auf den Screen gezeichnet werden sollte, wollen wir diesen Punkt mit einer 1 markieren. GL_ALWAYS macht genau das. Unsere Referenz und Masken Werte von1 stellen sicher, dass der Wert, der im Stencil Buffer plaziert wird, tatsächlich eine 1 sein wird! Da wir nicht sichtbar zeichnen, unsere Stencil Operation überprüft jeden Pixel und ersetzt die 0 mit einer 1.

    glEnable(GL_STENCIL_TEST);                        // aktiviere Stencil Buffer um den Boden "zu markieren"
    glStencilFunc(GL_ALWAYS, 1, 1);                        // immer bestehen, 1 Bit Ebene, 1 als Maske
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);                // Wir setzen den Stencil Buffer auf 1 wo wir ein Polygon zeichnen
                                        // 'Keep' (behalte) wenn Test fehl schlägt, Keep wenn Test erfolgreich ist, aber der Buffer Test fehl schlägt
                                        // ersetze wenn der Test bestanden wird
    glDisable(GL_DEPTH_TEST);                        // deaktiviere Depth Testing
    DrawFloor();                                // zeichne den Boden (zeichne in den Stencil Buffer)
                                        // wir wollen ihn nur im Stencil Buffer markieren

So, nun haben wir die unsichtbare Stencil Maske auf dem Boden. So lange Stencil Testing aktiviert ist, werden Pixel nur sichtbar, wo der Stencil Buffer einen Wert von 1 hat. Alle Pixel auf dem Screen, wo der unsichtbare Boden gezeichnet wurde, werden einen Stencilwert von 1 haben. Das bedeutet, das solange Stencil Testing aktiviert ist, die einzigen Pixel die wir sehen werden, werden die Pixel sein, die wir auf den selben Punkt zeichnen, wo unser unsichtbare Boden im Stencil Buffer definiert wurde. Der Trick beim erzeugen real aussehender Reflektion ist es, dass die Reflektion nur auf dem Boden ist und sonst nirgends!

So, da wir nun wissen, dass die Ball Reflektion nur dort gezeichnet werden wird, wo der Boden sich befinden sollte, ist es an der Zeit, die Reflektion zu zeichnen! Wir aktivieren Depth Testing und setzen die Farbmaske zurück auf eins (was bedeutet, dass alle Farben auf den Screen gezeichnet werden).

Anstatt GL_ALWAYS für unsere Stencil Funktion zu verwenden, werden wir GL_EQUAL verwenden. Wir lassen die Referenz und Masken Werte auf 1. Für die Stenicl Operation werden wir alle Parameter auf GL_KEEP setzen. Auf deutsch: jedes Objekt, das wir diesmal zeichnen werden, wird auf dem Screen erscheinen (da die Farbmaske auf true für jede Farbe gesetzt wurde). So lange Stencil Testing aktiviert ist, werden Pixel NUR dann gezeichnet, wenn der Stencil Buffer einen Wert von 1 hat (Referenzwert geANDed mit der Maske, was 1 GLEICH (GL_EQUAL) des Stencil Buffer Wertes geANDed mit der MAske ist, welche ebenfalls 1 ist). Wenn der Stencilwert nicht gleich 1 ist, da wo der aktuelle Pixel gezeichnet werden soll, so wird er nicht angezeigt! GL_KEEP teilt OpenGL einfach mit, dass keine Werte im Stencil Buffer modifziert werden sollen, wenn der Test erfolgreich ist ODER fehl schlägt!

    glEnable(GL_DEPTH_TEST);                        // aktiviere Depth Testing
    glColorMask(1,1,1,1);                            // Setze Farbmaske auf TRUE, TRUE, TRUE, TRUE
    glStencilFunc(GL_EQUAL, 1, 1);                        // Wir zeichnen nur da, wo der Stencil gleich 1 ist
                                        // (z.B. wo der Boden gezeichnet wurde)
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);                    // Verändere den Stencil Buffer nicht

Nun aktivieren wir die gespiegelte Clipping-Ebene. Diese Ebene ist durch eqr definiert und erlaubt es nur Objekte vom Mittelpunkt des Screens (wo der Boden ist) bis zum unteren Rand des Screens (jegliche negativen Werte auf der Y-Achse) zu zeichnen. Auf diese Weise kann der reflektierte Ball, den wir zeichnen nicht durch den Mittelpunkt unseres Bodens kommen. Das würde ziemlich schlecht aussehen, wenn das passieren würde. Wenn Sie nicht ganz verstehen, was ich meine, entfernen Sie die erste folgende Zeile aus dem Source Code und bewegen Sie den wirklichen Ball (nicht reflektierten) durch den Boden. Wenn Clipping nicht aktiviert ist, werden Sie den reflektierten Ball aus dem Boden heraussprigen sehen, sobald der echte Ball in den Boden hinein taucht.

Nachdem wir Clipping plane0 aktiviert haben (normalerweise können Sie 0-5 Clipping Ebenen haben), definieren wir die Ebene, indem wir die Parameter die in eqr gespeichert sind, übergeben.

Wir pushen die Matrix (welche grundsätzlich die Position von allem auf dem Screen speichert) und benutzen glScalef(1.0f,-1.0f,1.0f) um die Objekte 'upside down' zu 'flippen' (was eine wirklich echt aussehende Reflektion erzeugt). Dadurch, dass wir den Y-Wert von glScalef({x},{y},{z}) auf einen negativen Wert setzen, zwingen wir OpenGL dazu, auf die entgegengesetzte Seite der Y-Achse zu rendern. Es ist fast so, als ob wir den gesamten Screen auf den Kopf stellen würden. Wenn ein Objekt an einem positiven Wert auf der Y-Achse plaziert wird, wird es auf der unteren Seite des Screens erscheinen, anstatt in der oberen. Wenn Sie das Objekt zu sich hin rotieren lassen , wird es von Ihnen weg rotieren. Alles wird an der Y-Achse gespiegelt, bis Sie die Matrix poppen oder den Y-Wert zurück auf 1.0f, anstatt von -1.0f mittels glScalef({x},{y},{z}) setzen.

    glEnable(GL_CLIP_PLANE0);                        // aktiviere Clip Ebene um Artifakte zu entfernen
                                        // (Wenn das Objekt den Boden durchquert)
    glClipPlane(GL_CLIP_PLANE0, eqr);                    // Gleichung für reflektierte Objekte
    glPushMatrix();                                // Pushe die Matrix auf den Stack
        glScalef(1.0f, -1.0f, 1.0f);                    // spiegel die Y Achse

Die erste folgende Zeile positioniert unser Licht an dem Ort, der durch LightPos spezifiziert ist. Das Licht sollte von unten rechts des reflektierten Balles scheinen, was eine sehr real aussehende Lichtquelle erzeugt. Die Position unseres Lichts wird ebenfalls gespiegelt. Auf den wirklichen Ball (den Ball über dem Boden) wird das Licht oben rechts Ihres Screens positioniert und scheint von oben rechts auf den wirklichen Ball. Wenn der reflektierte Ball gezeichnet wird, wird das Licht unten rechts von Ihrem Screen positioniert.

Wir bewegen uns dann nach oben oder unten auf der Y-Achse, je nach Wert in height. Translationen werden gespiegelt, wenn der Wert von height also gleich 5.0f ist, ist die Position zu der wir translatieren gespiegelt (-5.0f). Das reflektierte Bild wird unter dem Boden positionierte anstatt über dem Boden!

Nachdem unser reflektierter Ball positioniert wurde, rotieren wir den Ball auf der X-Achse und der Y-Achse, basierend auf den Werten in xrot und yrot. Denken Sie daran, dass auch die Rotationen auf der X-Achse gespiegelt werden. Wenn der wirkliche Ball (der Ball über dem Boden) auf der X-Achse auf Sie zu rollt, wird die Reflektion von Ihnen weg rollen.

Nachdem der reflektierte Ball positioniert und rotiert wurde, zeichnen wir den Ball, indem wir DrawObject() aufrufen und die Matrix poppen (was die Dinge wieder so herrichtet, wie sie waren, bevor wir den Ball gezeichnet haben). Durch das Poppen der Matrix wird auch nicht mehr entlängs der Y-Achse gespiegelt.

Wir deaktivieren dann unsere Clipping-Ebene (plane0), so dass wir nicht nur auf der oberen Hälfte des Screens zeichnen können und als letztes deaktivieren wir Stencil Testing, so dass wir auch die anderen Punkte auf dem Screen zeichnen können, auch da wo nicht der Boden ist.

Beachten Sie, dass wir den reflektierten Ball zuerst zeichnen, bevor wir den Boden zeichnen. Ich werde später erklären, warum wir das so machen.

        glLightfv(GL_LIGHT0, GL_POSITION, LightPos);            // Setze Light0
        glTranslatef(0.0f, height, 0.0f);                // Positioniere das Objekt
        glRotatef(xrot, 1.0f, 0.0f, 0.0f);                // Rotiere lokales Koordinatensystem auf der X-Achse
        glRotatef(yrot, 0.0f, 1.0f, 0.0f);                // Rotiere lokales Koordinatensystem auf der Y-Achse
        DrawObject();                            // zeichne die Sphere (Reflektion)
    glPopMatrix();                                // Poppe die Matrix runter vom Stack
    glDisable(GL_CLIP_PLANE0);                        // deaktivere Clip Ebene um den Boden zu zeichnen
    glDisable(GL_STENCIL_TEST);                        // Wir benötigen den Stencil Buffer nicht mehr (deaktiviere)

Wir fangen in diesem Codeabschnitt mit der Positionierung unseres Lichtes an. Die Y-Achse wird nicht länger gespiegelt, weshalb beim zeichnen des Lichts diesmal die Position oben im Screen sein wird, anstatt unten rechts vom Screen.

Wir aktivieren Blending, deaktivieren Beleuchtung und setzen den Alphawert auf 80% mittels des Befehls glColor4f(1.0f,1.0f,1.0f,0.8f). Der Blending Modus wird mittels glBlendFunc() gesetzt und der halbtransparente Boden wird über den reflektierten Ball gezeichnet.

Wenn wir erst den Boden gezeichnet hätten und dann den reflektierten Ball, würde der Effekt nicht sehr gut aussehen. Indem wir den Ball zeichnen und dann den Boden, können Sie eine leichte Mischung der Farben von Boden und Ball auf dem Ball wahrnehmen. Wenn ich in einen BLAUEN Spiegel schaue, würde ich eine Reflektion erwarten, die auch etwas bläulich aussieht. Indem wir erst den Ball rendern, sieht das reflektierte Bild so aus, als ob es mit der Bodenfarbe gefärbt wurde.

    glLightfv(GL_LIGHT0, GL_POSITION, LightPos);                // Setze die Light0 Position
    glEnable(GL_BLEND);                            // aktiviere Blending (ansonsten wird das reflektierte Objekt nicht gezeigt)
    glDisable(GL_LIGHTING);                            // da wir Blending benutzen, deaktiveren wir die Beleuchtung
    glColor4f(1.0f, 1.0f, 1.0f, 0.8f);                    // Setze die Farbe auf weiß mit 80% Alpha
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);            // Blending basierend auf dem Quell-Alpha und 1 Minus Ziel-Alpha
    DrawFloor();                                // zeichne den Boden auf den Screen

Nun zeichnen wir den 'wirklichen' Ball (den, der über dem Boden schwebt). Wir haben die Beleuchtung deaktiviert, als wir den Boden gezeichnet haben, aber nun ist es Zeit einen weiteren Ball zu zeichnen, weshalb wir die Beleuchtung wieder einschalten.

Wir benötigen das Blending nicht mehr, weshalb wir Blending deaktivieren. Wenn wir Blending nicht deaktivieren würden, würden die Farben des Bodens sich mit den Farben unseres 'wirklichen' Balls mischen, wenn dieser über dem Boden schwebt. Wir wollen den 'wirklichen' Ball nicht so aussehen lassen, wie die Reflektion, weshalb wir Blending deaktivieren.

Wir werden den tatsächlichen Ball nicht clippen. Wenn der wirkliche Ball durch den Boden geht, sollten wir ihn aus der unteren Seite wieder herauskommen sehen. Wenn wir den Ball clippen würden, würde der Ball nicht auf der anderen Seite sichtbar sein, sobald er durch den Boden geht. Wenn Sie den Ball nicht durch den Boden gehen lassen wollen, würden Sie eine Clipping-Gleichung aufstellen, welche den Y-Wert auf +1.0f setzen würden, dann, wenn der Ball durch den Boden gehen würde, würden Sie ihn nicht sehen (Sie würden den Ball nur sehen, wenn dieser an einem positiven Y-Wert auf der Y-Achse gezeichnet werden würde. Für dieses Demo gibt es keinen Grund, warum wir ihn nicht durch den Boden gehen lassen sollten).

Wir translatieren dann hoch oder runter auf der Y-Achse um uns an der angegebenen Höhe zu positionieren. Nur dieses Mal wird die Y-Achse nicht gespiegelt, weshalb der Ball in die entgegengesetzte Richtung wandert, als die, in die das reflektierte Bild wandert. Wenn wir den 'wirklichen' Ball runter bewegen, wird der reflektierte Ball sich nach oben bewegen. Wenn wir den 'wirklichen' Ball nach oben bewegen, wird der reflektierte Ball sichnach uten bewegen.

Wir rotieren den 'wirklichen' Ball und, erneut, da die Y-Achse nicht gespiegelt ist, wird der Ball sich in die entgegengesetzte Richtung des reflektierten Ball's bewegen. Wenn der reflektierte Ball auf Sie zu rollt, wird der 'wirkliche' Ball von Ihnen weg rollen. Das erzeugt die Illusion einer wahren Reflektion.

Nachdem wir den Ball positioniert und rotiert haben, zeichnen wir den 'wirklichen' Ball, indem wir DrawObject() aufrufen.

    glEnable(GL_LIGHTING);                            // aktiviere Beleuchtung
    glDisable(GL_BLEND);                            // deaktiviere Blending
    glTranslatef(0.0f, height, 0.0f);                    // Positioniere den Ball in der richtigen Höhe
    glRotatef(xrot, 1.0f, 0.0f, 0.0f);                    // Rotiere auf der X Achse
    glRotatef(yrot, 0.0f, 1.0f, 0.0f);                    // Rotier auf der Y Achse
    DrawObject();                                // zeichne den Ball

Der folgende Code lässt den Ball auf der X und Y Achse rotieren. Indem xrot um xrotspeed inkrementiert wird, lassen wir den Ball auf der X-Achse rotieren. Indem wir yrot um yrotspeed inkrementieren, lassen wir den Ball auf der Y-Achse rotieren. Wenn xrotspeed einen sehr hohen Wert in die positive oder negative Richtung hat, wird der Ball schneller rotieren, als wenn xrotspeed einen niedrigeren Wert, näher an 0.0f, hat. Das selbe gilt für yrotspeed. Je höher der Wert ist, desto schneller rotiert der Ball auf der Y-Achse.

Bevor wir TRUE zurückgeben, machen wir ein glFlush(). Damit teilen wir OpenGL mit, dass alle was noch in der GL Pipeline zu rendern ist, gerendert wird, bevor fortgefahren werden soll. Damit verhindern wir ein Flackern auf langsameren Grafikkarten.

    xrot += xrotspeed;                            // aktualisiere X Rotationswinkel um xrotspeed
    yrot += yrotspeed;                            // aktualisiere Y Rotationswinkel um yrotspeed
    glFlush();                                // Flushe die GL Pipeline
    return TRUE;                                // Alles verlief OK
}

Der folgende Code wird die Tastendrücke überwachen. Die ersten 4 Zeilen überprüfen, ob Sie eine der 4 Pfeiltasten gedrückt haben. Wenn dem so ist, wird der Ball entsprechend nach rechts, links, unten oder oben rotieren.

Die nächsten beiden Zeilen überprüfen, ob Sie die Taste 'A' oder 'Z' gedrpückt haben. Drücken Sie 'A', so zoomen Sie näher an den Ball heran und drücken Sie 'Z', zoomen Sie vom Ball weg.

Drücken Sie 'Bild hoch', so wird der Wert von height inkrementiert, was den Ball weiter nach oben setzt und durch das Drücken von 'Bild runter' dekrementieren Sie height, was den Ball runter (näher zum Boden) bewegt.

void ProcessKeyboard()                                // bearbeite Tastatur Ergebnisse
{
    if (keys[VK_RIGHT])    yrotspeed += 0.08f;                // Rechte Pfeiltaste wurde gedrückt (inkrementiert yrotspeed)
    if (keys[VK_LEFT])    yrotspeed -= 0.08f;                // Linke Pfeiltaste wurde gedrückt (dekrementiert yrotspeed)
    if (keys[VK_DOWN])    xrotspeed += 0.08f;                // Pfeiltaste runter wurde gedrückt (inkrementiert xrotspeed)
    if (keys[VK_UP])    xrotspeed -= 0.08f;                // Pfeiltaste hoch wurde gedrückt (dekrementiert xrotspeed)

    if (keys['A'])        zoom +=0.05f;                    // 'A' Taste wurde gedrückt... reinzoomen
    if (keys['Z'])        zoom -=0.05f;                    // 'Z' Taste wurde gedrückt... rauszoomen

    if (keys[VK_PRIOR])    height +=0.03f;                    // Seite hoch Taste wurde gedrückt, bewegt den Ball nach oben
    if (keys[VK_NEXT])    height -=0.03f;                    // Seite runter Taste wurde gedrückt, bewegt den Ball nach unten
}

Der KillGLWindow() Code hat sich nicht geändert, weshalb ich diesen überspringen werde.

GLvoid KillGLWindow(GLvoid)                            // Entferne das Fenster korrekt

Sie können den folgenden Code überfliegen. Auch wenn sich nur eine Codezeile in CreateGLWindow() geändert hat, habe ich den gesamten Code hier eingefügt, damit es einfacher ist, dem Tutorial zu folgen.

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
    GLuint        PixelFormat;                        // Enthält die Ergebnisse nachdem nach was passendem gesucht wurde
    WNDCLASS    wc;                            // Fenster Klassen Struktur
    DWORD        dwExStyle;                        // erweiterter Fenster-Stil
    DWORD        dwStyle;                        // Fenster-Stil

    fullscreen=fullscreenflag;                        // Setze das globale Fullscreen Flag

    hInstance        = GetModuleHandle(NULL);            // Ermittle die Instanz für unser Fenster
    wc.style        = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;        // Zeichne neu beim bewegen und eigener DC für's Fenster
    wc.lpfnWndProc        = (WNDPROC) WndProc;                // WndProc behandelt die Nachrichten
    wc.cbClsExtra        = 0;                        // Keine extra Fenster-Daten
    wc.cbWndExtra        = 0;                        // Keine extra Fenster-Daten
    wc.hInstance        = hInstance;                    // Setze die Instanz
    wc.hIcon        = LoadIcon(NULL, IDI_WINLOGO);            // Lade das Standard-Icon
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);            // Lade den Pfeil-Zeiger
    wc.hbrBackground    = NULL;                        // es wird kein Hintergrund für GL benötigt
    wc.lpszMenuName        = NULL;                        // Wir wollen kein Menü
    wc.lpszClassName    = "OpenGL";                    // Setze den Klassen-Namen

    if (!RegisterClass(&wc))                        // Versuche die Fenster-Klasse zu registrieren
    {
        MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        return FALSE;                            // gebe FALSE zurück
    }

    if (fullscreen)                                // Fullscreen Modus erwünscht?
    {
        DEVMODE dmScreenSettings;                    // Device Modus
        memset(&dmScreenSettings,0,sizeof(dmScreenSettings));        // Stelle sicher, dass der Speicher geleert ist
        dmScreenSettings.dmSize=sizeof(dmScreenSettings);        // Größe der Devmode Struktur
        dmScreenSettings.dmPelsWidth    = width;            // ausgewählte Screen Breite
        dmScreenSettings.dmPelsHeight    = height;            // ausgewählte Screen Höhe
        dmScreenSettings.dmBitsPerPel    = bits;                // ausgewählte Bits Per Pixel
        dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

        // Versuche gewählten Modus zu setzen und Ergebnisse zurückliefern. ANMERKUNG: CDS_FULLSCREEN lässt die Start-Leiste nicht anzeigen.
        if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
        {
            // Wenn der Modus fehl schlägt, biete zwei Optionen an. Beenden oder im Fenster-Modus laufen lassen.
            if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
            {
                fullscreen=FALSE;                // wähle Fenster-Modus aus. Fullscreen=FALSE
            }
            else
            {
                // Message Box anzeigen, die den Benutzer wissen lässt, dass das Programm geschlossen wird.
                MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
                return FALSE;                    // gebe FALSE zurück
            }
        }
    }

    if (fullscreen)                                // Sind wir immer noch im Fullscreen Modus?
    {
        dwExStyle=WS_EX_APPWINDOW;                    // erweiterter Fenster-Stil
        dwStyle=WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;        // Fenster-Stil
        ShowCursor(FALSE);                        // verstecke Maus-Zeiger
    }
    else
    {
        dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;            // erweiterter Fenster-Stil
        dwStyle=WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;// Fenster Stil
    }

    // erzeuge das Fenster
    if (!(hWnd=CreateWindowEx(    dwExStyle,                // erweiterter Stil für das Fenster
                    "OpenGL",                // Klassen Name
                    title,                    // Fenster Titel
                    dwStyle,                // Fenster Stil
                    0, 0,                    // Fenster Position
                    width, height,                // ausgewählte Breite und Höhe
                    NULL,                    // Kein Parent-Fenster
                    NULL,                    // Kein Menu
                    hInstance,                // Instanz
                    NULL)))                    // Leite nichts an WM_CREATE weiter
    {
        KillGLWindow();                            // Resette die Ansicht
        MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        return FALSE;                            // Gebe FALSE zurück
    }

    static    PIXELFORMATDESCRIPTOR pfd=                    // pfd teilt Windows mit, wie wie die Dinge haben wollen
    {
        sizeof(PIXELFORMATDESCRIPTOR),                    // Größe des Pixel Format Descriptors
        1,                                // Versions Nummer
        PFD_DRAW_TO_WINDOW |                        // Format muss Fenster unterstützen
        PFD_SUPPORT_OPENGL |                        // Format muss OpenGL unterstützen
        PFD_DOUBLEBUFFER,                        // Muss Double Buffering unterstützen
        PFD_TYPE_RGBA,                            // Fordere ein RGBA Format an
        bits,                                // wähle unsere Farbtiefe aus
        0, 0, 0, 0, 0, 0,                        // Color Bits werden ignoriert
        0,                                // Kein Alpha Buffer
        0,                                // Shift Bit wird ignoriert
        0,                                // Kein Accumulation Buffer
        0, 0, 0, 0,                            // Accumulation Bits werden ignoriert
        16,                                // 16Bit Z-Buffer (Depth Buffer)

Die einzige Änderung in diesem Codeabschnitt ist die folgende Zeile. Es ist *SEHR WICHTIG*, dass Sie den Wert von 0 auf 1 ändern oder einen anderen Wert ungleich null. In allen vorherigen Tutorials war der Wert der folgende Zeile gleich 0. Um Stencil Buffering zu verwenden, MUSS dieser Wert größer oder gleich 1 sein. Dieser Wert ist die Anzahl der Bits, die Sie für den Stencil Buffer verwenden wollen.

        1,                                // benutze Stencil Buffer ( * Wichtig * )
        0,                                // Kein Auxiliary Buffer
        PFD_MAIN_PLANE,                            // Haupt-Zeichen-Schicht
        0,                                // Reserviert
        0, 0, 0                                // Layer Masken werden ignoriert
    };

    if (!(hDC=GetDC(hWnd)))                            // Haben wir einen Device Kontext erhalten?
    {
        KillGLWindow();                            // Resette die Anzeige
        MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        return FALSE;                            // Gebe FALSE zurück
    }

    if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))                // Hat Windows ein passendes Pixel Format gefunden?
    {
        KillGLWindow();                            // Resette die Anzeige
        MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        return FALSE;                            // Gebe FALSE zurück
    }

    if(!SetPixelFormat(hDC,PixelFormat,&pfd))                // Können wir das Pixel Format setzen?
    {
        KillGLWindow();                            // Resette die Anzeige
        MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        return FALSE;                            // Gebe FALSE zurück
    }

    if (!(hRC=wglCreateContext(hDC)))                    // Können wir einen Rendering Kontext kriegen?
    {
        KillGLWindow();                            // Resette die Anzeige
        MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        return FALSE;                            // Gebe FALSE zurück
    }

    if(!wglMakeCurrent(hDC,hRC))                        // Versuche den Rendering Kontext zu aktivieren
    {
        KillGLWindow();                            // Resette die Anzeige
        MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        return FALSE;                            // Gebe FALSE zurück
    }

    ShowWindow(hWnd,SW_SHOW);                        // Zeige das Fenster
    SetForegroundWindow(hWnd);                        // Etwas höhere Priorität
    SetFocus(hWnd);                                // Setze den Tastatur-Fokus auf das Fenster
    ReSizeGLScene(width, height);                        // Initialisiere unseren perspektivischen GL-Screen

    if (!InitGL())                                // Initialisiere unser neu erzeugtes GL Fenster
    {
        KillGLWindow();                            // Resette die Anzeige
        MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
        return FALSE;                            // Gebe FALSE zurück
    }

    return TRUE;                                // Erfolg
}

WndProc() hat sich nicht geändert, weshalb wir diese überspringen.

LRESULT CALLBACK WndProc(    HWND    hWnd,                    // Handle für dieses Fenster
                UINT    uMsg,                    // Nachricht für dieses Fenster
                WPARAM    wParam,                    // Weitere Nachrichten Informationen
                LPARAM    lParam)                    // Weitere Nachrichten Informationen

Nichts neues hier. Typischer anfang mit WinMain().

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
    }

Die einzige wirkliche Änderung in diesem Codeabschnitt ist der neue Fenstertitel, der jeden wissen lässt, dass es ein Tutorial über Reflektion mittels Stencil Buffer ist. Beachten Sie auch, dass wir die resx, resy und resbpp Variablen unserer Fenster-Erzeugungs-Prozedur übergeben, anstatt der üblichen 640, 480 und 16.

    // erzeuge unser OpenGL Fenster
    if (!CreateGLWindow("Banu Octavian & NeHe's Stencil & Reflection Tutorial", resx, resy, resbpp, fullscreen))
    {
        return 0;                            // Beende, wenn Fenster nicht erzeugt wurde
    }

    while(!done)                                // Schleife die so lange läuft, bis done=TRUE
    {
        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
            {
                TranslateMessage(&msg);                // Übersetze die Nachricht
                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)                        // Programm aktiv?
            {
                if (keys[VK_ESCAPE])                // Wurde ESC gedrückt?
                {
                    done=TRUE;                // ESC Signalisiert, dass Beendet werden soll
                }
                else                        // Es ist noch nicht Zeit zum beenden, zeichne Screen neu
                {
                    DrawGLScene();                // Zeichne die Szene
                    SwapBuffers(hDC);            // Swap Buffers (Double Buffering)

Anstatt auf Tastendrücke in WinMain() zu überprüfen, springen wir in die Tastatur-Handling Routine namens ProcessKeyboard(). Beachten Sie, dass die ProcessKeyboard() Routine nur dann aufgerufen wird, wenn das Programm aktiv ist!

                    ProcessKeyboard();            // bearbeite Tastendrücke
                }
            }
        }
    }

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

Ich hoffe wirklich, dass Sie dieses Tutorial genossen haben. Ich weiß, dass man noch etwas daran arbeiten könnte. Es war eins der schwierigsten Tutorials, die ich je geschrieben habe. Es ist einfach für mich zu verstehen, was alles macht und welche Befehle ich benötige um einen coolen Effekt zu erzeugen, aber wenn Sie sich hinsetzen und tatsächlich versuchen die Dinge zu erklären und dabei im Hinterkopf behalten, dass die meisten Leute noch nie etwas vom Stencil Buffer gehört haben, ist es wirklich schwer! Wenn Sie irgend etwas bemerken, dass klarer gemacht werden könnte oder Sie denken, dass Fehler im Tutorial sind, lassen Sie es mich bitte wissen. Wie immer, ich möchte das beste verfügbare Tutorial machen, das möglich ist, Ihr Feedback wird wirklich erwünscht.

Banu Octavian (Choko)

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 Joachim Rohde )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Dan )
* DOWNLOAD Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux Code für diese Lektion. ( Conversion by Gray Fox )
* 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.