Better Quality Text in WebGL

Pixi.js agrees with the recommendation above for SDFs.

https://github.com/PixelsCommander/pixi-sdf-text

They say (and they're an excellent gaming project for the browser)

SDF is the most efficient way to draw text in WebGL. It uses special kind of raster atlases and GLSL shader to draw vector scalable text in a very performant way on GPU.

And this was from MapBox who at least has done it too at some point.


Looking at the source code for three.js suggests a solution.

Here's the code for three.js font creation: FontUtils.js.

It says right at the top:

/*
 * For Text operations in three.js (See TextGeometry)
 *
 * It uses techniques used in:
 *
 *  typeface.js and canvastext
 *      For converting fonts and rendering with javascript
 *      https://gero3.github.io/facetype.js/
 *
 *  Triangulation ported from AS3
 *      Simple Polygon Triangulation
 *      http://actionsnippet.com/?p=1462
 *
 *  A Method to triangulate shapes with holes
 *      https://web.archive.org/web/20121018020533/https://www.sakri.net/blog/2009/06/12/an-approach-to-triangulating-polygons-with-holes/
 */

Typeface.js provides the font data and there's an online form to convert truetype fonts.

Other solutions:

  1. Render the text to your bitmaps at a higher resolution and draw them at that resolution or smaller.

  2. Use a curve renderer: Rendering Vector Art on the GPU.


After working with Fonts for some time, I can see 6 ways to do fonts in WebGL, all with advantages and disadvantages:


Font as Geometry

  1. Get an open source font like the ones from Google (Open Sans and Roboto are very popular)
  2. Read out the curves of the font using OpenType or similar (https://nodebox.github.io/opentype.js/)
  3. Triangulate the characters curves. My favorite for this is Earcut as it is very fast https://github.com/mapbox/earcut
  4. Draw the polygons for each character as normal WebGL triangles.

Advantages

  • Very fast, if you have a lot of text and need full featured fonts, this is your best bet. With font scaling being done on the GPU via matrix operations.

Disadvantages

  • Quality of the rendered font depends on the Anti-Aliasing enabled by your WebGL browser. Safari does 8x8 AA which looks good but all the other browsers only use 4x4 which may look blocky. Also the mobile browser do not enable AA at all making the Fonts look very blocky on your mobile devices.

Canvas

This is also called "On Demand Dynamic Textures". Render only the text glyphs to the (offscreen) canvas and blit it to the screen as a WebGL texture, as described here: http://delphic.me.uk/webgltext.html

Advantages

  • Decent quality.

Disadvantages

  • Speed, depending how much text you need to render in what time this may work fine. Especially if you only have to render static text.

The game Age of Empires III uses this method.


Bitmap Fonts

If you want max speed with best quality for a limited set of characters and a fixed character size (Game), it is probably best to create your own bitmap featuring the characters you want to use and blit them to the screen as needed. You can find quite a few of these character bitmaps predone on the internet.

This is fast and easy, but limits the languages and characters you can use but you would not mind this in a Game for example.

Advantages:

  • Simple to understand
  • Trivial to create a texture atlas
  • Can use colorized fonts

Disadvantages

  • Looks horribly blurry when scaled up
  • Must pre-render all glyphs that are used
  • Requires a texture per font size
  • Requires texture bin packing for optimal texture usage. Example

Signed-Distance Field Fonts

Chris Green of Valve wrote the "book" on using Signed-Distance Fields for textures. You'll want to read the 2007 SIGGRAPH whitepaper "Improved Alpha-Tested Magnification for Vector Textures and Special Effects

Normally a font texture atlas looks like this. A SDF texture looks like. Yes, the SDF texture atlas looks blurry when rendered "as-is". That's because the 8-bit channel has been encoded as:

  • 0 -> -1.0 (outside)
  • 255 -> +1.0 (inside)

Advantages:

  • A single texture can be used to scale fonts very tiny (6 px) to very huge (200 px+) without any loss in quality,
  • Anti-Aliasing is often "for free".

Disadvantages:

  • Must pre-render all glyphs that are used,
  • Pre-processing the SDF texture is normally done "offline" due to the S-L-O-W preprocessing (~15 seconds),
  • Not many people understand how to generate quality anti-aliasing. Hacks such as smoothstep() and fwidth() give poor quality scaled results due to people interpolating in texture space instead of screenspace. It also doesn't help that WebGL's fwidth() uses the wrong constant.
  • Single Channel SDF's doesn't preserve edges as Valve hinted at in their paper. Their solution was to using a multi-channel SDF but didn't provide any details. See Chlumsky Viktor Master thesis or his open source msdfgen
  • Requires "cell padding" or a border of a few pixels if SDF fonts are used at small sizes.
  • Only monochrome fonts are supported

That all said, if you have multiple fonts then SDF fonts can be a huge win in both size (only need 1) and quality (looks fantastic at both small and large sizes)

How to create SDF Textures

There is now an easy to use npm module which generates SDF textures and metadata available here.


SDF Demos

  • Michaelangel007's SDF vs Bitmap shadertoy demo
  • Konstantin Käfer's MapboxGL SDF Demo
  • SDF subpixel antialiasing demo
  • Multi-Channel SDF shadertoy by P Malin
  • Multi-Channel SDF Texture

Fonts on the GPU

People are exploring storing the cubic curves on the GPU and bypassing the texture entirely via having a smart fragment shader do all the heavy lifting of rendering.

This blog has a summary of Font Rendering as of February 2017.

Advantages

  • High Quality Glyphs at normal and large sizes

Disadvantages

  • High shader cost and complexity
  • Has quality issues at small sizes

GPU Font Demos

  • Will Dobbie's Vector Texture

Canvas Overlay

For my current project I use an HTML5 2D canvas to render text and other 2D primitives and overlay it using transparency over the WebgGL canvas. I was surprised at the resulting speed, it beats all other methods described here in speed and quality is very good.

As long as your text is static 2D and you do not need any 3D transformations, this would be my recommendation. In my project this is about 2 times faster than the previous method I used (Font as Geometry).