www.codeworx.org/opengl-tutorials/Tutorial 7: Texturfilter und Lichteffekte

Lektion 7: Texturfilter und Lichteffekte

In diesem Teil werde ich euch 3 verschiedene Texturfilter vorstellen. Ich werde zeigen, wie man ein Objekt mit Hilfe einer Tastatur bewegt und schließlich wie man einfache Belichtungen in eine OpenGL Szene einbindet. Da viel neues in diesem Teil besprochen wird solltet ihr bei Verständnisproblemen noch einmal die älteren Kurse durchgehen. Es ist wichtig erst einmal die Grundlagen zu verstehen bevor man weitermacht.

Wir werden wieder den Code vom ersten Kurs verwnden. Wie immer, wenn es grundlegende Veränderungen in einem Abschnitt gibt werde ich ihn komplett aufführen. Beginnen wir, indem wir einige neue Variablen deklarieren.

#include <windows.h>	        // Header File For Windows
#include <gl\gl.h>		// Header File For The OpenGL32 Library
#include <gl\glu.h>		// Header File For The GLu32 Library
#include <gl\glaux.h>	        // Header File For The GLaux Library

static	HGLRC hRC;		// Permanent Rendering Context
static	HDC hDC;		// Private GDI Device Context

BOOL	keys[256];		// Array Used For The Keyboard Routine
Die folgenden Zeilen sind neu. Wir benötigen 3 boolsche Variablen. BOOL bedeutet das die Variable nur den Inhalt TRUE (wahr) oder FALSE (falsch) haben kann. Wir definieren eine Variable namens light um festzuhalten, ob die Belichtung aktiviert oder deaktiviert ist. Die Variablen lp und fp nutzen wir, um zu sehen ob die "L" oder "F" Taste gedrückt wird. Ich erkläre später noch genauer wozu wir diese Variablen benötigen. Im Moment sind sie einfach nur nötig.
BOOL	light;		        // Lighting ON/OFF
BOOL	lp;			// L Pressed?
BOOL	fp;			// F Pressed?
Nun deklarieren wir 5 weitere Variablen, welche die Neigung auf der X-Achse (xrot), auf der Y-Achse (yrot), auf der Z-Achse (zrot) festhält, sowie die Geschwindigkeit der Rotation auf der X-Achse (xspeed) und auf der Y-Achse (yspeed). Wir definieren auch eine Variable, die festhält wie weit sich die Kiste im Bildschirm befindet.

GLfloat	xrot;			// X Rotation
GLfloat	yrot;			// Y Rotation
GLfloat xspeed;			// X Rotation Speed
GLfloat yspeed;			// Y Rotation Speed
GLfloat	z=-5.0f;		// Depth Into The Screen

Nun deklarieren wir die Arrays die zur Beleuchtung genutzt werden. Wir nutzen zwei unterschiedliche Arten von Licht. Ambientes Licht (ambient light) ist Licht, welches aus keiner bestimmten Richtung zu kommen scheint. Alle Objekte in einer Szene werden von ambientem Licht gleichermaßen angeleuchtet. Die zweite Art von Licht ise diffuses Licht (diffuse light). Diffuses Licht wird von einer Lichtquelle erzeugt und von den Objekten in einer Szene reflektiert. Jede Oberfläche auf die das diffuse Licht direkt scheint leuchtet hell auf. Die Flächen die von Licht nicht erreicht werden sind dunkler. Dies erzeugt einen netten Schatteneffekt an den Seiten unserer Kiste.

Licht wird genau wie eine Farbe erzeugt. Wenn der erste Wert 1.0f ist und die beiden folgenden 0.0f so scheint das Licht in einem hellen Rot. Wenn der dritte Wert 1.0f ist und die beiden anderen 0.0f haben wir ein helles, blaues Licht. Der vierte Wert ist der Alpha Wert. Wir lassen ihn im Moment auf 1.0f.

In der unten stehenden Zeile setzen wir die Werte für ein weiches, ambientes Licht mit halber Intensität fest (0.5f). Da alle Werte 0.5f enthalten wird ein Licht zwischen Dunkelheit (schwarz) und voller Intensität (weiß) erzeugt. Rot, Grün und Blau sind im selben Verhältnis gemischt und werfen einen Schatten von Schwarz (0.0f) bis weiß (1.0f). Ohne ambientes Licht wären die Punkte die das diffuse Licht nicht erreicht sehr dunkel.

