March 18th, 2013
I was just working on some text rendering for the arena intro and outro (which should be in tomorrow’s alpha video), and thought I would share how we handle that problem in Overgrowth. Almost every game uses text of some kind, but since most indie game programmers are not typography experts, there are a number of pitfalls that we run into. Here’s an example of some text in Lugaru which demonstrates almost all of them!
The most obvious problem here is that character spacing does not line up with character size. This is because it uses a text atlas (an image that contains every character that the game might use) that doesn’t contain any information about how wide each character should be. The text atlas also stores each character at a fixed size, so it becomes blurry when scaled. Here’s a scaled-up version of this bitmap text (above), compared to how the text ought to look (below).
In the years since releasing Lugaru, many free bitmap text atlas tools have appeared, which make it a lot easier to solve the character spacing issue. However, any text atlas will still result in fuzzy text if we scale it (even just reducing size by 1%), rotate it, or translate it (except if it is clamped to the nearest pixel). In Overgrowth, we solve these problems by avoiding text atlases entirely, and rasterizing text directly to a 1:1 image using the Freetype library, or using Awesomium (an embedded web browser).
The key to rendering high quality text is to rasterize it (convert it to pixels) as late as possible. Internally, fonts contain a combination of bitmaps, complex vector graphics and ‘hinting’ rules, which form an intricate system that determines how the font can be rendered to screen pixels in the best possible way, at a given size, scale, and style. When we rasterize fonts to a bitmap, all of this information is removed, and all that remains is a mindless array of pixels. This is perfect if we just want to copy these pixels directly to the screen somewhere, but any other kind of manipulation at this point will significantly degrade quality. Here’s a picture of text rotated after rasterizing (left), compared to text rotated before rasterizing (right).
One aspect of text rendering is really counter-intuitive to graphics programmers. Normally if we draw an image at a huge size, and then scale it down to fit the screen, it will have much higher quality because of all the sub-pixel information that we have added. This is not true for fonts! This is because the font hinting rules are specifically designed to render to particular pixels for maximum sharpness, and supersampling loses all of this information. Here is a comparison between Photoshop’s supersampled text (above) and Overgrowth’s text rasterized with Freetype (below). The difference is subtle, but look at the vertical components of the ‘d’ and ‘u’ characters.
So, if you would like the highest quality text in a game, I think the best solution is to use the Freetype library to directly render the text to a bitmap when needed, and then cache that bitmap to a texture for display. That is essentially how every other application and operating system does it, and I don’t see any reason why games should be the exception. Do you have any other ideas about how to improve text rendering, or any problems with this method? Please let me know in the comments!
Update 1: Signed distance fields are a really cool idea for preserving sharp edges when up-scaling fonts, but it still loses information, so it's better to just use the font itself if performance allows. Here is a demonstration of the distortion that we can get using a signed distance field: image link. We can reduce the problem by using multiple fields, but it can never look quite as nice as just rasterizing the font normally. However, if it's really necessary to up-scale a low-res vector bitmap, this is definitely the way to go!
Update 2: Text atlases can still be a useful optimization, if text rasterization turns out to be a bottleneck. It's probably wise to try the simple solution and profile, though, before assuming that an atlas is needed. When using an atlas, we just have to make sure that the atlas itself is rasterized properly, and for maximum quality, have to make sure it's copied to the screen at 1:1 scale, and clamped to the nearest pixel. We might also want to create the atlas on demand for whatever characters and character sizes we will have on the screen, instead of loading a premade texture.