Font rendering

BeforeAfterI’ve been spending a surprising amount of time working on font rendering.  Particularly considering that VectorStorm has supported font rendering for rather a long time now;  it’s only been recently that I’ve really spent a lot of time making it exactly right.

Drawing text is tricky for a number of reasons.  For one thing, font glyphs are usually complicated, with smooth curves and other shapes which are difficult to represent nicely as triangles, particularly at small sizes.  In games, we usually want to draw text as images.  That way, each letter becomes a single rectangle with an image on it.  It sounds like that’s all that should be required, but there are further problems.

First, it’s worth pointing out that Valve Software have done some awesome work on improving drawing high-quality big text.  For text inside a 3D scene which players might approach, that’s exactly what you want.  But it doesn’t work at all for drawing small, crisply antialiased text, and that’s what I’m going to talk about in this post.

In practice, using textures is the only practical way to handle small text glyphs.  The chief problem with this approach is that you have all the problems of textures — if you draw your texture too large on the screen, it starts to look blocky.  Similarly, if you draw it too small, you start to lose detail.  In a regular texture, that’s not a problem — nobody minds if a particular rock in a pile of rubble starts to go indistinct as it becomes smaller on screen.  But when we’re drawing text, losing information can make the text blurry or otherwise unreadable.  In the image above, there are only two mistakes in the top copy of the text, but it makes the text look very clunky and blurry compared against the correct rendering, beneath it.

Drawing text in the wrong size

The first common mistake is of drawing text in the wrong size.  If the image of your glyph is 20 pixels tall, you need to draw it 20 pixels tall on-screen, or else it will become either blurry or blocky (depending on your texture sampling mode).  The bigger the difference in size, the worse the problem.  In the image above, the top text is being drawn about two pixels larger than the images it’s drawing from, and that’s turning the text very slightly blocky.

In a real game, you’ll probably want several different font sizes in your game.  You may also want to support different screen resolutions.  Each combination of these will require separate fonts, so you can draw text at the correct size in each situation.

Right now, MMORPG Tycoon 2 has seven different versions of the same font, ranging from 12- to 48-point, and I still need to generate a few larger ones for use in places like the title screen.  I’ve been careful in my choices of font size to try to reuse fonts for example, if you run on a high DPI screen, the game will use the 24-point sprite font where the game wants a 12-point sprite font.  But that same 24-point sprite font is used on standard DPI for the spots where I do want a 24-point sprite font.  (and the high DPI version will use the 48-point font).  This gives you a good variety of available print sizes, while still maintaining far better text quality than if you tried to draw a large sprite font into a small space on the screen.

Drawing text misaligned from the screen

Remember that we have our text as an image.  As discussed above, we’ve ensured that we draw our text at exactly the same size in our game as the font images exist in our data, to avoid scaling those images on the screen.  So we should be good, right?

There’s another problem, though.  A computer screen is made up of a fixed grid of pixels, each pixel a fixed-size box which can only show one color at a time.  The same is true of our texture — a fixed grid of pixel colors.  What happens if when we tell the computer to draw our texture to the screen, we don’t precisely line up the texture’s grid of pixels with the screen’s grid of pixels, so each screen pixel overlaps several of the pixels in the texture?

When this happens with normal rendering (which it does almost all the time), we want the computer to blend between the colors of the texture pixels which intersect each screen pixel.  This works great for most textures, but it’s catastrophic for text — we’ve carefully built anti-aliasing into our text to make it appear as sharp as possible at exactly its current size;  adding any blur at all will likely make the text quite blurry.  (In the top image above, the text is being drawn half a pixel lower than it should be, accidentally introducing extra vertical blurring as each screen pixel is displaying the average of two glyph texture pixels).  Alternately, we can tell the computer not to blur between pixels, but to instead just use the texture pixel which is closest to the middle of the screen pixel, which at least stops the blur, but can result in different glyphs of our text moving jumping up, down, left, or right by a pixel based upon which direction each pixel thinks is closest — this can make the text appear very uneven, and is possibly even worse than being blurry.

So not only do we need to be sure that we draw our text at exactly the right size so that it takes up the same number of pixels on the screen as it does in the texture, but we also need to make sure that those pixels are exactly aligned between the texture and the screen.

How do we do this?

Well, there are a lot of approaches.  But here’s the simple approach that I’m using in VectorStorm for the information-dense displays in MMORPG Tycoon 2:

  1. For critical text that needs to be drawn small and crisply, render them in an orthographic projection which has the vertical and horizontal clipping planes set at 0 and at the width/height-in-pixels of the rendering context.  This way, a movement of 1 unit in that space will exactly correspond to a movement of 1 pixel on the screen.
  2. When drawing text, you draw it at a scale which matches its original pixel size — drawing a 24-point sprite font at a size of 24 units in this orthographic projection ensures that your text will come out at exactly 24 pixels tall, without needing to do any special math.
  3. Immediately before drawing text, inspect the local to world matrix, and round off any accumulated translations to the nearest integer.  This effectively snaps fonts to the nearest pixel, to ensure that they don’t get smeared between multiple pixels.  (You probably want to restore the old matrix once you’re done)

Where sprite fonts come from

“Sprite fonts” have been around for ages.  I’ve used them in every project I’ve ever worked on, dating all the way back to the original PlayStation, and there exist several tools which will convert normal vector fonts into sprite fonts.  In VectorStorm, I used my own tool, “FontMaker” to do the job, but it was a bit of a hack and never worked 100% correctly, so I’ve now added support for the ubiquitous BMFont sprite font format to VectorStorm v2 (which is still in development on Github), and that’s what I recommend.  (There are lots of other tools which spit out the same sprite format, too, so look around to see what works for you)

Another approach used to draw text in games is to use a library like Freetype or SDL_ttf to render full strings into textures, and then draw those full-string textures to screen.  That’s a great option if your text doesn’t change often, but it doesn’t solve these issues;  that full-string texture suffers from both these same issues, if you’re not drawing it at exactly the correct size or position (but at least you don’t have the problem of individual letters wiggling up and down independently of the rest of the text).