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)