|
|
|
|
|
After
posting the last two tutorials on bitmap and outlined fonts, I received
quite a few emails from people wondering how they could texture map the
fonts. You can use autotexture coordinate generation. This will generate
texture coordinates for each of the polygons on the font.
A small note, this code
is Windows specific. It uses the wgl functions of Windows to build the
font. Apparently Apple has agl support that should do the same thing, and
X has glx. Unfortunately I can't guarantee this code is portable. If anyone
has platform independant code to draw fonts to the screen, send it my way
and I'll write another font tutorial.
We'll build our Texture
Font demo using the code from lesson 14. If any of the code has changed
in a particular section of the program, I'll rewrite the entire section
of code so that it's easier to see the changes that I have made.
The following section
of code is similar to the code in lesson 14, but this time we're not going
to include the stdarg.h file. |
|
|
|
|
#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
|
|
|
|
|
We're
going to add one new integer variable here called texture[ ]. It
will be used to store our texture. The last three lines were in tutorial
14 and have not changed in this tutorial. |
|
|
|
|
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
|
|
|
|
|
The
following section of code has some minor changes. In this tutorial I'm
going to use the wingdings font to display a skull and crossbones type
object. If you want to display text instead, you can leave the code the
same as it was in lesson 14, or change to a font of your own.
A few of you were wondering
how to use the wingdings font, which is another reason I'm not using a
standard font. Wingdings is a SYMBOL font, and requires a few changes to
make it work. It's not as easy as telling Windows to use the wingdings
font. If you change the font name to wingdings, you'll notice that the
font doesn't get selected. You have to tell Windows that the font is a
symbol font and not a standard character font. More on this later. |
|
|
|
|
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
|
|
|
|
|
This
is the magic line! Instead of using ANSI_CHARSET like we did in tutorial
14, we're going to use SYMBOL_CHARSET. This tells Windows that the font
we are building is not your typical font made up of characters. A symbol
font is usually made up of tiny pictures (symbols). If you forget to change
this line, wingdings, webdings and any other symbol font you may be trying
to use will not work. |
|
|
|
|
SYMBOL_CHARSET, // Character Set Identifier ( Modified )
|
|
|
|
|
The
next few lines have not changed. |
|
|
|
|
OUT_TT_PRECIS, // Output Precision
CLIP_DEFAULT_PRECIS, // Clipping Precision
ANTIALIASED_QUALITY, // Output Quality
FF_DONTCARE|DEFAULT_PITCH, // Family And Pitch
|
|
|
|
|
Now
that we've selected the symbol character set identifier, we can select
the wingdings font! |
|
|
|
|
"Wingdings"); // Font Name ( Modified )
|
|
|
|
|
The
remaining lines of code have not changed. |
|
|
|
|
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
|
|
|
|
|
I'm
allowing for more deviation. This means GL will not try to follow the outline
of the font as closely. If you set deviation to 0.0f, you'll notice problems
with the texturing on really curved surfaces. If you allow for some deviation,
most of the problems will disappear. |
|
|
|
|
0.1f, // Deviation From The True Outlines
|
|
|
|
|
The
next three lines of code are still the same. |
|
|
|
|
0.2f, // Font Thickness In The Z Direction
WGL_FONT_POLYGONS, // Use Polygons, Not Lines
gmf); // Address Of Buffer To Recieve Data
}
|
|
|
|
|
Right
before ReSizeGLScene() we're going to add the following section of code
to load our texture. You might recognize the code from previous tutorials.
We create storage for the bitmap image. We load the bitmap image. We tell
OpenGL to generate 1 texture, and we store this texture in texture[0].
I'm creating a mipmapped
texture only because it looks better. The name of the texture is lights.bmp. |
|
|
|
|
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);
|
|
|
|
|
The
next four lines of code will automatically generate texture coordinates
for any object we draw to the screen. The glTexGen command is extremely
powerful, and complex, and to get into all the math involved would be a
tutorial on it's own. All you need to know is that GL_S and GL_T are texture
coordinates. By default they are set up to take the current x location
on the screen and the current y location on the screen and come up with
a texture vertex. You'll notice the objects are not textured on the z plane...
just stripes appear. The front and back faces are textured though, and
that's all that matters. X (GL_S) will cover mapping the texture left to
right, and Y (GL_T) will cover mapping the texture up and down.
GL_TEXTURE_GEN_MODE lets
us select the mode of texture mapping we want to use on the S and T texture
coordinates. You have 3 choices:
GL_EYE_LINEAR - The texture
is fixed to the screen. It never moves. The object is mapped with whatever
section of the texture it is passing over.
GL_OBJECT_LINEAR - This
is the mode we are using. The texture is fixed to the object moving around
the screen.
GL_SPHERE_MAP - Everyones
favorite. Creates a metalic reflective type object.
It's important to note
that I'm leaving out alot of code. We should be setting the GL_OBJECT_PLANE
as well, but by default it's set to the parameters we want. Buy a good
book if you're interested in learning more, or check out the MSDN help
CD / DVD. |
|
|
|
|
// 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
}
|
|
|
|
|
There
are a few new lines at the end of the InitGL() code. BuildFont() has been
moved underneath our texture loading code. The line glEnable(GL_COLOR_MATERIAL)
has been removed. If you plan to apply colors to the texture using glColor3f(r,g,b)
add the line glEnable(GL_COLOR_MATERIAL) back into this section of code. |
|
|
|
|
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
|
|
|
|
|
Enable
2D Texture Mapping, and select texture one. This will map texture one onto
any 3D object we draw to the screen. If you want more control, you can
enable and disable texture mapping yourself. |
|
|
|
|
glEnable(GL_TEXTURE_2D); // Enable Texture Mapping
glBindTexture(GL_TEXTURE_2D, texture[0]); // Select The Texture
return TRUE; // Initialization Went OK
}
|
|
|
|
|
The
resize code hasn't changed, but our DrawGLScene code has. |
|
|
|
|
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
|
|
|
|
|
Here's
our first change. Instead of keeping the object in the middle of the screen,
we're going to spin it around the screen using COS and SIN (no surprise).
We'll translate 3 units into the screen (-3.0f). On the x axis, we'll swing
from -1.1 at far left to +1.1 at the right. We'll be using the rot variable
to control the left right swing. We'll swing from +0.8 at top to -0.8 at
the bottom. We'll use the rot variable for this swinging motion as well.
(might as well make good use of your variables). |
|
|
|
|
// Position The Text
glTranslatef(1.1f*float(cos(rot/16.0f)),0.8f*float(sin(rot/20.0f)),-3.0f);
|
|
|
|
|
Now
we do the normal rotations. This will cause the symbol to spin on the X,
Y and Z axis. |
|
|
|
|
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
|
|
|
|
|
We
translate a little to the left, down, and towards the viewer to center
the symbol on each axis. Otherwise when it spins it doesn't look like it's
spinning around it's own center. -0.35 is just a number that worked. I
had to play around with numbers for a bit because I'm not sure how wide
the font is, could vary with each font. Why the fonts aren't built around
a central point I'm not sure. |
|
|
|
|
glTranslatef(-0.35f,-0.35f,0.1f); // Center On X, Y, Z Axis
|
|
|
|
|
Finally
we draw our skull and crossbones symbol then increase the rot variable
so our symbol spins and moves around the screen. If you can't figure out
how I get a skull and crossbones from the letter 'N', do this: Run Microsoft
Word or Wordpad. Go to the fonts drop down menu. Select the Wingdings font.
Type and uppercase 'N'. A skull and crossbones appears. |
|
|
|
|
glPrint("N"); // Draw A Skull And Crossbones Symbol
rot+=0.1f; // Increase The Rotation Variable
return TRUE; // Keep Going
}
|
|
|
|
|
The
last thing to do is add KillFont() to the end of KillGLWindow() just like
I'm showing below. It's important to add this line. It cleans things up
before we exit our program. |
|
|
|
|
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
}