Jérôme Belleman
Home  •  Tools  •  Posts  •  Talks  •  Travels  •  Graphics  •  About Me

Displaying Text with OpenGL

17 Aug 2008

If OpenGL offers means to display text, the business of showing fonts really only becomes satisfyingly powerful when you use FreeType to render it as texture.

1 Using GLUT

Thanks to GLUT, you can very easily and quickly print text on the OpenGL window.

  1. Include the necessary headers:

    #include <GL/glut.h>
  2. Measure up your string. This gives you the length of the rendered text, which is especially useful if you intend to centre it on screen in the next step:

    unsigned char string[] = "The quick god jumps over the lazy brown fox."
    int w;
    w = glutBitmapLength(GLUT_BITMAP_8_BY_13, string);
    The first parameter is the font (find a list in man glutBitmapCharacter).
  3. Place text in the screen:

    glRasterPos2f(0., 0.);

    How positioning is carried out here I never really understood, especially when the above call is not the same as no call at all. You'd better experiment a bit. Since we called glutBitmapLength() and know how long the rendered text will be, we can centre it on point x:

    float x = .5; /* Centre in the middle of the window */
    glRasterPos2f(x - (float) width / 2, 0.);
    Bear in mind that positions are clamped in [0,1]. Go beyond these limits and your text won't show up!
  4. Set the text colour using glColor3f(), for instance:

    glColor(1., 0., 0.);
  5. Display text:

    int len = strlen(string);
    for (i = 0; i < len; i++) {
        glutBitmapCharacter(GLUT_BITMAP_8_BY_13, string);
    }

    Again, you have to recall the font name.

This approach might put you off, however, as the rendered quality is rather poor. What's more, there's only little choice in the fonts you can use.

2 Using FreeType

FreeType is a separate library that lets you get some bitmap from any TrueType, OpenType and other fonts. The idea is to get the pixels, make an OpenGL texture from them and map one glyph texture on one quad whose rendering may be carried out by display lists for better performance.

2.1 Font Setup

Since that's something carried out only once in the process, you're better off doing this in your home-made OpenGL initialisation function:

  1. Set up blending with GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA for the source and destination factors respectively.
  2. Set up the FreeType library, load a face (i.e. a font file), give it a size.
  3. In the next step, we'll set up 256 glyphs (128 ASCII + 128 special ones) in one loop. So it's time you initialise both the 256 textures and 256 display lists we'll need.
  4. For each of the 256 glyphs we're interested in, get the index, the glyph itself and render it. At this stage, you'll have your pixels which are actually values in [0,255].
  5. You'll find that we do not necessarily have glyph images whose dimensions are powers of 2, which is nonetheless necessary for OpenGL to map textures. We'll have to copy all of the pixels from the original FreeType array to a home-made array which will have the necessary dimensions. For both the width and height we'll have to find the next power of 2 (which may be the current size if it is a power of 2 already):

    int p2 = 1;
    while (p2 < original_glyph_length) {
        p2 <<= 1;
    }

    This algorithm does something like:

    00010110 → 00100000

    ... as a power of two is nothing more than a 1 with 0s everywhere else. With the resulting dimensions, we can build a new array in which we'll put the original image in the bottom left corner of the new one. The background value will be 0 so you may fill it up at once with bzero().
  6. Now is the time to actually create the texture with glTexImage2D() whose internal format and format parameters are both GL_ALPHA. Once that done, the home-made array we just created may be freed.
  7. You may now create the display list for this glyph where you won't forget to glEnable(GL_TEXTURE_2D), call glTexEnvf() with GL_REPLACE and call glBindTexture().
  8. Call glTranslatef() to follow the glyph's left and top bearings:

    glTranslatef(face->glyph->bitmap_left, 0., 0.);
    glTranslatef(0., -face->glyph->bitmap.rows + face->glyph->bitmap_top, 0.);
  9. At this point you may choose your font colour with glColor3f() and begin describing your texture-mapped geometry. However it may be cleverer not to if you want to choose any colour upon a call to your custom print function. Read on.
  10. Last thing, move the cursor by the glyph's advance (don't forget the advance is given in 1/64 pixels, hence the >> 6):

    glTranslatef(face->glyph->advance.x >> 6, 0., 0.);

2.2 Displaying Text

It's only about calling the right display lists in turn. In a separate home-made print() function you may just have to do:

glListBase(uint_list);
glCallLists(strlen(string), GL_UNSIGNED_BYTE, string);

By so doing, thanks to the offset set by glListBase(), glCallLists() will simply call the right display list based on the ASCII value of each of character of string.

You should be able to call glColor3f() or something just before your home-made print() function in order to choose your text colour. Of course, this only works if you haven't already done so when setting up your display list.

3 References