GLfloat LightAmbient[]=	{ 0.5f, 0.5f, 0.5f, 1.0f };
In der nächsten Zeile setzen wir die Werte für ein sehr helles, diffuses Licht. Alle Werte sind auf 1.0f gesetzt. Das bedeutet das Licht ist so hell wie es nur sein kann. Ein diffuses Licht mit dieser Intensität wird unsere Kiste nett beleuchten.
GLfloat LightDiffuse[]=	{ 1.0f, 1.0f, 1.0f, 1.0f };

Schließlich setzen wir die Position der Lichtquelle fest. Die ersten 3 Werte sind die gleichen wie bei glTanslate. Der erste Wert dient dazu sich rechts und links auf der X-Achse zu bewegen. Der zweite um sich nach oben und unten auf der Y-Achse zu bewegen. Der dritte Wert um sich auf der Y-Achse in den Bildschirm hinein und wieder hinaus zu bewegen. Weil unser Licht genau auf die vordere Seite unserer Kiste scheinen soll bewegen wir uns nicht nach links oder rechts und so bleibt der erste Wert auf 0.0f. Wir bewegen uns auch nicht nach oben oder unten also ist auch der zweite Wert 0.0f. Mit dem dritten Wert stellen wir sicher das die Lichtquelle vorne, vor unserer Kiste ist. Also positionieren wir sie näher zum Betrachter. Sagen wir das Glas eures Monitors liegt bei 0.0f auf der Z-Achse. Positionieren wir das Licht bei 2.0f auf der Z-Achse und wenn ihr das Licht sehen könntet würde es direkt vor eurem Monitor schweben. Falls wir dies tun würden wäre der einzige Weg die Kiste vor der Monitorscheibe darzustellen. Natürlich wäre sie dann nicht mehr auf dem Monitor sichtbar also ist es egal wo wir das Licht darstellen. Macht das Sinn?

Es gibt keinen wirklich einfachen Weg, um den dritten Parameter zu erklären. Ihr solltet euch einfach merken das -2.0f näher zu euch liegt als -5.0f und -100.0f wäre SEHR weit in den Bildschirm. Wenn ihr 0.0f erreicht ist euer Objekt so groß das es den ganzen Bildschirm ausfüllt. Sobald ihr in den positiven Bereich kommt taucht euer Objekt nicht mehr auf dem Bildschirm auf, da es "über den Bildschirm hinaus" gelangt ist. Das meine ich wenn ich sage "außerhalb des Bildschirmes". Das Objekt existiert noch, nur könnt ihr es nicht mehr sehen.

Laßt den letzten Wert auf 1.0f. Dies teilt OpenGL mit das die angegebenen Koordinaten die Position der Lichtquelle darstellen. Mehr davon in einem späteren Kurs.

GLfloat LightPosition[]={ 0.0f, 0.0f, 2.0f, 1.0f };

Die unten stehende Filtervariable ist dazu da sich zu merken welche Textur gerade angezeigt wird. Die erste Textur (texture0) wird mit gl_nearest (keine Glättung) gefiltert. Die zweite Textur (texture1) nutzt gl_linear als Filter, welches sie netter aussehen lässt. Die dritte Textur (texture2) nutzt MipMapping, welches sehr gut aussehende texturen erzeugt. Die Variable filter enthält 0,1 oder 2, je nachdem welche Art von Textur wir nutzen wollen. Wir fangen mit der ersten (texture0) an.

Die nächste Zeile schafft Platz für 3 verschiedene Texturen. Sie werden in texture[0], texture[1] und texture[2] gespeichert.


GLuint filter; // Which Filter To Use GLuint texture[3]; // Storage for 3 textures LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc

Nun laden wir ein Bild und erstellen drei verschiedene Texturen damit. Dieses Turorial benutzt die glaux Bibliothek um ein Bild (Bitmap) zu laden, also vergewissere dich ob du sie eingebunden hast, bevor du den Code kompilierst. Ich kenne Delphi und VisualC++, und beide besitzen die glaux Bibliothek. Ich weiss nicht ob andere Sprachen sie auch benutzen. Ich werde nur die neuen Zeilen im Code erklären, wenn du eine Zeile entdeckst, die du nicht verstehst, lies im Tutorial 6 nach. Es erklärt das Laden und erstellen von Texturen aus Bitmap Bildern im einzelnen.

