Adding Font support in OpenGL

*Update* Brandon Mantzey very kindly pointed out a glaring bug in the code accompanying this post… Never fear, I’ve tracked down the idiot responsible (me), and it’s all good now. The bug was actually two bugs – first I was confusing the width in pixels of a character’s texture and the space it takes on screen. Secondly I was working from the bottom left corner of a string, where as I should have been working from the top left. Thanks Brandon for encouraging me to fix this! The link in the paragraph below now has the updated code.

Custom fonts in OpenGLA bit of a change of pace this week as we dive into something more technical.  Code that accompanies this post can be found here, and is free for you to use as you see fit.

While OpenGL provides a lot of graphical power, it’s typically at a fairly low level, giving the programmer a set of tools with which they can build higher level, more useful code.  Rendering text is one example of this.  Pretty much every OpenGL application needs to put text on screen at some stage.  But with no built in font rendering support, we’re left to our own devices.  In this blog I’ll show you one easy way to get text on screen. We’ll end up with something like the screen shot on the right.

There’s a number of different strategies we can use.

One simple option is to use an image editor like Photoshop.  We can generate a texture file with the text ‘baked in’.  It’s then a reasonably simple process to load the image into a texture and render it.  This is possibly the quickest way to render text on screen.  However, it means we need to know in advance what text we want to render.  Also, every piece of text uses another chunk of texture memory.

A second option is to render the text into a texture on the fly.  Noel Llopis from Snappy Touch describes how this can be done for the iPhone on his Games from Within blog. While this allows us to generate text on the fly, it still suffers from each piece of text chewing up more texture memory.  We’re also limited to fonts already installed on the iPhone.

Both of these techniques are useful, but sometimes we need the ability to generate large quantities of text using our own custom font.  What we can do is pre-render our character set into a texture map, and generate text as a series of textured quads, with one letter per quad.  This way we store the font once, and can render as much text as we like.  The trouble here is that we need to store all the font’s kerning information.  Now you could delve into all sorts of documentation to get the information you need.  But someone else has already done the heavy lifting for us, and we can reap the rewards.

Paul Nettle has written a nice utility for us that will take any given font and dump out a data file with all the information we need.  His Font Generation tool is Windows based, but most of us can find a Windows machine if we really need to…

So, enough talking, let’s get started.  The first thing you’ll need to do is to choose your font and install it.  There are large numbers of freely available available fonts on the net.  One good source is dafont.com.  Next, grab the Font generator from Paul Nettle’s site, under the ‘Code’ section.  It’s a fairly simple tool, select the font you want, specify a file to save it to and hit the ‘go’ button.  Transfer the generated file to your Mac and add it to the Resources section of your XCode project.

We create a class, CFont, to store our font information.  It’s constructor takes the path to our font file as an argument.  We can get the path within our application bundle using the following code:

 
NSString *galacticaFontPath = [[NSBundle mainBundle] 
				pathForResource:@"Battlestar" 
				ofType: @"fnt"];
mGalacticaFont = new CFont([galacticaFontPath cStringUsingEncoding:1]);
 

The file is in a simple binary format, so we use fopen() to open it and read out the each character:

 
void CCharacterData::Load(FILE *fontFile)
{
	fread(&byteWidth, sizeof(int), 1, fontFile);
	fread(&byteHeight, sizeof(int), 1, fontFile);
	fread(&xOffset, sizeof(int), 1, fontFile);
	fread(&yOffset, sizeof(int), 1, fontFile);
	fread(&screenWidth, sizeof(int), 1, fontFile);
	fread(&screenHeight, sizeof(int), 1, fontFile);
	
	buffer = new unsigned char[byteWidth * byteHeight];
	fread(buffer, byteWidth * byteHeight, 1, fontFile);
}
 

The array buffer contains the pixel data that we will transfer to our texture map. The other fields are used to tell us how and were to render the character. Or in other words, they contain the relevant kerning information that we need.  We load all the character data from the file and store it away for later use.

