www.codeworx.org/opengl-tutorials/Tutorial 6: Texture Mapping

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.