Sunday, February 3, 2013

Rendering Text in OpenGL Using FreeType Fonts

EDIT (04.14.2013): Tutorial updated to handle new-lines (\n character)

I finally decided to tackle the rendering of TrueType fonts in my engine. It proved to be a much bigger challenge than I had originally anticipated; I perused over dozens of outdated guides, tutorials, code snippets, and documentation files to finally achieve something legible on screen. I decided to provide a complete guide to this process so that if anyone should decide to follow in my footsteps, they will know where to go.


FreeType 2 Installation

OpenGL works with pixels, and TrueType fonts are not stored like image files, so we need the FreeType 2 library to create font bitmaps. If you are well-versed in 3rd party API installation, feel free to skip this section. I have only performed the compilation using Visual Studio 2010 and 2012.
  • Download the latest headers for FreeType 2 from SourceForge. As of this write-up, the latest version is 2.4.11.
  • Unzip the archive and go to the builds folder to locate your platform. I will be demonstrating this with the win32/vc2010 build.
  • Open freetype.sln in Visual Studio. If you are running VS2012, you will need to update the solution to use the latest compiler and libraries.
  • Select the Release candidate and go to Build->Build Solution.
  • The compiled .lib file will be located in objs/win32/vc2010/freetype[version].lib, where [version] is "2411" as of this writing.
  • Copy this file to somewhere in your current project directory's library path.
  • Copy the contents of the include/ folder to somewhere in your current project directory's include path.

Basic Structures

We are going to define some structures in order to make handling the fonts a bit easier. We need a rectangle to keep track of dimensions, a color for the text color, an a 2D vector to track position. Also, I'm going to include my vertex structure, representing a vertex to send to the VBO for rendering. This is pretty standard and I assume if you've made it this far in OpenGL, you have one of your own.


Font.hpp


This will be the main font header that is used to render text. To load a .ttf font file, we call LoadFromFile(). Here, the mp_glyphTextures map will be filled out in order to create {character: glyph} pairs, which are later used in rendering to find offset values and bind the character bitmaps. The Glyph struct contains a rectangle representing the various offsets (a g should be drawn lower than a b, for example), and the bitmap texture. When RenderText() is called, each character in the given string will be rendered using the unique texture found in the dictionary. The m_FontRender may throw some people off. This is just a very basic shader wrapper class I have written, and I'm sure anyone following this tutorial has a similar wrapper of their own. I will go into more detail as to what the shader does later. Here is the class outline:

The comments should explain everything else in a pretty detailed fashion. Now on to the implementation.

Font.cpp

This a detailed explanation of the implementation of the CFont class.

Initialize()

Here the TrueType library is initialized. This can be called explicitly via CFont::Initialize(), but it will also be called by default when a CFont instance is created.

CFont()

This is the class constructor. Here, we will load the shader and verify that the TrueType library is initialized.


LoadFromFile()

Now we must load the font. Filename is a path to a TrueType font (.ttf) and size is the size of the font, in pixels.


RenderText()

text is, obviously, the text we want to render. Currently, this only supports one line at a time, so characters like '\r' will not render properly. Actually they likely won't render at all and will cause a crash. You should add bounds checking to ensure that the provided char is within the range of render-able bitmaps. Pos is the position where to start rendering. It is not the top-left corner of the text, but rather is on the line to be written on. Imagine a piece of paper: Pos is the line which you're writing on.


FontRender.fs

There is some shader magic necessary in order to properly render the text. Since TrueType gives us a monochrome bitmap, we need to specify that we want to use the r value (specified when we said GL_R8 and GL_RED in the glTexImage2D call in CFont::LoadFromFile() in the incoming texture fragment for all four components for the output fragment.

There you have it! The rest of the methods in CFont should be fairly easy to implement. I mean, I'm sure it's fairly self-explanatory that CFont::SetColor() just modifies CFont::m_Color to reflect the new RGB values, and that Resize() just calls CFont::LoadFromFile() again with the new size specified.

You can see the raw class files directly from my IronClad rendering engine in their entirety on my Github here (header) and here (implementation). Hopefully I will be modifying this system soon to use a texture atlas to contain all of the glyph bitmaps in a single texture and load a VBO with custom texture coordinates on-demand. Feel free to add any comments, questions, or suggestions below!

6 comments:

  1. Thnx for providing this information to the web.

    my page; tutors melbourne

    ReplyDelete
  2. Thank you, much appreciated.

    ReplyDelete
  3. With regards to "Restore default alignment value", you can query this via glGetIntegerv( GL_UNPACK_ALIGNMENT, &alignment ); to properly restore the alignment.
    The default is indeed 4 according to http://www.opengl.org/sdk/docs/man/xhtml/glPixelStore.xml

    ReplyDelete
  4. Awesome! Where can I get the full code?

    ReplyDelete
  5. In this line: vertex2_t* verts = new vertex2_t[vlen];
    vertex2_t* is never defined?

    ReplyDelete
    Replies
    1. Hey Matt! Unfortunately, it took me 2 years to see your comment... I don't really have a standalone sample (@ the full code comment), and vertex2_t is just a structure holding a position + color value (5 floats, basically).

      Delete