The next task is to turn this data into a useful texture map.  We calculate the size of texture we need, using the CFont::GetHeightForTexture() method, and allocate a suitably sized data array.  In the code below, each character is added to the array, and we generate and store texture coordinates as we go.  Finally we generate and bind a GL texture using our data array.


// Allocate texture data
GLubyte *textureData = new GLubyte[textureWidth * textureHeight * 4];
		
// Should really clear out data array...
int texX = 0;
int texY = 0;
		
for (i = 0; i = textureWidth)
	{
		texX = 0;
		texY += maxSizeY;
	}
			
	int offX = texX + cCharacterPadding / 2;
	int offY = textureHeight - 1 - (texY + cCharacterPadding / 2);
			
	// Calculate texture coordinates for this character
	float widthScale = 1.0f / (float)(textureWidth - 1);
	float heightScale = 1.0f / (float)(textureHeight - 1);
			
	float left = (float)offX * widthScale;
	float right = 
		left + (float)mCharacterData[i].byteWidth * widthScale;

	float top = (float)offY * heightScale;
	float bottom = 
		top - (float)mCharacterData[i].byteHeight * heightScale;
			
	mCharacterData[i].texCoords[0] = left;
	mCharacterData[i].texCoords[1] = bottom;
			
	mCharacterData[i].texCoords[2] = left;
	mCharacterData[i].texCoords[3] = top;
			
	mCharacterData[i].texCoords[4] = right;
	mCharacterData[i].texCoords[5] = bottom;
			
	mCharacterData[i].texCoords[6] = right;
	mCharacterData[i].texCoords[7] = top;
			
	// Store the pixel data in the texture
	// Should investigate using a single channel texture
	for (int x = 0; x 

OK, so we have a font texture, what do we do with it? What we need now is a class, lets call it CGLString, to build our text and render it as needed.  The CGLString constructor takes a string with the text we want to render and the font we want to render it with.  The first time the Render() method is called, we construct a series of textured quads that are textured with the font's texture.  The actual rendering is fairly simple. We just call the CGLString's Render method:


mGalacticaText->Render();

Inside the Render() method, here's what's going on:


// Enable texturing, bind the font's texture and set up blending
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, mFont->mTexId);
	
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
// Bind our vertex data
glVertexPointer(2, GL_FLOAT, 0, mVertices);
glEnableClientState(GL_VERTEX_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, mUVs);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
// Draw the text
glDrawElements(GL_TRIANGLES, 6 * mNumberOfQuads, GL_UNSIGNED_BYTE, mIndices);

We bind the font texture, enable blending and render the textured quads. Fairly simple, eh?

And there we have it - your favourite font rendered in OpenGL.  Now what we have here is a good starting point, although there's a lot more we could add here.  A string that will be rendered repeatedly could be rendered using Vertex Buffer Objects for efficiency.  Drop shadows can be added fairly simply.  A useful method would be one that calculates the size of a piece of text. Then we'll be able to align and justify the text, or scale it if it's too large. There's lots here we can do. The code here is a useful basis on which to build. I plan to post an updated version later to show you what this could be like when fully developed.

Related posts:

  1. Dynamic Lighting Experiments
Post Tagged with , , ,

