Drawing Text in OpenGL ES

Screen01

If you're familiar with OpenGL, you know that it does not provide any native font/text support. On the PC, developers have access to wglUseFontBitmaps Windows extension to easily create Bitmap fonts for them; but unfortunately this is not available on Windows Mobile. However, a font is essentially just a texture, so all the tools necessary to draw text are available.

There are traditionally two approaches to drawing text on the screen:

  • Using GDI, draw the string to a Bitmap and load it into OpenGL for blitting.
    • Pros:
      • Easy to do.
      • Really fast if your text isn't changing. (2 triangles)
    • Cons:
      • You need a texture per string, which can be costly.
      • The texture objects are basically immutable and not reusable.
  • Using GDI, draw every printable character to a Bitmap, and then draw a series of quads that reference the specific texture coordinates you want to draw the corresponding letter. (creating dynamic Bitmap fonts)
    • Pros:
      • This is very efficient with memory, since any new string is simply just a new set of vertex and texture coordinates that reference a single texture.
      • Since the text is simply just a series of quads, you can do per vertex tweaking to do some really cool effects.
    • Cons:
      • More work on the GPU
      • Harder to set up and do "right".

Here's the resulting textures from both methods:

Foo

temp

1 Texture for a single blittable string

1 texture that contains all printable characters. Strings are drawn by drawing a series of rects.

 

The optimal approach to this problem would be a hybrid solution that would hinge on the nature of the text: static/dynamic, large/small, special effects/vanilla? But in this article, I'm going to describe how to do it the better of the two ways when on a mobile platform: the single texture lookup.

Let's take a look at the sample image again:

Screen01 BitmapBrush

This sample is demonstrates a couple of the unique capabilities of the Bitmap font method:

  • The FPS and "this is" text is just standard text rendering. Notice that their geometry can overlap and not interfere with each other's rendering. This is because the Bitmap font is actually just a GL_ALPHA texture. The visible portions of a letter have a non zero alpha.
  • The "a test" text is per character coloring. The top half is red, while the bottom left is green, and the bottom right is blue.
  • The "hello world" text is a multitextured font. The Bitmap font texture was merged with the flame texture on the right to create a cool textured string effect!

So, there are two challenges to creating a proper Bitmap font:

  • Determining the width/height of each character in a given font, and generating the resultant texture and character lookup position/dimension.
  • Creating vertex array and texture coordinate array from a given string (ie, "hello world").

Text is not as trivial to render as you may think at first glance. Characters are not just a series of identical sized quads that are lined up left to right. Each character has it's own width, and one character's quad can actually overlap into another character. For example, look at these "foos". Notice how the "f" somewhat falls into the area over the first "o".

Foo

Foo2

Foo3

Rendered

Incorrect quads

Correct quads

This is a significant factor we need to account for when Bitmap fonts. Primitive approaches will often use the incorrect way since it is very easy to implement. The leading and trailing space font properties can be retrieved from GDI through the GetFontMetrics function. And another GDI call, GetCharWidth32, can be used to determine the width of a given character. With that, one can generate the Bitmap texture, and also compute the dimensions and overlap of each character.
 
Using this information, you can generate the following texture:
temp
This is a good start, but this Bitmap is still just an RGBA image. Although this bitmap works, all we really need is a texture that consists of only alpha components, ie, GL_ALPHA. (Using only alpha components allows the GPU to optimize the pipeline and save memory). I say alpha, because this bitmap is not a simple "mask". If it were a mask, the text would be quite pixelated like the left X:
TextAlpha
 
So, to convert from an RGB image to an Alpha image, we must loop through the Bitmap memory and add the R, G, and B components and divide it by 3 to determine the Alpha value. And with that you'll have a valid Alpha texture! (Note: You can't simply take a single R, G, or B component and use that for alpha because the color is gray; you must take the average. This is because ClearType actually sets pixels to non-gray values to achieve sub-pixel antialiasing. More info at the ClearType Wikipedia entry.)
 
The next difficult task is rendering a string by using the texture and character offsets/dimensions we computed earlier. Drawing a single line string is a straightforward loop through the characters. But in actual application, text can wrap when the end of the screen is reach, and be aligned to the center, right, or left. GDI (and by proxy the Graphics object) allow a developer to render string given a Font, a bounding box, and various formatting flags by way of the DrawText function. This function handles all the positioning and such transparently. For OpenGL, we need to recreate that functionality. And although that bit is difficult, it is also tedious and rather uninteresting, so I won't get into the implementation details.
 
Anyhow, you didn't read all the way to the end of this article and not get a code sample! Here's the updated Managed OpenGL ES SDK, along with the sample application demonstrated above.

9 comments:

Anonymous said...

Great job and thanks for sharing this ! I know that I need this kind of information. - cgeboers

Anonymous said...

Hi,

Great site! Does the OpenGL library you wrote provide any HitTest support?

I have an application in in which I would like to drag something. Does seem like it will be a relatively easy task with OpenGL ES or should I stick with GDI...

Thanks!

Koush said...

The purpose of this is to simply provide access to the base functionality of OpenGL through managed code on a mobile device. And, hit testing is outside of the scope of OpenGL.
So no, there's no hit testing.
You probably want to stick to GDI to do what you want to do. Unless you want to implement that sort of framework with OpenGL...

Anonymous said...

Your openGL ES code for drawing text is quite impressive! I would have been enchanted if it was written in C++ because the new generation of software engineers in India are avoiding managed languages for their apps.Hope you provide the C++ equivalent.Great job, by the way!

极品如来 said...

Great job!
Thanks!
I've be searching on google for it so long time.

Anonymous said...

Hi,
Actually i am working on application OpenGl ES in which i have to draw text. I have developed this application as "MFC Smart Device" ,so i need koushik's text drawing code in C++. Any help for drawing text in Open GL ES application is of great help to me.

beefface said...

This is just a series of blah and blah. You are explaining why proportional fonts are a bit tricky to use over the course of half of the article. Then you totally omit the way you actually render the font into a texture - which by the way is not really clever to do the way you do it in your example.
To me, it looks a lot as if you copy-pasted much of the code without actually understanding it. Then you mashed up this article for bragging on how clever you were. Well Sir, you are not.

What it boils down to is, if you need to display proportional fonts in Direct3D or OpenGL/ES, you will need to rasterize them and store them in a bitmapped format together with the proportional per character sizing information. For that, about a zillion solutions exist since more than 20 years. For actually displaying the font, simply use the character images as textures for your rendered quads.

said...

can you give me a Android opengl ES draw text sample code?
Very appreciate !

Anonymous said...

this is a good wrapper of opengles... however, your screenshot shows that the frame rate is 60 fps. How did you achieve that because when i run on my emulator or device, it only has 30 or lesser fps. I discovered that drawing text would reduce much on the fps. Any idea or solution for this?