www.codeworx.org/opengl-tutorials/Tutorial 23: Sphere Mapping mit Quadratics

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