Direkt nach dem oberen Code, aber noch vor ReSizeGLScene(), fügen wir diese Codestelle ein. Das ist der selbe Code, den wir in Tutorial 6 benutzt haben um ein Bild zu laden. Nichts hat sich verändert. Wenn du es nicht verstehst, lies Tutorial 6.

AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image
{
  FILE *File=NULL;                       // File Handle

  if (!Filename)                         // Make Sure A Filename Was Given
  {
    return NULL;                         // If Not Return NULL
  }

  File=fopen(Filename,"r");              // Check To See If The File Exists

  if (File)                              // Does The File Exist?
  {
    fclose(File);                        // Close The Handle
    return auxDIBImageLoad(Filename);    // Load The Bitmap And Return A Pointer
  }

  return NULL;                           // If Load Failed Return NULL
}
Der nächste Teil lädt das Bitmap, (indem es den oberen Code aufruft) und macht daraus 3 Texturen. Die Variable Status sagt uns ob die Textur geladen werden konnte oder nicht.
int LoadGLTextures()                     // Load Bitmaps And Convert To Textures
{
  int Status=FALSE;                         // Status Indicator
  AUX_RGBImageRec *TextureImage[1];         // Create Storage Space For The Texture
  memset(TextureImage,0,sizeof(void *)*1);  // Set The Pointer To NULL
Nun laden wir das Bild und speichern es als Textur. TextureImage[0]=LoadBMP("Date/Crate.bmp") springt zu unserem LoadBMP() Code. Die Datei Crate.bmp im Verzeichnis Data wir geladen. Wenn alles gut geht ist das Bild dann in TextureImage[0] gespeichert, Status is auf TRUE gesetzt und wir beginnen unsere textur zu erstellen.
  // Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
  if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
  {
    Status=TRUE;                      // Set The Status To TRUE
Jetzt, wo wir das Bild in TextureImage[0] geladen haben, benutzen wir die Daten um uns drei Texturen zu erstellen. Die folgende Codezeile sagt OpenGL das wir drei Texturen erstellen die unter texture[0], texture[1] und texture[2] gespeichert werden.
    glGenTextures(3, &texture[0]);                    // Create Three Textures

Im Tutorial 6 haben wir 'linear' gefilterte Texturen verwendet. Diese benötigen einiges an Rechenleistung, sehen dafür aber auch wirklich schön aus. Die erste Art von Textur die wir in diesem Tutorial erstellen wollen benutzt GL_NEAREST. Diese Art von Textur hat eigentlich gar keinen Filter. Sie verbraucht fast keine Rechenpower, sieht allerdings auch schauderhaft aus. Wenn du jemals ein Spiel mit sehr abgehackten Texturen gespielt hast, wird es wohl diesen Typ von Textur verwendet haben. Das einzig gute an solchen Texturen ist, das sie meisst auch auf langsamen Computern gut laufen.

Wie du vielleicht bemerkt hast benutzen wir GL_NEAREST sowohl für MIN als auch für MAG. Du kannst auch beides verwenden GL_NEAREST und GL_LINEAR, die Textur wird besser aussehen. Aber wir sind an der Geschwindigkeit interessiert also verwenden wir die niedrige Qualität für beide. Der MIN_FILTER wird benutzt, wenn die Textur kleiner gezeichnet wird als ihre original Grösse. Der MAG_FILTER wird verwendet wenn die Textur vergrössert werden muss.

   // Create Nearest Filtered Texture
   glBindTexture(GL_TEXTURE_2D, texture[0]);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); ( NEW )
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); ( NEW )
   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 erstellen wollen is die selbe, wie die in Tutorial 6. Lineares filtern. Das einzigste was sich verändert hat ist, das wir die Texture in texture[1] und nicht in texture[0] speichern, da es unsere zweite Textur ist. Wenn wir es unter texture[0] speichern würden, würde es die mit GL_NEAREST erstellte Textur überschreiben.
   // Create Linear Filtered Texture
   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 kommen wir zu einer neuen Art Texturen zu generieren. Mipmapping ! Du hast vielleicht schonmal bemerkt, das wenn du ein Bild sehr klein auf dem Bildschirm erscheinen lässt, viele Details verschwinden. Oberflächen die gut aussahen fangen an schlecht auszusehen. Wenn man aber OpenGL sagt es soll eine 'mipmapped' Textur erstellen, versucht es mehrere, verschieden grosse 'high quality' Bilder zu erstellen. Wenn man nun eine 'mipmapped' Textur benutzt sucht OpenGL automatisch die detailreichste Textur heraus anstatt die Grösse der Textur zu verändern (was Detailverlust zur Folge hätte).

