Luckily it is fairly easy to make better looking fonts using the GNU FreeType library. FreeType is the same library that Blizzard uses to render the fonts in their games, so you know it has to be good!
Here's a close up of the text that I've created with the help of the FreeType library:
Now we need to setup MSVC to use FreeType. So create new project as in lesson 1 , but when you go to Project->Settings->Link add libfreetype.lib to Object Modules / libraries along with opengl32.lib, glu32.lib and glaux.lib.
Next we need to go to Tools->Options->Directories and add the FreeType library directories. Under "Show Directories For" select "Include Files", then double click on the empty line at the bottom of the directory list, after you double click a "..." button will appear that you can use to browse for a directory. In this way add
C:\PROGRAM FILES\GNUWIN32\INCLUDE\FREETYPE2
C:\PROGRAM FILES\GNUWIN32\INCLUDE
Now under "Show Directories For" select "Library Files", and this time add
C:\PROGRAM FILES\GNUWIN32\LIB
At this point we should be able to compile programs using FreeType, but they won't run unless they can access the freetype-6.dll. Right now you have a copy of that DLL file in your GNUWIN32\BIN directory, and if you put it somewhere where all your programs can see it (Program Files\Microsoft Visual Studio\VC98\Bin is a good choice), you will be able to run programs using FreeType. But remember that if you distribute a program that you have made using FreeType, you will need to also distribute copies of the DLL along with it.
Ok, now we can finally start writing code. I've decided to work off the lesson13 source, so grab a copy of lesson 13 if you don't have one already. Copy lesson13.cpp to your project directory, and add the file to the project.
Now add and create two new files called "freetype.cpp" and "freetype.h". We will put all our FreeType specific code into these files, and then modify lesson13 a little to show off the functions that we've written. When we are done, we will have created a very simple OpenGL FreeType library that could theoretically be used in any OpenGL project.
We will start with freetype.h.
Naturally, we need include the FreeType and OpenGL headers. We will also include some handy parts of the Standard Template Library, including STL's exception classes, which will make it easier for us create nice debugging messages.
#ifndef FREE_NEHE_H #define FREE_NEHE_H //FreeType Headers #include <ft2build.h> #include <freetype/freetype.h> #include <freetype/ftglyph.h> #include <freetype/ftoutln.h> #include <freetype/fttrigon.h> //OpenGL Headers #include <windows.h> //(the GL headers need it) #include <GL/gl.h> #include <GL/glu.h> //Some STL headers #include <vector> #include <string> //Using the STL exception library increases the //chances that someone else using our code will correctly //catch any exceptions that we throw. #include <stdexcept> //MSVC will spit out all sorts of useless warnings if //you create vectors of strings, this pragma gets rid of them. #pragma warning(disable: 4786)
We will put all the information that each font needs into one structure (this will make managing multiple fonts a little easier). As we learned in lesson13, when WGL creates a font, it generates a set of consecutive display lists. This is nifty, because it means that you can use glCallLists to print out a string of characters with just one command. When we create our font we will set things up the same way, which means that the list_base field will store the first of 128 consecutive display lists. Because we are going to use textures to draw our text, so we will also need storage for the 128 associated textures. The last bit of info that we will need is the height, in pixels, of the font that we have created (this will make it possible to handle newlines in our print function).
///Wrap everything in a namespace, that way can use common ///function names like "print" without worrying about ///overlapping with anyone else's code. namespace freetype { //Inside of this namespace, give ourselves the ability //to write just "vector" instead of "std::vector" using std::vector; //Ditto for string. using std::string; //This holds all of the information related to any //freetype font that we want to create. struct font_data { float h; ///< Holds the height of the font. GLuint * textures; ///< Holds the texture id's GLuint list_base; ///< Holds the first display list id ///The init function will create a font of ///of the height h from the file fname. void init(const char * fname, unsigned int h); ///Free all the resources associated with the font. void clean(); };
The last thing we need is a prototype for our print function:
///The flagship function of the library - this thing will print ///out text at window coordinates x,y, using the font ft_font. ///The current modelview matrix will also be applied to the text. void print(const font_data &ft_font, float x, float y, const char *fmt, ...) ; } //close the namespace #endif
And that's the end of the header file! Time to open up freetype.cpp.
//Include our header file. #include "freetype.h" namespace freetype {
We are using textures to display each character in our font. OpenGL textures need to have dimensions that are powers of two, so we need to pad the font bitmaps created by FreeType to make them a legal size. That's why we need this function:
///This function gets the first power of 2 >= the ///int that we pass it. inline int next_p2 (int a ) { int rval=1; // rval<<=1 is a prettier way of writing rval*=2; while(rval<a) rval<<=1; return rval; }
The next function that we need is make_dlist, it is really the heart of this code. It takes in an FT_Face, which is an object that FreeType uses to store information about a font, and creates a display list coresponding to the character which we pass in.
///Create a display list corresponding to the give character. void make_dlist ( FT_Face face, char ch, GLuint list_base, GLuint * tex_base ) { //The first thing we do is get FreeType to render our character //into a bitmap. This actually requires a couple of FreeType commands: //Load the Glyph for our character. if(FT_Load_Glyph( face, FT_Get_Char_Index( face, ch ), FT_LOAD_DEFAULT )) throw std::runtime_error("FT_Load_Glyph failed"); //Move the face's glyph into a Glyph object. FT_Glyph glyph; if(FT_Get_Glyph( face->glyph, &glyph )) throw std::runtime_error("FT_Get_Glyph failed"); //Convert the glyph to a bitmap. FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 ); FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph; //This reference will make accessing the bitmap easier FT_Bitmap& bitmap=bitmap_glyph->bitmap;
Now that we have a bitmap created by Freetype, we need to pad it with empty pixels to make it a legal source for an OpenGL texture. It's important to remember that while OpenGL uses the term "bitmap" to mean binary images, in FreeType bitmaps store 8 bits of information per pixel, so FreeType's bitmaps can store the grays that we need to create anti-aliased text.
//Use our helper function to get the widths of //the bitmap data that we will need in order to create //our texture. int width = next_p2( bitmap.width ); int height = next_p2( bitmap.rows ); //Allocate memory for the texture data. GLubyte* expanded_data = new GLubyte[ 2 * width * height]; //Here we fill in the data for the expanded bitmap. //Notice that we are using a two channel bitmap (one for //channel luminosity and one for alpha), but we assign //both luminosity and alpha to the value that we //find in the FreeType bitmap. //We use the ?: operator to say that value which we use //will be 0 if we are in the padding zone, and whatever //is the the Freetype bitmap otherwise. for(int j=0; j <height;j++) { for(int i=0; i < width; i++){ expanded_data[2*(i+j*width)]= expanded_data[2*(i+j*width)+1] = (i>=bitmap.width || j>=bitmap.rows) ? 0 : bitmap.buffer[i + bitmap.width*j]; } }
With the padding done, we can get onto creating the OpenGL texture. We are including an alpha channel so that the black parts of the bitmap will be transparent, and so that the edges of the text will be slightly translucent (which should make them look right against any background).
//Now we just setup some texture parameters. glBindTexture( GL_TEXTURE_2D, tex_base[ch]); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); //Here we actually create the texture itself, notice //that we are using GL_LUMINANCE_ALPHA to indicate that //we are using 2 channel data. glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data ); //With the texture created, we don't need the expanded data anymore delete [] expanded_data;
We use texture mapped quads to draw our text. This means that it will be easy to rotate and scale text, and it will also make fonts inherit their color from the current OpenGL color (none of which would be true if we used pixmaps).
//Now we create the display list glNewList(list_base+ch,GL_COMPILE); glBindTexture(GL_TEXTURE_2D,tex_base[ch]); //first we need to move over a little so that //the character has the right amount of space //between it and the one before it. glPushMatrix(); glTranslatef(bitmap_glyph->left,0,0); //Now we move down a little in the case that the //bitmap extends past the bottom of the line //(this is only true for characters like 'g' or 'y'. glTranslatef(0,bitmap_glyph->top-bitmap.rows,0); //Now we need to account for the fact that many of //our textures are filled with empty padding space. //We figure what portion of the texture is used by //the actual character and store that information in //the x and y variables, then when we draw the //quad, we will only reference the parts of the texture //that we contain the character itself. float x=(float)bitmap.width / (float)width, y=(float)bitmap.rows / (float)height; //Here we draw the texturemaped quads. //The bitmap that we got from FreeType was not //oriented quite like we would like it to be, //but we link the texture to the quad //in such a way that the result will be properly aligned. glBegin(GL_QUADS); glTexCoord2d(0,0); glVertex2f(0,bitmap.rows); glTexCoord2d(0,y); glVertex2f(0,0); glTexCoord2d(x,y); glVertex2f(bitmap.width,0); glTexCoord2d(x,0); glVertex2f(bitmap.width,bitmap.rows); glEnd(); glPopMatrix(); glTranslatef(face->glyph->advance.x >> 6 ,0,0); //increment the raster position as if we were a bitmap font. //(only needed if you want to calculate text length) //glBitmap(0,0,0,0,face->glyph->advance.x >> 6,0,NULL); //Finnish the display list glEndList(); }
The next function that we are going to create will use make_dlist to create a set of display lists corresponding to a given font file and pixel height.
FreeType uses truetype fonts, so you will want to find yourself some truetype font files to feed into this function. Truetype font files are very common, and there are a bunch of sites out there where you can download lots of different truetype fonts for free. Windows98 used truetype for nearly all of it's fonts, so if you can find an old computer running windows98, you can get all of the standard fonts in truetype format from its windows/fonts directory.
void font_data::init(const char * fname, unsigned int h) { //Allocate some memory to store the texture ids. textures = new GLuint[128]; this->h=h; //Create and initilize a freetype font library. FT_Library library; if (FT_Init_FreeType( &library )) throw std::runtime_error("FT_Init_FreeType failed"); //The object in which Freetype holds information on a given //font is called a "face". FT_Face face; //This is where we load in the font information from the file. //Of all the places where the code might die, this is the most likely, //as FT_New_Face will fail if the font file does not exist or is somehow broken. if (FT_New_Face( library, fname, 0, &face )) throw std::runtime_error("FT_New_Face failed (there is probably a problem with your font file)"); //For some twisted reason, Freetype measures font size //in terms of 1/64ths of pixels. Thus, to make a font //h pixels high, we need to request a size of h*64. //(h << 6 is just a prettier way of writing h*64) FT_Set_Char_Size( face, h << 6, h << 6, 96, 96); //Here we ask opengl to allocate resources for //all the textures and displays lists which we //are about to create. list_base=glGenLists(128); glGenTextures( 128, textures ); //This is where we actually create each of the fonts display lists. for(unsigned char i=0;i<128;i++) make_dlist(face,i,list_base,textures); //We don't need the face information now that the display //lists have been created, so we free the assosiated resources. FT_Done_Face(face); //Ditto for the font library. FT_Done_FreeType(library); }
Now we need a function to cleanup all the displaylist and textures associated with a font.
void font_data::clean() { glDeleteLists(list_base,128); glDeleteTextures(128,textures); delete [] textures; }
Here are two little functions that we are going to define in anticipation of our print function. The print function is going to want to think in pixel coordinates (also called window coordinates), so we are going to need to switch to a projection matrix that makes everything measured in pixel coordinates.
We are using two very handy OpenGL functions here, glGet to get the window dimensions, and glPush/PopAttrib to make sure that leave the matrix mode in the same state as we found it. If you are not familiar with these functions, it's probably worth your time to look them up in your favorite OpenGL reference manual.
/// A fairly straightforward function that pushes /// a projection matrix that will make object world /// coordinates identical to window coordinates. inline void pushScreenCoordinateMatrix() { glPushAttrib(GL_TRANSFORM_BIT); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]); glPopAttrib(); } /// Pops the projection matrix without changing the current /// MatrixMode. inline void pop_projection_matrix() { glPushAttrib(GL_TRANSFORM_BIT); glMatrixMode(GL_PROJECTION); glPopMatrix(); glPopAttrib(); }
Our printing function looks very much like the one from lesson 13, but there are a couple of important differences. The OpenGL enable flags that we set are different, which reflects the fact that we are actually using 2 channel textures rather than bitmaps. We also do a little extra processing on the line of text that we get in order to properly handle newlines. Because we are such good samaritans, we take care to use OpenGL's matrix and attribute stacks to ensure that the function undoes any changes that it makes to OpenGL's internal state (this will prevent anyone using the function from suddenly finding that, say, the modelview matrix had mysteriously changed).
///Much like Nehe's glPrint function, but modified to work ///with freetype fonts. void print(const font_data &ft_font, float x, float y, const char *fmt, ...) { // We want a coordinate system where distance is measured in window pixels. pushScreenCoordinateMatrix(); GLuint font=ft_font.list_base; //We make the height a little bigger there will be some space between lines. float h=ft_font.h/.63f; char text[256]; // Holds Our String va_list ap; // Pointer To List Of Arguments if (fmt == NULL) // If There's No Text *text=0; // Do Nothing else { va_start(ap, fmt); // Parses The String For Variables vsprintf(text, fmt, ap); // And Converts Symbols To Actual Numbers va_end(ap); // Results Are Stored In Text } //Here is some code to split the text that we have been //given into a set of lines. //This could be made much neater by using //a regular expression library such as the one available from //boost.org (I've only done it out by hand to avoid complicating //this tutorial with unnecessary library dependencies). const char *start_line=text; vector<string> lines; for(const char *c=text;*c;c++) { if(*c=='\n') { string line; for(const char *n=start_line;n<c;n++) line.append(1,*n); lines.push_back(line); start_line=c+1; } } if(start_line) { string line; for(const char *n=start_line;n<c;n++) line.append(1,*n); lines.push_back(line); } glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TRANSFORM_BIT); glMatrixMode(GL_MODELVIEW); glDisable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glListBase(font);
Because we are using texture mapped quads, any transformations that we apply to the modelview matrix before making our glCallLists call will apply to the text itself. This means that there is the potential to rotate or scale the text (another advantage over using WGL bitmaps). The most natural way to take advantage of this fact would be to leave the current modelview matrix alone, thus letting any transformation made before the print function apply to the text. But because of the way that we are using the modelview matrix to set font position, this won't work. Our next best option is to save a copy of the modelview matrix that is passed in, and apply it between the glTranslate and the glCallLists. This is easy enough to do, but because we need to draw the text using a special projection matrix the effects of the modelview matrix will be a little different than one might expect- everything will be will be interpreted on scale of pixels. We could get around this issue entirely by not resetting the projection matrix inside of print. This is probably a good idea in some situations - but if you try it make sure to scale the fonts to an appropriate size (they tend to be something like 32x32, and you probably want something on the order of 0.01x0.01).
float modelview_matrix[16]; glGetFloatv(GL_MODELVIEW_MATRIX, modelview_matrix); //This is where the text display actually happens. //For each line of text we reset the modelview matrix //so that the line's text will start in the correct position. //Notice that we need to reset the matrix, rather than just translating //down by h. This is because when each character is //draw it modifies the current matrix so that the next character //will be drawn immediately after it. for(int i=0;i<lines.size();i++) { glPushMatrix(); glLoadIdentity(); glTranslatef(x,y-h*i,0); glMultMatrixf(modelview_matrix); // The commented out raster position stuff can be useful if you need to // know the length of the text that you are creating. // If you decide to use it make sure to also uncomment the glBitmap command // in make_dlist(). // glRasterPos2f(0,0); glCallLists(lines[i].length(), GL_UNSIGNED_BYTE, lines[i].c_str()); // float rpos[4]; // glGetFloatv(GL_CURRENT_RASTER_POSITION ,rpos); // float len=x-rpos[0]; (assuming no rotations have happend) glPopMatrix(); } glPopAttrib(); pop_projection_matrix(); } } //close the namespace
The library is now finished. Open up lesson13.cpp and we will make some minor modifications to show off the functions we just wrote.
Underneth the other headers, add in the freetype.h header.
#include "freetype.h" // Header for our little font library.
And while we are here, let's create a global font_data object.
// This holds all the information for the font that we are going to create.
freetype::font_data our_font;
Now we need to see about creating and destroying the resources for our font. So add the following line to the end of InitGL
our_font.init("test.TTF", 16); //Build the freetype font
And add this line to the start of KillGLWindow to destroy the font when we are finished.
our_font.clean();
Now we need to modify the function DrawGLScene function so that it uses our print function. This could have been as simple to adding a single line "hello world" command to the end of the function, but I got a little more creative because I wanted to show off rotations and scaling.
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer glLoadIdentity(); // Reset The Current Modelview Matrix glTranslatef(0.0f,0.0f,-1.0f); // Move One Unit Into The Screen // Blue text glColor3ub(0,0,0xff); // Position the WGL Text On The Screen glRasterPos2f(-0.37, .35); glPrint("Active WGL Bitmap Text With NeHe - %7.2f", cnt1); // Print GL Text To The Screen //Here we print some text using our freetype font //The only really important command is the actual print() call, //but for the sake of making the results a bit more interesting //I have put in some code to rotate and scale the text. glPushMatrix(); glLoadIdentity(); glRotatef(cnt1,0,0,1); glScalef(1,.8+.3*cos(cnt1),1); glTranslatef(-217,0,0); freetype::print(our_font, 320,200,"Active Freetype Text With NeHe - %7.2f", cnt1); glPopMatrix(); //Uncomment this to test out print's ability to handle newlines. //freetype::print(our_font, 320,200,"Here\nthere\nbe\n\nnewlines\n.", cnt1); cnt1+=0.051f; // Increase The First Counter cnt2+=0.005f; // Increase The First Counter return TRUE; // Everything Went OK }
The last thing to do is put in a little exception handling code. Go to WinMain and open up a try { .. } statement at the beginning of the function.
MSG msg; // Windows Message Structure BOOL done=FALSE; // Bool Variable To Exit Loop try { // Use exception handling
Then modify the end of the function to have a catch {} command.
// Shutdown KillGLWindow(); // Kill The Window //catch any exceptions that where thrown } catch (std::exception &e) { MessageBox(NULL,e.what(),"CAUGHT AN EXCEPTION",MB_OK | MB_ICONINFORMATION); } return (msg.wParam); // Exit The Program }
Now if we ever hit an exception, we will get a little message box telling us what happened. Note that exception handling can slow down your code, so when you are compiling a release version of your program, you may want to go to Project->Settings->C/C++, switch to the "C++ Language" category, and turn off exception handling.
So that's it! Compile the program and you should see some nice FreeType rendered text moving around underneath the original bitmapped text from lesson 13.
Right now I have the text spinning around it's center. However, to get an effect like this to work for arbitrary text, you would need to have some way of knowing exactly how long the line of text was - this can be a little tricky. One way to get the length of the text is to put glBitmap commands into the font's display lists in order modify the raster position as well as the modelview matrix (I've left the necessary line in the code, but it is commented out). Then you can set the raster position to x,y before using glCallLists, and use glGet to find the raster position after the text is drawn - the difference in raster positions will give you the length of the text in pixels.
You should be aware that FreeType fonts take up much more memory than WGL's bitmap fonts (that's one the advantages of binary images, they use very little space). If for some reason you really need to conserve your texture memory, you might want to stick with the code from lesson 13.
Another interesting advantage of using texture mapped quads to represent fonts is that quads, unlike bitmaps, work well with the OpenGL picking functions (see lesson 32 ). This makes life much easier if you want to create text that responds when someone holds the mouse over it or clicks on it. (Making WGL fonts work well with picking functions is possible, again the key trick is to use raster coordinates to figure out the length of the text in pixels).
And finally, here are some links to OpenGL font libraries. Depending on your goals and compiler you may want to use one of them instead of this code (there are many more of them out there, I've generally only included things that I have some amount of experience with myself).
GLTT This library is an old library that doesn't seem to still be maintained, but it has gotten some very positive reviews. Based on FreeType1. I think you will need to find a copy of the old Freetype1 source distribution to compile it in MSVC6. Download available from http://www.opengl.org/developers/faqs/technical/fonts.htm
OGLFT A nice Freetype2 based font library, it takes a bit of work to compile under MSVC though (mostly just typical for-loop scope problems). Seems to be targeted at linux machines. http://oglft.sourceforge.net/
FTGL Yet a third Freetype based font library, this one was clearly developed for OS X. http://homepages.paradise.net.nz/henryj/code/#FTGL
FNT A non-Freetype library that is part of PLIB. Seems to have a nice interface, uses its own font format, compiles under MSVC6 with a minimum amount of fuss. http://plib.sourceforge.net/fnt/