Tutorial 23: Sphere Mapping mit Quadratics
Sphere Enviroment Mapping ist ein recht schneller Weg um reflektierende Objekte
zu erzeugen (z.B. Metalloberflächen, Spiegel, Wasser usw.). Genutzt wird
der Code der 18. Lektion, dort ging es schon einmal um Quadratics. Neue Texturen
kommen hinzu, eine für die Sphere Map, die andere für den zu spiegelnden
Hintergrund.
Bevors losgeht, ein Blick ins Redbook: "Eine Sphere Map ist das Bild einer
Szene auf einem unendlich entfernten Metallball mit unendlichem Brennpunkt".
Das wird sich in der Realität kaum machen lassen, mit OpenGL aber (theoretisch)
kein Problem ;-)
Eine Sphere Map mit Fotoshop erstellen
Zuerst muss ein Bild der zu spiegelnden Szene, die Sphere Map also, her. Dazu
wird das Hintergrundbild in Photoshop kopiert und in eine neue Datei gleicher
Größe gespeichert (Hintergrundbild öffnen -> strg+a ->
strg+c -> strg+n -> enter -> strg+v -> strg+s). Sollte das Bild
nicht im RGB-Format sein, läßt sich das mit (Bild->Modus->RGB-Farbe)
umstellen (Jetzt sollten die meisten Filter funktionieren).
Das Bild muss anschließend zurechtgeschnitten werden. Erlaubt sind 2er-Potenzen,
also 64*64, 128*128, 256*256 Pixel usw. Hat das Bild z.B eine Höhe von
100 und eine Breite von 80 Pixeln, wird daraus 128*128. Dabei sollte nicht gespart
werden, sonst gehen möglicherweise wichtige Details verloren...
Zuletzt muss der Filter ( Filter->Verzerrungsfilter->Distorsion->Stärke:
-35 ) angewendet werden. Das irgendwie ballonartige Ergebnis wird als Bitmap
(*.bmp) gespeichert, hier heißt es "Reflect.bmp".
Also in den Code.
Es werden keine neuen globalen Variablen genutzt, allerdings sollen 6 Texturen
(2 Texturen * jeweils 3 Filter) verfügbar sein:
GLuinttexture[6]; // Storage For 6 Textures
int LoadGLTextures() // Load Bitmaps And Convert To Textures
{
int Status=FALSE; // Status Indicator
AUX_RGBImageRec *TextureImage[2]; // Create Storage Space For The Texture
memset(TextureImage,0,sizeof(void *)*2); // Set The Pointer To NULL
// Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
if ((TextureImage[0]=LoadBMP("Data/BG.bmp")) && // Background Texture
(TextureImage[1]=LoadBMP("Data/Reflect.bmp"))) // Reflection Texture (Spheremap)
{
Status=TRUE; // Set The Status To TRUE
glGenTextures(6, &texture[0]);// Create Three Textures
for (int loop=0; loop<=1; loop++)
{
// Create Nearest Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[loop]);// Gen Tex 0 And 1
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
// Create Linear Filtered Texture
glBindTexture(GL_TEXTURE_2D, texture[loop+2]);// Gen Tex 2, 3 And 4
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[loop]->sizeX, TextureImage[loop]->sizeY,
0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
// Create MipMapped Texture
glBindTexture(GL_TEXTURE_2D, texture[loop+4]);// Gen Tex 4 and 5
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
}
for (loop=0; loop<=1; loop++)
{
if (TextureImage[loop])// If Texture Exists
{
if (TextureImage[loop]->data)// If Texture Image Exists
{
free(TextureImage[loop]->data);// Free The Texture Image Memory
}
free(TextureImage[loop]);// Free The Image Structure
}
}
}
return Status;// Return The Status
}
Die Ausgabefunktion für den Würfel muss ein wenig umgeschrieben werden.
Normalenwerte wie 1.0f und -1.0f werden in 0.5f und -0.5f umgewandelt. Mit den
Werten läßt sich das reflektierte Bild ein- und auszoomen. Sind sie
zu groß (bzw klein) wirkt das Bild klotzig, an 0 angenäherte Werte
erzeugen auch eher unschöne Ergebnisse.
GLvoid glDrawCube()
{
glBegin(GL_QUADS);
// Front Face
glNormal3f( 0.0f, 0.0f, 0.5f);
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,-0.5f);
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, 0.5f, 0.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);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
// Bottom Face
glNormal3f( 0.0f,-0.5f, 0.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);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// Right Face
glNormal3f( 0.5f, 0.0f, 0.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);
glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
// Left Face
glNormal3f(-0.5f, 0.0f, 0.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);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd();
}
In InitGL kommen zwei neue Funktionsaufrufe dazu. Dabei wird der Modus der
Texturgenerierung von S und T auf Sphere Mapping gestellt. Die Texturkoordfinaten
S, T, R und Q sind mit den Objektkoordinaten x, y, z und w verwandt. Bei eindimensionalen
Texturen gibt es nur die S-Koordinate, bei zwei Dimensionen (hier) gibt es S
und T.
Der folgende Code bringt OpenGL dazu den Sphere Mapping Algorithmus für
S und T zu nutzen. R und Q werden ignoriert. Die Q-Koordinate wird bisher von
einigen GL-Extensions, also den Erweiterungen neuerer Grafikkarten genutzt,
R wird vielleicht irgendwann fürs 3D-Texturmapping interessant.
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); // Set The Texture Generation Mode For S To Sphere Mapping ( NEW )
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); // Set The Texture Generation Mode For T To Sphere Mapping ( NEW )
Es ist fast geschafft! Jetzt muss nur noch gerendert werden. Es sollen verschiedene
Quadratics zu sehen sein, einige fehlen allerdings weil sie Fehler verursacht
haben. Die Texturgenerierung wird aktiviert und die Sphere Map, also die eben
erstellte Textur ausgewählt. Dann wird das gewünschte Objekt (je nachdem
welchen Wert "object" hat) angezeigt. Das Sphere Mapping wird jetzt
wieder deaktiviert damit folgende Objekte mit "normalen" Texturn überzogen
werden. Das Texturbinden mag etwas umfangreich erscheinen, sorgt aber nur dafür
das Textur und Filter richtig ausgewählt werden.
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
glTranslatef(0.0f,0.0f,z);
glEnable(GL_TEXTURE_GEN_S); // Enable Texture Coord Generation For S ( NEW )
glEnable(GL_TEXTURE_GEN_T); // Enable Texture Coord Generation For T ( NEW )
glBindTexture(GL_TEXTURE_2D, texture[filter+(filter+1)]);
// This Will Select A Sphere Map
glPushMatrix();
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
switch(object)
{
case 0:
glDrawCube();
break;
case 1:
glTranslatef(0.0f,0.0f,-1.5f);// Center The Cylinder
gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32);// A Cylinder With A Radius Of 0.5 And A Height Of 2
break;
case 2:
gluSphere(quadratic,1.3f,32,32);// Sphere With A Radius Of 1 And 16 Longitude/Latitude Segments
break;
case 3:
glTranslatef(0.0f,0.0f,-1.5f);// Center The Cone
gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32);// Cone With A Bottom Radius Of .5 And Height Of 2
break;
};
glPopMatrix();
glDisable(GL_TEXTURE_GEN_S);// Disable Texture Coord Generation ( NEW )
glDisable(GL_TEXTURE_GEN_T);// Disable Texture Coord Generation ( NEW )
glBindTexture(GL_TEXTURE_2D, texture[filter*2]);// This Will Select The BG Texture ( NEW )
glPushMatrix();
glTranslatef(0.0f, 0.0f, -24.0f);
glBegin(GL_QUADS);
glNormal3f( 0.0f, 0.0f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-13.3f, -10.0f, 10.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 13.3f, -10.0f, 10.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 13.3f, 10.0f, 10.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-13.3f, 10.0f, 10.0f);
glEnd();
glPopMatrix();
xrot+=xspeed;
yrot+=yspeed;
return TRUE;// Keep Going
}
Wenn die Leertaste gedrückt wird und im Frame davor noch nicht gedrückt
war, soll das nächste Objekt ausgewählt werden.
if (keys[' '] && !sp)
{
sp=TRUE;
object++;
if(object>3)
object=0;
}
Das wars. Mit Enviroment Mapping lassen sich wirklich viele sehr schöne
Effekte erzeugen und auf neuerer Hardware sollten kaum Engpässe auftreten.
Danke und Viel Spaß!
GB Schmick (TipTup) http://www.tiptup.com
Jeff Molofee (NeHe) http://nehe.gamedev.net
Die Source Codes und Ausführbaren Dateien zu den Kursen liegen auf der Neon
Helium Website
Übersetzt und modifiziert von Hans-Jakob Schwer 05.08.2k3, www.codeworx.org