Ich sagte in Tutorial 6 das es eine Möglichkeit gäbe Texturen mit anderer Breite und Höhe als 64,128,256,etc zu erstellen. gluBuild2DMipmaps tut dies. Ich habe herausgefunden, das, wenn man 'mipmapped' Texturen erstellt, man jedes beliebige Bitmap nehmen kann. OpenGL verändert die Grösse und Breite automatisch.

Da dies Textur Nummer drei ist speichern wir sie unter texture[2]. Jetzt haben wir also texture[0] ohne Filter, texture[1] mit dem linearen Filter und texture[2] welche mipmapping benutzt. Wir sind fertig mit den Texturen für dieses Tutorial.

   // Create MipMapped Texture
   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); ( NEW )
Die nächste Zeile erstellt die 'mipmapped' Textur. Wir machen ein 2D Textur mit 3 Farben (Rot, Grün, Blau). TextureImage[0]->sizeX ist die Bitmap Breite, TexturImage[0]->sizeY ist die Bitmap Höhe. GL_RGB heisst, das wir ROT, GRÜN und BLAU in dieser Reihenfolge verwenden. GL_UNSIGNED_BYTE heisst das Daten auf denen die Textur aufgebaut ist aus Bytes besteht, und TextureImage[0]->data zeigt auf die Stelle wo die Daten der Textur liegen.
   gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, 
   TextureImage[0]->sizeY, 
GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); ( NEW ) }
Nun geben wir allen RAM frei, den wir verwendet haben um die Bitmap Daten zu speichern. Wir überprüfen die Daten auch in TextureImage[0] gespeichert sind. Dann geben den Speicherplatz frei, den die Bitmap Struktur belegt hielt.
  if (TextureImage[0])              // If Texture Exists
  {
    if (TextureImage[0]->data)      // If Texture Image Exists
    {
      free(TextureImage[0]->data);  // Free The Texture Image Memory
    }

    free(TextureImage[0]);          // Free The Image Structure
  }
Zum Schluss liefern wir den Status zurück. Wenn alles gut gegangen ist wird die Variable Status auf TRUE stehen. Wenn irgendetwas schief gegangen ist, wird Status auf FALSE stehen.
 
  return Status;                    // Return The Status
}
Jetzt laden wir die Texturen und Initialisieren OpenGL. Die erste Zeile der Funktion initGL lädt die Texturen. Nachdem die Texturen erstellt worden sind aktivieren wir 2D Texturen mit glEnable(GL_TEXTURE_2D). Der Schatten wird auf 'weich' gesetzt. Der hintergrund wird schwarz, Tiefen-Test wird aktiviert und eine nette perspektive wird errechnet.

int InitGL(GLvoid)                       // All Setup For OpenGL Goes Here
{
  if (!LoadGLTextures())                 // Jump To Texture Loading Routine
  {
    return FALSE;                        // If Texture Didn't Load Return FALSE
  }

  glEnable(GL_TEXTURE_2D);               // Enable Texture Mapping
  glShadeModel(GL_SMOOTH);               // Enable Smooth Shading
  glClearColor(0.0f, 0.0f, 0.0f, 0.5f);  // Black Background
  glClearDepth(1.0f);                    // Depth Buffer Setup
  glEnable(GL_DEPTH_TEST);               // Enables Depth Testing
  glDepthFunc(GL_LEQUAL);                // The Type Of Depth Testing To Do

  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);                      
  // Really Nice Perspective Calculations
Nun erstellen wir die Beleuchtung. Die nächste Zeile legt fest wieviel 'ambient' Licht uns Licht 1 gibt. Am Anfang dieses Tutorials haben wir diese Werte in LightAmbient gespeichert. Die Werte aus dem Array werden nun benutzt.
  glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);                         
  // Setup The Ambient Light
