www.codeworx.org/opengl-tutorials/Tutorial 15: 3D-Fonts mit Texturen

Lektion 15: 3D-Fonts mit Texturen

Hallo. In diesem fünfzehnten Tutorial wird es darum gehen, mit Hilfe des im letzten Teil besprochenen codes und einer interssanten Funktion, die uns die passenden Texturkoordinaten generiert, einen 3D-Font mit einer geladenen Textur zu "überziehen".

Bei den includes bleibt fast alles beim alten, nur wird diesmal stdarg.h nicht benötigt.

#include <windows.h>  // Header File For Windows
#include <math.h>     // Header File For Windows Math Library
#include <stdio.h>    // Header File For Standard Input/Output
#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
HDC hDC=NULL;         // Private GDI Device Context
HGLRC hRC=NULL;       // Permanent Rendering Context
HWND hWnd=NULL;       // Holds Our Window Handle
HINSTANCE hInstance;  // Holds The Instance Of The Application
bool keys[256];       // Array Used For The Keyboard Routine
bool active=TRUE;     // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default

Wir brauchen einen Integer um die Texturspäter ansprechen zu können

GLuint texture[1];   // One Texture Map ( NEW )
GLuint base;         // Base Display List For The Font Set
GLfloat rot;         // Used To Rotate The Text
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 
// Declaration For WndProc

Im folgenden gibt es einige kleinere Änderungen. Es soll ein Totenkopf mit gekreutzten Knochen (Wingdings, Buchstabe "N") ausgegeben werden. Wingdings ist kein Standardfont, es muss also SYMBOL_CHARSET genutzt werden (Alles andere würde nicht richtig funktionieren.).

GLvoid BuildFont(GLvoid)       // Build Our Bitmap Font
{
   GLYPHMETRICSFLOAT gmf[256]; // Address Buffer For Font Storage
   HFONT font;                 // Windows Font ID
   base = glGenLists(256);     // Storage For 256 Characters
   font = CreateFont( -12,     // Height Of Font
   0,                          // Width Of Font
   0,                          // Angle Of Escapement
   0,                          // Orientation Angle
   FW_BOLD,                    // Font Weight
   FALSE,                      // Italic
   FALSE,                      // Underline
   FALSE,                      // Strikeout
   SYMBOL_CHARSET,             // Character Set Identifier ( Modified )
   OUT_TT_PRECIS,              // Output Precision
   CLIP_DEFAULT_PRECIS,        // Clipping Precision
   ANTIALIASED_QUALITY,        // Output Quality
   FF_DONTCARE|DEFAULT_PITCH,  // Family And Pitch


Da wir die Schriftart als SYMBOL klassifiziert haben, läßt ich jetzt guten Gewissens Wingdings auswählen.

   "Wingdings");                // Font Name ( Modified )
   SelectObject(hDC, font);     // Selects The Font We Created
   wglUseFontOutlines( hDC,     // Select The Current DC
0, // Starting Character
255, // Number Of Display Lists To Build
base, // Starting Display Lists

Um Fehler zu vermeiden muss OpenGL eine leichte Abweichung von den Originaldaten des Fonts erlaubt werden. 0.1f sieht aber immernoch sehr gut aus. Der Rest ändert sich nicht.

   0.1f,                        // Deviation From The True Outlines
   0.2f,                        // Font Thickness In The Z Direction
   WGL_FONT_POLYGONS,           // Use Polygons, Not Lines
   gmf);                        // Address Of Buffer To Recieve Data
}

Vor ReSizeGLScene() müssen noch die schon aus den vorhergehenden Tuts bekannten Funktionen zum Laden der Textur übernommen werden.

