War and Peace and WebGL

The previous post demonstrated how to render text on the GPU directly from its bezier curve representation. If you zoomed out far enough you may have noticed the text shimmering. This new demo implements a fix for the issue. The source material is Leo Tolstoy’s War And Peace from Project Gutenberg - 1273 pages with 2.7 million glyphs.

Click here to see the WebGL demo

Before (right) and after (left) the fix implemented in this demo.

The problem

The problem is too much complexity in one spot. Consider an algorithm that renders individual blades of grass. If the earth were covered in grass and you zoomed out to orbit, you’d expect to see a green sphere. But painting the earth green one blade of grass at a time would not be a good idea. At some point we need to switch to a different representation.

The same applies to rendering tiny fonts. The vector atlas chops each glyph into a grid of cells so that each pixel only has to consider a subset of the curves that make up the glyph. However, once each pixel exceeds the size of a grid cell, the pixel shader will skip some cells altogether. We could simply store more curves in each cell but each pixel would have to do more work and performance would drop.

The solution is to use a different algorithm when text is sufficiently small, namely a normal prerendered font atlas. This is implemented in the new demo. It’s created at startup by rendering the vector atlas into a regular texture.

The fragment shader chooses which atlas to use based on the size of the glyph. The vector atlas is rendered in red to show the changeover point.

Caveat

If you zoom out far enough the text will start to shimmer again. The problem now is that each glyph is so small that a single pixel may overlap several glyphs. Rendering this correctly with individual glyphs requires a lot of overdraw and kills performance. Fortunately there’s not a lot of need for text smaller than a pixel, but if necessary we could cache entire prerendered pages in a texture once they shrink below a certain size.