Search code examples
javascripthtmlopengl-eswebgltruetype

Better Quality Text in WebGL


I am searching a method to draw better quality (arbitrary) text inside WebGL. Currently I am using bitmap font rendering on a 2D canvas and blitting them into the WebGL context.

This method is described here http://delphic.me.uk/webgltext.html

This is the only solution for drawing arbitrary unicode text inside WebGL I know of right now. The problem with this method is that these are bitmap fonts and look blocky on smaller font sizes. I mostly use a font size of 18 and the result is quite blocky compared to desktop quality fonts.

I know that threeJS has a font library which generates better looking text, however I do not want to use threeJS as I have my own wrapper which is working fine for what I need and don't want to add the additional overhead of threeJS.

So how to create better quality text in WebGL ? Are there methods to extract text shapes in Javascript to improve quality ?


Solution

  • 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.

    Another solution is the font generator which is used heavily on Shadertoy. The generator is embedded in an HTML wrapper and is available on GitHub. You can see the generated texture used in Shadertoy here. The advantage with this solution is that there are many examples for all kind of use cases for this on Shadertoy, just search for font.


    SDF Demos


    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


    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).