Die textur heißt "lights.bmp" und wird in texture[0] geladen.

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
}
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
   if (TextureImage[0]=LoadBMP("Data/Lights.bmp")) // Load The Bitmap
   {
      Status=TRUE; // Set The Status To TRUE

      glGenTextures(1, &texture[0]); // Create The Texture
      
      // Build Linear Mipmapped Texture
      glBindTexture(GL_TEXTURE_2D, texture[0]);
      gluBuild2DMipmaps(GL_TEXTURE_2D, 
                        3, 
                        TextureImage[0]->sizeX, 
                        TextureImage[0]->sizeY,    
                        GL_RGB, 
                        GL_UNSIGNED_BYTE, 
                        TextureImage[0]->data);

      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

Die nächsten Zeilen generieren automatisch Texturkoordinaten für Objekte die ausgegeben werden sollen. Das glTexGen-Kommando ist so komplex das es den Rahmen dieses Tuts sprengen würde. Wichtig sind GL_S und GL_T, beide Texturkoordinaten. Normalerweise enthalten sie die aus den aktuellen X- und Y-Positionen generierten Texturkoordinaten.

GL_TEXTURE_GEN_MODE - Legt den Modus des Texturmappings fest, dabei gibt es drei Möglichkeiten:

GL_EYE_LINEAR - Die Textur ist an den Hintergrund gebunden, bewegt sich also nicht mit wenn sich das Objekt bewegt.

GL_OBJECT_LINEAR - Dieser Modus wird diesmal genutzt, die Textur ist auf dem sich bewegenden Objekt fixiert.

GL_SPHERE_MAP - Ein interessanter Effekt, am besten selbst ausprobieren ;)

Mehr dazu gibt es in der MSDN.

      // Texturing Contour Anchored To The Object
      glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
      // Texturing Contour Anchored To The Object
      glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
      glEnable(GL_TEXTURE_GEN_S); // Auto Texture Generation
      glEnable(GL_TEXTURE_GEN_T); // Auto Texture Generation
   }
   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
   }

   return Status;                     // Return The Status
}

InitGL() ist um ein paar Zeichen reicher geworden. BuildFont() ist hinter die Funktion zum Texturladen gerückt. glEnable(GL_COLOR_MATERIAL) fehlt jetzt, sollte man später glColor3f(r,g,b) nutzen wollen, muss die Zeile natürlich wieder eingefügt werden.

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
   }

   BuildFont(); // Build The Font
   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
   glEnable(GL_LIGHT0);  // Quick And Dirty Lighting (Assumes Light0 Is Set Up)
   glEnable(GL_LIGHTING);                // Enable Lighting
   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); 
   // Really Nice Perspective Calculations

2D Texturmapping wird aktiviert und die Textur ausgwählt. Die Textur wird von dort an auf jedes ausgegebene Objekt gemappt.

   glEnable(GL_TEXTURE_2D); // Enable Texture Mapping
   glBindTexture(GL_TEXTURE_2D, texture[0]); // Select The Texture
   return TRUE; // Initialization Went OK
}

DrawGLScene hat sich natülich auch noch ein wenig verändert.

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

Wieder wird das Objekt mithilfe des Cosinus und des Sinus bewegt.

   // Position The Text
   glTranslatef(1.1f*float(cos(rot/16.0f)),0.8f*float(sin(rot/20.0f)),-3.0f);

Jetzt die Rotation um die drei Achsen.

   glRotatef(rot,1.0f,0.0f,0.0f);      // Rotate On The X Axis
   glRotatef(rot*1.2f,0.0f,1.0f,0.0f); // Rotate On The Y Axis
   glRotatef(rot*1.4f,0.0f,0.0f,1.0f); // Rotate On The Z Axis


Das ganze muss in die Mitte gerückt werden:

   glTranslatef(-0.35f,-0.35f,0.1f);   // Center On X, Y, Z Axis

Und hier jetzt endlich die Ausgabe des N's (Dem Totenschädel in Wingdings.). rot wird erhöht um das Objekt zu bewegen.

   glPrint("N"); // Draw A Skull And Crossbones Symbol
   rot+=0.1f;    // Increase The Rotation Variable
   return TRUE;  // Keep Going
}

Und jetzt noch die Aufräumarbeiten am Ende von KillGLWindow():

   if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class
   {
       MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK    | MB_ICONINFORMATION);
       hInstance=NULL; // Set hInstance To NULL
   }
   KillFont(); // Destroy The Font
}


Es sollte jetzt keine größeren Probleme mehr mit dem generieren von Texturen geben und der Ausgabe von Fonts generell geben. Viel Spaß beim coden:
nehe.

Die Source Codes und Ausführbaren Dateien zu den Kursen liegen auf der Neon Helium Website

Übersetzt und leicht modifiziert von Hans-Jakob Schwer 10.09.2k2, www.codeworx.org