Nun legen wir die Stärke des Streulichtes fest. Wir haben diese Werte in LightDiffuse gespeichert. Die Werte aus dem Array werden nun benutzt.
  glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);                         
  // Setup The Diffuse Light
Jetzt legen wir die Position des Lichtes fest. Wir haben sie am Anfang in LightPosition gespeichert. (0.0f,0.0f,2.0f) Ausserhalb des Bildschirms.
  glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);                        
  // Position The Light
Zum Schluss brauchen wir Licht 1 nur noch zu aktivieren. Das Licht ist zwar defininiert und positioniert aber solange wir es nich einschalten wird nichts zu sehen sein.

  glEnable(GL_LIGHT1);  // Enable Light One
  return TRUE;          // Initialization Went OK
}
Endlich wenden wir uns nun dem texturierten Würfel zu. Ich habe nur die neuen Codezeilen kommentiert, falls jemandem die unkommentierten Zeilen Probleme bereiten schaut nochmal ins Tutorial 6.

int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);                     
  // Clear The Screen And The Depth Buffer

  glLoadIdentity();                                                       
  // Reset The View
Die nächsten drei Zeilen positionieren den Würfel und lassen ihn Routieren. glTranslatef(0.0f,0.0f,z) bewegt den Würfel um den Wert z auf der Z-Achse in den Bildschirm hinein oder in hinaus. glRotatef(xrot,1.0f,0.0f,0.0f) benutzt die Variable xrot um den Würfel um die X-Achse zu drehen. glRotatef(yrot,0.0f,1.0f,0.0f) benutzt die Variable yrot um den Würfel um die Y-Achse zu drehen.
  glTranslatef(0.0f,0.0f,z);                                              
  // Translate Into/Out Of The Screen By z

  glRotatef(xrot,1.0f,0.0f,0.0f);                                         
  // Rotate On The X Axis By xrot

  glRotatef(yrot,0.0f,1.0f,0.0f);                                         
  // Rotate On The Y Axis By yrot

Die nächste Zeile ähnelt sehr der Zeile, welche wir in Tutorial 6 benutz haben. Aber anstatt texture[0] setzen wir die aktuelle Textur mit texture[filter] fest. Jedesmal wenn wir 'F' drücken erhöht sich der Wert in filter. Wenn filter grösser als zwei ist wird die Variable zurückgestzt zu 0. Am Anfang hat die Variable filter den Wert 0.

Das ist das gleiche als wenn man schreiben würde glBindTexture(GL_TEXTURE_2D, texture[0]). Wenn man nun 'F' drückt erhöht sich filter und man schreibt glBindTexture(GL_TEXTURE_2D, texture[1]). Indem wir die variable filter verwenden können wir die textur zur Laufzeit wechseln.

  glBindTexture(GL_TEXTURE_2D, texture[filter]);                          
  // Select A Texture Based On filter

glBegin(GL_QUADS);                                                     
// Start Drawing Quads

glNormal3f ist neu in meinen Tutorials. Diese Funktion definiert eine Linie die aus der Mitte des Polygons im 90° Winkel hervor kommt. Wenn man Beleuchtung benutzt brauch man die Definition, denn sie sagt OpenGL in welche Richtung das Polygon zeigt und was die Oberfläche und was die andere Seite ist. Wenn man dies nicht tut können die verrücktesten Sachen passieren. Zum Beispiel könnten Teile des inneren des Polygons beleuchtet werden.

Wie man schnell bemerkt zeigt diese Linie in Richtung Betrachter, hat also einen Positiven Wert auf der Z-Achse. Wenn man den Würfel um 180° dreht zeigt seine Rückseite jetzt in Richtung des Betrachters. Die Linie hat aber immernoch einen positiven z Wert und deshabl wird nun die Rückseite beleuchtet, genau wie es sein sollte. Wenn man in den Würfel hinneinzoomt wird man feststellen das er dunkel ist, da die Linie ja nach aussen zeigt.

  // Front Face
  glNormal3f( 0.0f, 0.0f, 1.0f);  // Normal Pointing Towards Viewer
  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);

  // Back Face
  glNormal3f( 0.0f, 0.0f,-1.0f); // Normal Pointing Away From Viewer
  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f); 
  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
  
  // Top Face
  glNormal3f( 0.0f, 1.0f, 0.0f); // Normal Pointing Up
  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
                
  // Bottom Face
  glNormal3f( 0.0f,-1.0f, 0.0f); // Normal Pointing Down
  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);

  // Right face
  glNormal3f( 1.0f, 0.0f, 0.0f); // Normal Pointing Right
  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);

  // Left Face
  glNormal3f(-1.0f, 0.0f, 0.0f); // Normal Pointing Left
  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
