Home

Awesome

Multi-channel signed distance field generator

This is a utility for generating signed distance fields from vector shapes and font glyphs, which serve as a texture representation that can be used in real-time graphics to efficiently reproduce said shapes. Although it can also be used to generate conventional signed distance fields best known from this Valve paper and perpendicular distance fields, its primary purpose is to generate multi-channel distance fields, using a method I have developed. Unlike monochrome distance fields, they have the ability to reproduce sharp corners almost perfectly by utilizing all three color channels.

The following comparison demonstrates the improvement in image quality.

demo-msdf16 demo-sdf16 demo-sdf32

Getting started

The project can be used either as a library or as a console program. It is divided into two parts, core and extensions. The core module has no dependencies and only uses bare C++. It contains all key data structures and algorithms, which can be accessed through the msdfgen.h header. Extensions contain utilities for loading fonts and SVG files, as well as saving PNG images. Those are exposed by the msdfgen-ext.h header. This module uses FreeType, TinyXML2, libpng, and (optionally) Skia.

Additionally, there is the main.cpp, which wraps the functionality into a comprehensive standalone console program. To start using the program immediately, there is a Windows binary available for download in the "Releases" section. To use the project as a library, you may install it via the vcpkg package manager as

vcpkg install msdfgen

Or, to build the project from source, you may use the included CMake script. In its default configuration, it requires vcpkg as the provider for third-party library dependencies. If you set the environment variable VCPKG_ROOT to the vcpkg directory, the CMake configuration will take care of fetching all required packages from vcpkg.

Console commands

The standalone program is executed as

msdfgen.exe <mode> <input> <options>

where only the input specification is required.

Mode can be one of:

The input can be specified as one of:

The complete list of available options can be printed with -help. Some of the important ones are:

For example,

msdfgen.exe msdf -font C:\Windows\Fonts\arialbd.ttf 'M' -o msdf.png -dimensions 32 32 -pxrange 4 -autoframe -testrender render.png 1024 1024

will take the glyph capital M from the Arial Bold typeface, generate a 32×32 multi-channel distance field with a 4 pixels wide distance range, store it into msdf.png, and create a test render of the glyph as render.png.

Note: Do not use -autoframe to generate character maps! It is intended as a quick preview only.

Library API

If you choose to use this utility inside your own program, there are a few simple steps you need to perform in order to generate a distance field. Please note that all classes and functions are in the msdfgen namespace.

Example:

#include <msdfgen.h>
#include <msdfgen-ext.h>

using namespace msdfgen;

int main() {
    if (FreetypeHandle *ft = initializeFreetype()) {
        if (FontHandle *font = loadFont(ft, "C:\\Windows\\Fonts\\arialbd.ttf")) {
            Shape shape;
            if (loadGlyph(shape, font, 'A', FONT_SCALING_EM_NORMALIZED)) {
                shape.normalize();
                //                      max. angle
                edgeColoringSimple(shape, 3.0);
                //          output width, height
                Bitmap<float, 3> msdf(32, 32);
                //                            scale, translation (in em's)
                SDFTransformation t(Projection(32.0, Vector2(0.125, 0.125)), Range(0.125));
                generateMSDF(msdf, shape, t);
                savePng(msdf, "output.png");
            }
            destroyFont(font);
        }
        deinitializeFreetype(ft);
    }
    return 0;
}

Using a multi-channel distance field

Using a multi-channel distance field generated by this program is similarly simple to how a monochrome distance field is used. The only additional operation is computing the median of the three channels inside the fragment shader, right after sampling the distance field. This signed distance value can then be used the same way as usual.

The following is an example GLSL fragment shader with anti-aliasing:

in vec2 texCoord;
out vec4 color;
uniform sampler2D msdf;
uniform vec4 bgColor;
uniform vec4 fgColor;

float median(float r, float g, float b) {
    return max(min(r, g), min(max(r, g), b));
}

void main() {
    vec3 msd = texture(msdf, texCoord).rgb;
    float sd = median(msd.r, msd.g, msd.b);
    float screenPxDistance = screenPxRange()*(sd - 0.5);
    float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
    color = mix(bgColor, fgColor, opacity);
}

Here, screenPxRange() represents the distance field range in output screen pixels. For example, if the pixel range was set to 2 when generating a 32x32 distance field, and it is used to draw a quad that is 72x72 pixels on the screen, it should return 4.5 (because 72/32 * 2 = 4.5). For 2D rendering, this can generally be replaced by a precomputed uniform value.

For rendering in a 3D perspective only, where the texture scale varies across the screen, you may want to implement this function with fragment derivatives in the following way. I would suggest precomputing unitRange as a uniform variable instead of pxRange for better performance.

uniform float pxRange; // set to distance field's pixel range

float screenPxRange() {
    vec2 unitRange = vec2(pxRange)/vec2(textureSize(msdf, 0));
    vec2 screenTexSize = vec2(1.0)/fwidth(texCoord);
    return max(0.5*dot(unitRange, screenTexSize), 1.0);
}

screenPxRange() must never be lower than 1. If it is lower than 2, there is a high probability that the anti-aliasing will fail and you may want to re-generate your distance field with a wider range.

Shape description syntax

The text shape description has the following syntax.

For example,

{ -1, -1; m; -1, +1; y; +1, +1; m; +1, -1; y; # }

would represent a square with magenta and yellow edges,

{ 0, 1; (+1.6, -0.8; -1.6, -0.8); # }

is a teardrop shape formed by a single cubic Bézier curve.