Text rendering

One of the latest additions to my OpenGL game engine is text rendering. TIt took more effort than I expected, and that’s why this article exists. The OpenGL standard does not define text rendering. This was surprising to me at first, because while OpenGL is a low-level graphics API, rendering text seemed pretty low-level to me. Boy was I wrong. To ensure the correct placement of individual glyphs, a number of variables must be taken into account, as shown in the following figure:

glpyh_metrics Source: https://learnopengl.com/In-Practice/2D-Game/Render-text

As if that wasn’t annoying enough, there is not only text from left to right, but also from right to left and even vertical text. Also there exist quite a few more letters than the ASCII range. If you aren’t convinced yet, get your blood boiling by reading the article Text Rendering Hates You by Aria Beingessner. It seems like there is no implementation that gets everything perfect. So yes, maybe rendering text is not as easy as I thought.

In addition to the computer-unfriendly way written language works, there are also different methods to achieve font rendering.

Legacy font rendering

The basic implementation uses a texture of all characters and cuts out the letters it needs to combine them into a word. This implementation works, but it has the big disadvantage that we can’t really scale the text well. Therefore, our font atlas texture would have to have a huge resolution to make large letters look good on for example a 4k screen. This would end up taking precious memory on our graphics card and decrease performance of our font rendering. Moreover, we might face issues when downscaling our characters in case they only occupy little space on a monitor with lower resolution. That’s why we need something better.

SDF

In a Signed Distance Field (SDF) each point that is part of a defined space is assigned a positive value that corresponds to the smallest distance to any point outside of the space. Each point outside the space is represented by a negative value which corresponds to the smallest distance to any point inside the space. We can write these distances into a channel of a texture (instead of the letter itself) to describe the shape of a character and then use these distances in our shader to construct the letter in an arbitrary size. Since constructing the character in our shader based on the texture can be done very efficiently this only introduces minimal overhead while allowing for a font atlas texture with much smaller resolution. Here is the result of creating the letter “A” from a 16x16 SDF texture:

sdf_A

Source: https://github.com/Chlumsky/msdfgen

MSDF

A Multi-Channel Signed Distance Field (MSDF) expands the idea of encoding the shape into a texture to all three color channels. This further improves the quality of the rendered text. This method has been published by Viktor Chlumský in a GitHub repository. THis master’s thesis on this topic can also be found there and is well worth reading. Here is the result of creating the letter “A” from a 16x16 MSDF texture:

msdf_A

Source: https://github.com/Chlumsky/msdfgen

Observe how much sharper the character construction turned out, even though our font atlas texture has the same resolution!

Pain

After researching various methods of text rendering, I decided to use MSDF. Luckily, Chlumský also provided a project that generates an entire font atlas instead of individual characters (msdf-atlas-gen). Furthermore, it compresses them into a single texture with all the metadata.

However, in my opinion this project lacks usability. To implement MSDF atlas generation into my game engine I had to dig deep into the code of the CLI tool and other resources. Here are some that helped me:

Viktor Chlumský:

Michael Martz:

Pain relief

I made this very basic sample implementation https://github.com/Fahersto/OpenGL_msdf which I hope may prevent you from suffering as much as I did. As you can see some parts of it are heavily based on code by Chlumský and Martz. Any improvements to this repository are very much welcome. The intent of this project is to make other people not spend solid 2 weeks on implementing MSDF font rendering. Just open a pull request!

Demo inside my 2D engine

Finally, some of that CSI: Miami zoom that MSDF rendering allows: msdf_font_rendering