glEnd(); // Done Drawing Quads
Die nächsten beiden Zeilen erhöhen xrot und yrot um xspeed bzw. yspeed. Wenn der Wert in xspeed oder yspeed hoch ist, erhöht sich xrot bzw. yrot schnell. Um so schneller der Wert sich erhöht, destso schneller dreht sich der Würfel um die entsprechende Achse.
 xrot+=xspeed; // Add xspeed To xrot
 yrot+=yspeed; // Add yspeed To yrot
 return TRUE;  // Keep Going
}

Jetzt gehen wir runter im Code zur Funktion WinMain(). Wir werden Code hinzufügen um das Licht ein und aus zuschalten, den Würfel zu drehen, den Texturfilter zu wechseln und den Würfel in den Bidschirm hinein bzw. hinaus zu bewegen. Gegen Ende der Funktion WinMain() könnt ihr die Zeile SwapBuffers(hDC) erkennen. Direkt danach fügt folgendes ein.

Dieser Abschnitt testet ob die Taste 'L' gedrückt wurde. Die erste Zeile schaut ob 'L' gedrückt wurde aber lp nicht FALSE ist, das heisst wenn 'L' schon gedrückt wurde, aber immernoch gehalten wird passiert nichts

  SwapBuffers(hDC);       // Swap Buffers (Double Buffering)
  if (keys['L'] && !lp)   // L Key Being Pressed Not Held?
  {

Wenn lp auf FALSE stand, das heisst 'L' wurde nicht gedrückt oder längst wieder los gelassen, dann wird lp auf TRUE gesetzt. Das bewirkt, das die Person die die Taste gedrückt hat, sie erst wieder loslassen muss, bevor der Code nochmalig ausgeführt wird. Wenn wir das nicht testen würden, würde das Licht anfangen zu flackern, da das Programm denken würde, der User drückt die Taste 'L' ganz schnell hintereinander.

Wenn lp einmal auf TRUE gesetzt wurde, der Computer weiss also das die Taste gedrückt wird, schalten wir das Licht an oder aus. Die Variable light kann nur TRUE oder FALSE sein. Also wenn wir sagen light=!light, heisst das Licht ist ungleich Licht. Ins deutsche übersetzt heisst das also wenn light TRUE war, mache es nicht TRUE (also FALSE) und wenn light False war, mache es nicht FALSE (also TRUE). Wenn light WAHR ist, wird es FALSCH, wenn es FALSCH ist, wird es WAHR.

    lp=TRUE;       // lp Becomes TRUE
    light=!light;  // Toggle Light TRUE/FALSE
Nun überprüfen wir was der Wert von light am Ende nun ist. Die erste Zeile ins Deutsche übertragen heisst: Wenn light gleich FALSE. Wenn wir also alles zusammenfassen, tut der Code folgendes: Wenn light auf FALSE steht, schalte das Licht aus, ansonsten (light steht auf TRUE) schalte das Licht an.
    if (!light)                       // If Not Light
    {
      glDisable(GL_LIGHTING);         // Disable Lighting
    }

    else                              // Otherwise
    {
      glEnable(GL_LIGHTING);          // Enable Lighting
    }
  }
Die nächsten Zeilen überprüfen ob die Taste 'L' losgelassen wurde. Wenn sie losgelassen wurde, wird die Variable lp auf FALSE gesetzt. Wenn wir nicht überprüfen würden ob die Taste losgelassen wurde, wären wir in der Lage das Licht einmalig einzuschalten, aber der Computer würde denken das 'L' gedrückt bleibt, und uns dadurch das Licht nicht wieder ausschalten lassen.
  if (!keys['L'])                     // Has L Key Been Released?
  {
    lp=FALSE;                         // If So, lp Becomes FALSE
  }
