Lektion 6: Texture Mapping
- Download des Arbeitsbereiches
Wenn man mit dem Wissensstand der letzten Lektionen eine Rakete über den
Bildschirm fliegen lassen wollte, müßte diese aus hunderten von bunten
Polygonen zusammengesetzt werden, damit sie halbwegs realistisch wirkt. Das
ist unglaublich aufwendig und was bei einer Rakete für den Computer und
die Graka noch einigermaßen zu machen ist, scheitert garantiert bei einer
wirklich aufwendigen Szene, zum Beispiel in einem 3D-Shooter.
Da läßt sich dank Texturen Abhilfe schaffen, denn man könnte
ja auch auf jeden, in einer Szene vorkommenden Körper eine (oder mehrere)
Grafiken "kleben". Die Rakete könnte jetzt aus einigen wenigen
Polygonen und einer darübergelegten Fotografie ein solchen bestehen. Und
das Erbebnis sieht garantiert realistischer aus!
Also los. <stdio.h> muss inkludiert werden um mit Dateien umgehen zu können.
Außerdem müssen drei neue Variablen deklariert werden, die die Drehwinkel
für die Rotation enthalten und ein Array das die Textur(en) speichern kann.
#include <windows.h> // diverse Windowsfunktionen
#include <stdio.h> // Dateien sollen verwendet werden
#include <gl\glu.h> // Damit kann Glu32 genutzt werden.
#include <gl\gl.h> // Damit kann OpenGL32 genutzt werden.
#include <gl\glaux.h> // Und das Gleiche nochmal für Glaux
HGLRC hRC=NULL; // Der OpenGL Rendering Context
HDC hDC=NULL; // Geräte Kontext
HWND hWnd=NULL; // Verweist später auf den Windows Handle
HINSTANCE hInstance; // Die Instanz der Anwendung
bool keys[256]; // Vektor (Array) der den Status
// einzelner Tasten enthält
// (gedrückt/nicht gedrückt)
bool active=TRUE; // Wenn active FALSE ist, wurde das
// Fenster vom Benutzer minimiert.
bool fullscreen=TRUE; // Läuft das Programm im Vollbildmodus
// oder nicht?
GLfloat xrot; // Rotation um die X-Achse
GLfloat yrot; // Rotation um die Y-Achse
GLfloat zrot; // Rotation um die Z-Achse
GLuint texture[1]; // Texturspeicher
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// WndProc wird deklariert
Die Aufgabe der folgenden Prozedur ist es, Bitmap Dateien zu laden. Diese Dateien
dürfen als Seitenmaße ausschließlich Zweipotenzen haben (64,
128 oder 256 Pixel). Um jetzt Objekte darzustellen die nicht genau quadratisch
sind, muss die Grafik mit einem Bildbearbeitungsprogramm so verzerrt werden,
das sie genau in ein solches Format passt. Natürlich kann die Textur dann
mit OpenGL wieder auf eine "vernüftige" Größe gezerrt
werden, das ist umständlich, aber nur schwer zu umgehen.
Jetzt wird ein File Handle erzeugt, mit dem das Programm auf eine geöffnete
Datei zugreifen kann.
AUX_RGBImageRec *LoadBMP(char *Filename) // Lädt ein BMP
{
FILE *File=NULL; // Das File Handle
if (!Filename)
// Ist ein Dateiname übergeben worden?
{
return NULL; // offenbar nicht... Abbruch
}
File=fopen(Filename,"r");
// Versuch die Datei zu öffnen
if (File) // Existiert die Datei?
{
fclose(File); // Datei schließen
return auxDIBImageLoad(Filename);
// BMP laden und Zeiger (Pointer) zurückgeben,
// der auf die Bilddaten verweist
}
return NULL; // Laden hat nicht geklappt
}
Als nächstes müssen die Bilddaten in eine Textur umgewandelt werden.
Status wird am Anfang auf FALSE gesetzt und speichert, ob ein BMP korrekt geladen
und konvertiert werden konnte. Der darauffolgende, sogenannte Image Record enthält
die Texturdaten und einige Informationen über die Textur wie Höhe
und Breite.
int LoadGLTextures() // Bitmaps laden und konvertieren
{
int Status=FALSE;
// Status ersteinmal auf FALSE setzen
AUX_RGBImageRec *TextureImage[1];
// Speicherplatz für die Textur schaffen
memset(TextureImage,0,sizeof(void *)*1);
//Vorher sichergehen das dieser auch leer ist
Das Bitmap "Data/codeworx.bmp" wird mit Hilfe von LoadBMP geladen
und konvertiert. Falls das geklappt hat, wird Status auf TRUE gesetzt.
if (TextureImage[0]=LoadBMP("Data/codeworx.bmp"))
// Bitmap laden
{
Status=TRUE; // hat geklappt.
Aus den gespeicherten Bilddaten in TextureImage[0] wird jetzt mit Hilfe von
glGenTextures() in texture[0] eine Textur erstellt. glBlindTexture() teilt OpenGL
mit, das es sich bei texture[0] um eine 2D-Textur handelt.
glGenTextures(1, &texture[0]); // Textur wird erstellt
glBindTexture(GL_TEXTURE_2D, texture[0]);
//Es handelt sich um eine 2D Textur
Wirklich fertig ist die Textur leider noch nicht, wieder muss "mitgeteilt"
werden, das es sich um eine 2D-Textur handelt, der übergebene Wert 0 ist
vorerst unwichtig, repräsentiert den Grad derDetails, 3 steht für
die 3 Farbkanäle Rot, Grün und Blau in denen das Bild als BMP vorliegt.
Die Abmessungen werden automtisch mit sizeX und sizeY ermittelt, die nächste
Null gibt die Rahmenbreite an, was ersteinmal vernachläßigt werden
kann, der Farmodus ist RGB, die Bildinformationen bestehen aus unsigned Bytes
und mit TextureImage[0]->data werden die Bilddaten letztendlich ausgelesen.
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX,
TextureImage[0]->sizeY, 0, GL_RGB,
GL_UNSIGNED_BYTE, TextureImage[0]->data);
Die nächsten Zeilen legen fest, welche Filter benutzt werden sollen um
die Textur zu verzerren. GL_TEXTURE_MIN_FILTER steht für den Filter der
verwendet werden soll wenn die Textur zusammengedrückt werden soll, GL_TEXTURE_MAG_FILTER
wird genutzt wenn die Textur auseinandergezerrt wird. Die Filtermethode GL_LINEAR
sieht sehr schön aus, belastet aber den Prozessor recht stark. GL_NEAREST
kann ebenfalls genutzt werden, was zwar Ressourcen spart, leider aber recht
pixelig wirkt.
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
// GL_LINEAR wird für GL_TEXTURE_MIN_FILTER genutzt
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
// GL_LINEAR wird für GL_TEXTURE_MAG_FILTER genutzt
}
Abschließend sollte der durch TextureImage[0] belegte RAM wieder freigegeben
werden
if (TextureImage[0]) // Existiert TextureImage[0]?
{
if (TextureImage[0]->data) // enthält sie Bilddaten?
{
free(TextureImage[0]->data);
// wenn ja, dann diese Bilddaten löschen
}
free(TextureImage[0]);
// Und auch die Struktur an sich löschen.
}
Und der Status wird zurückgegen, ist dieser TRUE, hat alles geklappt.
return Status; // Status zurückgeben
}
InitGL wurde um einige Zeilen erweitert. Zuerst werden die Texturen geladen,
wenn das nicht klappt, also der zurückgegene Status von LoadGLTextures()
FALSE ist, wird auch InitGL ein FALSE zurückgeben und damit das ganze Programm
beenden. Wenn allerdings alles geklappt haben sollte, wird das 2D Texture Mapping
aktiviert. Wird das nicht getan, werden alle Flächen weiss dargestellt,
was sicher nicht Ziel der Übung sein soll ;).Der Rest sollte noch aus vorherigen
Versionen bekannt sein und wird hier einfach übernommen.
int InitGL(GLvoid)
{
if (!LoadGLTextures())
{
return FALSE; // Texturen konnten nicht geladen werden...
}
glEnable(GL_TEXTURE_2D); // Texture Mapping aktivieren
glShadeModel(GL_SMOOTH);
// Das Smooth Shading wird aktiviert, das
// sehr schöne Farbübergänge auf Körpern ermöglicht.
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// In diesem Falle ein schwarzer Hintergrund
glClearDepth(1.0f);
// depht buffer zurückgesetzt
glEnable(GL_DEPTH_TEST);
// Aktivierung des depht Tests (dazu später mehr.)
glDepthFunc(GL_LEQUAL);
// Der Typ des Depht Tests wird festgelegt
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
// Perspektive wird festgelegt
return TRUE; // Initialisierung scheint geklappt zu haben!
}
Der Würfel wird jetzt endlich gezeichnet. Die ersten Zeilen sind nicht
neu hinzugekommen
int DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Die vorherige Szene wird vom Bildschirm gelöscht,
// damit die neuen nicht einfach über die alten
// Objekte gezeichnet werden
glLoadIdentity();
// modelview-Matrix wird wieder einmal zurückgesetzt
glTranslatef(0.0f,0.0f,-5.0f);
// Damit der Würfel auch zusehen ist,
// wird er um 5 Einheiten zurückgesetzt
Der Würfel soll sich auch drehen, das wird von den nächsten Zeilen
bewerkstelligt. Die jeweilligen Rotationswinkel werden am Ende der Prozedur
verändert.
glRotatef(xrot,1.0f,0.0f,0.0f); // Um die X Achse rotieren
glRotatef(yrot,0.0f,1.0f,0.0f); // Um die Y Achse rotieren
glRotatef(zrot,0.0f,0.0f,1.0f); // Um die Z Achse rotieren
Jetzt wird festgelegt welche Textur auf den Würfel gespannt werden soll.
Wieder muss übergeben werden das es sich um eine 2D-Textur handelt und
natürlich welche Textur benutzt werden soll. Die Textur muss immer außerhalb
von glBegin() und glEnd() eingebunden werden.
glBindTexture(GL_TEXTURE_2D, texture[0]);
Damit die Textur richtig herrum und im ganzen auf dem QUAD gelegt wird, muss
die jeweillige Koordinate mit der Ecke der Textur übereinstimmen. Die obere
linke Ecke der Textur muss also ganau auf die obere linke Ecke des QUAD gelegt
werden. (Das gleiche gilt natürlich auch für alle anderen Ecken und
Formen.)
Der erste Wert von glTexCoord2f ist die X Koordinate, der zweite die Y-Koordinate.
Die obere linke Ecke hätte also doe Koordinate (0.0f ,0.0f), die untere
rechte Ecke (1.0f, 1.0f).
Das am Anfang etwas gewöhnungsbedürftig, einige eigene Experimente
helfen bestimmt weiter. Hinter einer Texturkoordinate folgt dann die eigentliche
Ecke des QUADs.
glBegin(GL_QUADS);
// Das vordere QUAD
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// unten links an der Form und der Textur
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
// unten rechts an der Form und der Textur
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
// oben rechts an der Form und der Textur
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
// oben links an der Form und der Textur
// Das hintere QUAD
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
// unten rechts an der Form und der Textur
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
// oben rechts an der Form und der Textur
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
// oben links an der Form und der Textur
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
// unten links an der Form und der Textur
// Das obere QUAD
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
// oben links an der Form und der Textur
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
// unten links an der Form und der Textur
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
// unten rechts an der Form und der Textur
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
// oben rechts an der Form und der Textur
// Das untere QUAD
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
// oben rechts an der Form und der Textur
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
// oben links an der Form und der Textur
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
// unten links an der Form und der Textur
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// unten rechts an der Form und der Textur
// Das rechte QUAD
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
// unten rechts an der Form und der Textur
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
// oben rechts an der Form und der Textur
glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
// oben links an der Form und der Textur
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
// unten links an der Form und der Textur
// Das linke QUAD
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
// unten links an der Form und der Textur
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// unten rechts an der Form und der Textur
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
// oben rechts an der Form und der Textur
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
// oben links an der Form und der Textur
glEnd();
xrot+=0.4f; // X Achsen Rotation
yrot+=0.6f; // Y Achsen Rotation
zrot+=0.1f; // Z Achsen Rotation
return true; // Alles hat geklappt!
}
Die letzten drei Zeilen sind für die Erhöhung der einzelnen Drehwinkel
bei jedem Durchgang veranwortlich. Auch der Titel des Fensters sollte geändert
werden. (Wie immer zu finden in der WINAPI WinMain)
if (!CreateGLWindow("Opengl Tut. 5 - Teture Mapping - www.codeworx.org",
640,480,16,fullscreen))
{
return 0; // Falls ein Fehler auftrat, beenden
}
Auch ein Arbeitsbereich ist
wieder verfügbar. Weiter zur nächsten
Lektion.