43 Responses so far.

  1. [...] this anyway, as you’ll most likely be required to pay royalties if you do this… Have a look at this, I hope it’ll [...]

  2. Dan Kyles says:

    Cool, nice post. BTW, I bought Amber an iPhone, so you got another beta tester if you need one :D

    Not that I’m sure how you do load custom apps without a jail break.

    That said… what’s the dev pipeline for iPhone?

    iPhone dev summary for ‘coders of other languages’ —– Future post?

  3. George says:

    Hey Dan. As I understand it, I just need to get the device ID of Amber’s iPhone, and then I can build an application bundle specifically for that device. Or something. Good idea on the development pipeline post. Once I understand the process better, I’ll post about it :)

  4. Paul says:

    Wow, this was a really useful article. I happened across your site searching for resources on how to program nice looking fonts with OpenGL, specifically for the iPhone.

    That link you provided to Paul Nettle’s site for the font converter and your article has made my life so much easier.

    Thanks!

  5. George says:

    Hi Paul. Glad you found it useful. That font generator has made life so much simpler in the past, I figured it was worth sharing. There’ll be more coding articles coming too, hopefully they’ll also be of use.

  6. vhalik says:

    So why C++ and not Objective-C? Is there a performance gain?

  7. George says:

    The choice to use C++ is based on personal preference rather than a substantial performance gain. Both Sam and I have spent a significant amount of time with C++, so it’s a comfortable fit for us. Also, the code remains portable for other systems – Objective C is a nice language, but is very Apple specific at the moment.

  8. Alexei White says:

    I also did something similar to this. Would be curious to hear what people think about it. It basically lets you rip a TTF font, add a dropshadow (or not) and it creates the objective C code to support the sprite map. Then there is a class for converting a string to a bitmap for use with an opengl texture or what have you. http://ambiguiti.es/2009/05/easyglyph-updated-kerning-fixed-dropshadows/

  9. [...] people typically view a couple of pages, of which my font rendering post is a clear [...]

  10. I’m yet another developer who has wrestled with this problem and wasted a week of game development time on writing a Windows utility to convert a font into a bitmap! For what it’s worth, you can check out Font Scraper on my website. Between all these solutions, hopefully people can find one that fits them exactly!

  11. Gil says:

    One note:
    If you use this blend mode: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); you can use an alpha texture of 8 bits per pixel, reducing the memory requirement of your example by 75%.

  12. Gil says:

    But thanks very much for the pointer in the right direction!

  13. George says:

    Hi Gil,

    That’s a very good point, and one I haven’t covered. It was something in the back of my mind to try, but got missed out. When I come back to this code I’ll be sure to put it in.

  14. Mark says:

    Hi George, I’ve tried integrating the code in my app, i can get font rendering, but I’m having some issue, maybe you have an idea of what’s happening :

    1) My font was rendering upside down, i had to reverse the bottom/top vars in the texture mapping code when building character data.

    2) not sure why, but some characters seems offseted while others seem to work properly, see this screenshot : http://www.rndlabs.ca/Misc/fontIssue.jpg

    3) Finally if you look at the screenshot, you see some artifacts between the characters, there’s obviously something i’m doing wrong here, tried with a lot of different fonts, all of them give the same result.

    Thanks for any insight.

  15. George says:

    Hi Mark,

    You’re right about the texture coordinates being ‘upside down’. It’s a common GL problem, because GL coordinates tend to go from bottom to top, yet bitmap / texture data goes from top to bottom. It doesn’t matter to much where you ‘flip’ the vertical texture coordinates, as long as it gets done!

    As for the artefacts between characters, if you look in the code that goes with this article, in the CFont constructor, about a third of the way down, there is a constant cCharacterPadding that is used to provide space between the characters in the texture. Make sure you’re using this padding, otherwise you may get adjacent characters ‘bleeding’ into the character you’re trying to draw. This becomes even more pronounced if you’re using any mipmapping, as the smaller mipmaps blur characters into each other.

    As for the odd characters, I’m not sure what’s going in. If you want, let me know what font you’re trying to use, and I can try it at my end to see what’s going on.

    Let me know how you get on.

  16. Mark says:

    I fixed my issues, but I ended up using BMFont ( http://www.angelcode.com/products/bmfont/ ) to generate my font data(including kerning), which also exported my font in a 8bpp single channel png file. I used stb_image to do the import of the png texture data. Finally, I used your GLString class for rendering, works like a charm. Thanks.

  17. George says:

    Hi Mark,

    Sounds like you’ve got a good solution worked out. I’ll definitely be having a look at BMFont – it’s always good to find alternatives.

  18. Pravesh says:

    Hi Mark,
    I am referring the same approach to render the font in openGL context however I am facing the same character alignment issue with most of the fonts.

    As per your fixes, I am trying alternative using BMFont generator and SOIL library (stb_image)to load the texture data but I am stucking up how to load the character data from png/tga files.

    I am using the same GLString class which use character data to render the font.

    It would be very helpful if could provide some help on this.

  19. textureWidth is not defined anywhere. I could hard code the texture width into a #define or something but I don’t see where this information is taken from the header.

    I see screenWidth but not textureWidth.

  20. George says:

    Hi Brandon,

    textureWidth is a local variable defined in the CFont constructor. It is calculated to be as small as feasible while still fitting all the required characters. After the texture has been created, it is not needed any more, as character data array contains all the information we need.

  21. Would it be possible to see the CFont source code?

  22. George says:

    Ah, I see! The code is downloadable from a link in the first paragraph. Sorry it’s not very obvious, hopefully the full code will help! :)

  23. I see it now, thanks a bunch! :)

  24. I’ve got your sample running and I tried a plain font, which happened to be “Comic Sans”. I had the same issues with some letters being offset on the Y. It looks to me like it’s letters that have a different height like capital letters, ‘y’, ‘j’, ‘f’, ‘p’, etc. Letters like ‘o’, ‘a’, ‘i’, ‘x’, etc, are all aligned. I think what’s happening is in the file export something is being added where it should be subtracted when accounting for the vertical alignment.

    Looking at your code, I can’t really tell if you have any access to the kerning offset or the relative positioning at all. I’ll continue to look closer.

  25. One thing that makes me scratch my head is this:

    // Calculate texture coordinates for this character
    float widthScale = 1.0f / (float)(textureWidth – 1);
    float heightScale = 1.0f / (float)(textureHeight – 1);

    Since the textureHeight and textureWidth are always the same, there will be no unique scaling to each character. Also, the -1 doesn’t do much. On a 1024×1024 texture, it comes out to always being 1/1023 for the scale, which is always the same for each character and practically negligible.

    Is this code correct?

    The more I look at it, the more I’m convinced that this is the problem:

    All the letters that are other than normal size (ie ‘o’, ‘a’, ‘x’, ‘w’, ‘n’) are being scaled on the Y. All the letters that are not normal size (ie ‘y’, ‘k’, ‘i’, ‘j’, ‘l’, and capital letters or other letters that are either full height or extend below, seem to be right. If you want to email me, I’ll gladly work with you to fix this issue.

  26. George says:

    Hi Brandon – it looks like you may have found a bug. Truth is, I haven’t revisited this code lately, and my choice of font probably wasn’t the best…

    I’m more than willing to accept there is a bug here. I’ll see if I can get some time to track it down.

    Thanks for your feedback!

  27. Thanks a lot George! Let me know if there’s anything I can do to help.

  28. I found what was causing the scaling issue. It turns out, you were using .byteHeight/Width in some places where it should not have been used. As it says in the ReadMe file, “The byte width & height of each character is so that you may read the character from the file, but also so that you know how big the character is in memory. These dimensions have nothing to do with character placement, alignment or kerning.” After changing it to the .screenWidth/Height, I get this:

    http://i76.photobucket.com/albums/j18/brandhey/Work/iPhoneSSBugFix1.png

    As you can see, the character placement is still off. It’s most noticeable in the Y coordinate but I bet it’s got the same bug in the X, just that it’s not as noticeable as that’s probably just kerning displacement.

    I’m sure you’re very busy just as I am but if you could give me a clue as to how I could fix the placement of the characters, I’ll continue troubleshooting this and I’ll send you an updated project once I’ve figured it out. Email me! Thanks again!

  29. George says:

    Hi Brandon, I found the bug(s), and the code linked above is now much better. Sorry for the hassle, I hope this works for you now!

  30. Thank you so much George for taking the time to fix that bug. After a few hours of pulling my hair out, slamming my face against my keyboard, beating my head against the wall, and cussing, I finally got it to work in my game. Here’s a screenshot of what I’ve got:

    http://i76.photobucket.com/albums/j18/brandhey/Work/FontwithArtifactsarrowed.png

    I pencilled in some arrows because there are artifacts in the image. I’ll kick it around a bit to see if I can fix that issue but if you could give me a clue just in case I don’t figure it out, I would be even more eternally grateful than I already am! lol

    Thanks again!!!

  31. …and of course I figured it out as soon as I posted this. The code that was the culprit is this:


    float widthScale = 1.0f / (float)(textureWidth -1.0f);
    float heightScale = 1.0f / (float)(textureHeight -1.0f);

    I had it set to -2 because it looked better in test project. I’m a little confused by these lines of code. Why the inverse of the width and height minus 1?

    It’s better now but there is still a slight trace of an artifact where the above image has the arrows drawn. I ended up going with:


    float widthScale = 1.0f / (float)(textureWidth -.8f);
    float heightScale = 1.0f / (float)(textureHeight -1.0f);

    That should suffice, but is there a non-hack way of removing that artifact completely? Thanks again, again!!!!!

  32. Back again, here’s the finished product:

    http://i76.photobucket.com/albums/j18/brandhey/Work/FontwithArtifacts2.png

    The artifacts are really noticeable. :(

  33. George says:

    Hi Brandon,

    widthScale is measuring the size of 1 pixel in texture coordinates, which range from 0 to 1. Think of, say, an 8 x 8 texture. The distance from one grid point to the adjacent grid point is the ‘size’ of one pixel in texture coordinates. So, rather than counting the number of pixels in the texture (textureWidth), we use one less (textureWidth – 1) because we’re interested in the spaces between the grid points, and there’s one less of those.

    I hope that makes some sense. There’s a character padding variable used when the font is created that adds some extra clear space around each character in the texture. Make sure this is at least 4 to get enough clear space between characters. It may help fix the issues you’re seeing.

  34. Yes, that does make sense. Thanks for breaking it down. You’re converting it to a percentage essentially but instead of per 100, it’s per 1. .9 = 90%, just like OpenGL scaling is 0-1. I get it now. lol

    I had to crank that padding variable up to 15 to make those artifacts go away.

    Well, it looks like I’ve got it rocking now. Thank you so much again for your help. I’ve learned a lot from this.

  35. Al says:

    Hello,

    I saw it mentioned above that Brandon had fixed the “rendering upside down” issue. I was wondering if you could let me know how? I have been looking at the opengl code, and everything I change the top / bottom vars, I just get very odd results.

    Thanks

  36. George says:

    Hi Al,

    If the characters are in the correct place, just upside down, then you need to swap the y texture coordinates. IS the sample project working for you?

  37. Al says:

    Thanks for the quick response. Yes the test app works just fine. I am even using one of the pre converted font files from your example for my test app. But in my test app when I swap the y values the text is right side up, but the lowercase letters are too high, and other letters too low

    Thanks for any info.

  38. Alex says:

    Hi,

    this is really a nice way to use fonts in an OpenGL application. Thanks for publishing it.

    I tried writing some international characters and when rendering the CGLString it stopps rendering when the special character should be appear. I tried that Windows Tool to create a new binary font file but again when rendering the CGLString the special characters are not beeing rendered.

    Does anyone has an idea to make it possible to use chars like the german umlaut “ä, ö, ü, ß” ???

    Any help would be much appreciated

    Alex

  39. JustinB says:

    Hi George.
    A few months ago I found this tutorial while I was learning how to program on the Android alongside OpenGL (I’m not one to do it easy, I guess…) Anyway, I translated your code into Java and got it working on my Cliq; you have probably the easiest and most elegant method I’ve seen (along with Paul Nettle’s program) to get text into OpenGL.

    I really just wanted to say thanks for the great tip, and thank you for sharing this code!

  40. argelast says:

    Does anyone know How to do it in Opengl ES 2.0?
    Thanks.

    • George says:

      Hi argelast,

      The basic idea in OpenGL ES v2.0 is similar, although a lot of the setup code is different. I’m in the process of moving over to v2.0 at the moment, although it’s taking some time. When I get a chance I’ll post an updated version.

  41. Jp says:

    Im looking for a way to render true type fonts with OpenGL on iOS, and BAM! George Sealy!!! Great info George! Ur the best!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>