Nun machen wir etwas ähnliches mit der Taste 'F'. Wenn die Taste gedrückt wird und nicht nur gehalten wird die Variable fp auf TRUE gesetzt, was heisst das die Taste jetzt gehalten wird. Dies erhöht die Variable filter. Wenn filter grösser als 2 wird (was texture[3] entsprechen würde, die ja gar nicht existiert), setzen wir die Variable zurück auf 0.
  if (keys['F'] && !fp)               // Is F Key Being Pressed?
  {
    fp=TRUE;                          // fp Becomes TRUE
    filter+=1;                        // filter Value Increases By One
    if (filter>2)                     // Is Value Greater Than 2?
    {
      filter=0;                       // If So, Set filter To 0
    }
  }

  if (!keys['F'])                     // Has F Key Been Released?
  {
    fp=FALSE;                         // If So, fp Becomes FALSE
  }
Die nächsten vier Zeilen schauen nach, ob die 'Bid hoch' Taste gedrückt wurde. Wenn sie gedrückt wurde wird die Variable z verringert, was den Würfel weiter weg von uns bewegen wird, da wir ja glTranslate(0.0f,0.0f,z) in der Prozedur DrawGLScene benutzt haben.
  if (keys[VK_PRIOR])                 // Is Page Up Being Pressed?
  {
    z-=0.02f;                         // If So, Move Into The Screen
  }
Diese vier Zeilen tun fast dasselbe. Sie überprüfen ob die Taste 'Bild runter' gedrückt wurde und erhöhen dann die Variable z was den Würfel näher in unsere Richtung bewegen wird. Aufgrund glTranslate(0.0f,0.0f,z) in der Prozedur DrawGLScene.
  if (keys[VK_NEXT])                  // Is Page Down Being Pressed?
  {
    z+=0.02f;                         // If So, Move Towards The Viewer
  }
Nun müssen wir nur noch die Pfeiltasten überprüfen. Wenn man Links oder Rechts drückt, wird xspeed erhöht oder verringert. Wenn man Hoch oder Runter drückt, wird yspeed erhöht oder verringert. Am Anfang dieses Tutorials sagte ich bereits, wenn der Wert in xspeed oder yspeed hoch ist, dreht sich der Würfel schnell. Umso länger du also eine Pfeiltaste drückst, umso schneller dreht sich der Würfel in diese Richtung.
  if (keys[VK_UP])     // Is Up Arrow Being Pressed?
  {
    xspeed-=0.01f;     // If So, Decrease xspeed
  }
  if (keys[VK_DOWN])   // Is Down Arrow Being Pressed?
  {
    xspeed+=0.01f;     // If So, Increase xspeed
  }
  if (keys[VK_RIGHT])  // Is Right Arrow Being Pressed?
  {
    yspeed+=0.01f;     // If So, Increase yspeed
  }
  if (keys[VK_LEFT])   // Is Left Arrow Being Pressed?
  {
    yspeed-=0.01f;     // If So, Decrease yspeed
  }
Wie in alle meinen Tutorials, achte darauf das der Titel des Fensters auch korrekt ist.
  if (keys[VK_F1])     // Is F1 Being Pressed?
  {
    keys[VK_F1]=FALSE;             // If So Make Key FALSE
    KillGLWindow();                // Kill Our Current Window
    fullscreen=!fullscreen;        // Toggle Fullscreen / Windowed Mode
    // Recreate Our OpenGL Window

    if (!CreateGLWindow("NeHe's Textures, Lighting & Keyboard Tutorial",
         640,480,16,fullscreen))
    {
      return 0;                    // Quit If Window Was Not Created
    }
  }
}
}
}

  // Shutdown
  KillGLWindow();         // Kill The Window
  return (msg.wParam);    // Exit The Program
}

Nach diesem Tutorial solltest du in der Lage sein texturierte, echt aussehende Objekte, bestehend aus Vierecken, zu erstellen und sie zu bewegen. Du solltest die Vor- und Nachteile der verschiedenen Teturfilter kennen und auf Benutzereingaben durch die Tastatur reagieren können. Ausserdem bist du in der Lage nun Licheffekte in deine OpenGL Welt einzubeziehen um sie etwas realistischer zu gestalten.

Jeff Molofee (NeHe)

Die Source Codes und Ausführbaren Dateien zu den Kursen liegen auf der Neon Helium Website

Übersetzung von Delax & ChaosAngel/ Sundancer Inc.

(Präsentiert von www.codeworx.org)