Distance Field Textures

A friend recently turned me onto a really cool paper (thanks James!) that Valve wrote that allows you to encode monochromatic (black & white) textures in a way that they can be incredibly low resolution, but when you scale them up, they still look crisp and smooth, not blurry or pixelated.

It is really quite amazing and is perfect for things like fonts or decals.

I recommend reading the paper, but below are some details to help you implement this in your own application, and also some examples of things taken to the extreme.

The paper is here: Improved Alpha-Tested Magnification for Vector Textures and Special Effects

Here’s a really easy to use program that can turn fonts or SVG files into distance field images: signed-distance-field-font-generator

Implementation

Ok so, in a signed distance field texture, the alpha value of each pixel is a value of how far that pixel is from the edge of the shape. In a signed distance field, you essentially take the value which is from 0 to 1, and you subtract 0.5 and multiply by 2 so that you change it from 0-1 to -1 to +1. Negative distances mean the pixel is inside the shape, Positive distances mean the pixel is outside the shape.

You only need to do that math if you care about the exact distance though. If you only care about whether the pixel is inside or outside the shape, you can just consider values less than 0.5 to be inside the shape, and values greater than 0.5 to be outside the shape. In other words, you could just do an ALPHA TEST against 0.5 to render these guys.

Here’s an excerpt of some OpenCL code that does this:

float alpha = read_imagef(tex3dIn, g_textureSampler, textureCoords).w;
float3 color = (alpha < 0.5f) ? (float3)(1.0f) : (float3)(0.0f); [/cpp] I'll refer to that code as the "Alpha Test" code. Another way to do it would be to use smoothstep to smooth the jaggies out a bit. Here's an excerpt of some OpenCL code that does that: [cpp] const float smoothing = 1.0/64.0; float distance = read_imagef(tex3dIn, g_textureSampler, textureCoords).w; float alpha = Saturate(smoothstep(0.5 - smoothing, 0.5 + smoothing, distance)); float3 color = (float3)(1.0f - alpha); [/cpp] In the above, the smoothing constant can be adjusted to change how it smooths out the jaggies. Note that even though the texture is monochromatic, you could use the color channel in the texture if you wanted to, or multiply the color by some other color to make it a colored image. Here are the two source images I used. The first one is of the "Comic Sans" font which I doubled vertically since my textures have to be square, and the second one is a mustache SVG vector graphics image I found online. The font image is 512x512 and the mustache is 128x128. comic_source

moustache_source

Distance Field Textures in Action

Here’s a shot of the texture usages rendered from a distance:
ZoomedOut

Font in Action

Here’s a shot of the text close up with the alpha test code:
LettersAlphaTest

Here’s the same shot, using the smooth step code. Keep in mind that the “8” you are looking at is about 32×32 pixels 😛
LettersSmooth

Here’s the text taken from 512×512 down to 256×256, rendered with the alpha test code. You can already see degradation unfortunately but the look at the pictures above and remember that the full font texture is essentially 512×256 (I doubled it because my textures have to be square) and looks great up close:
LettersAlphaTest_256x256

Here’s the 256×256 font texture again, this time rendered with smooth step. A little bit better, but still pretty bad (but not bad for the resolution of the source font texture!):
LettersSmooth_256x256

Decal in Action

Here’s the mustache decal, which has a source image size of 128×128, rendered with the alpha test code:
MoustacheAlphaTest

Here’s the mustache rendered with the smooth step code:
MoustacheSmooth

Now it starts to get interesting. Here it is at 64×64 with alpha test code:
MoustacheAlphaTest_64x64

And now 64×64 with smooth step:
MoustacheSmooth_64x64

Here’s 32×32 with alpha test:
MoustacheAlphaTest_32x32

Here’s 32×32 with smooth step:
MoustacheSmooth_32x32

Here’s 16×16 with alpha test:
MoustacheAlphaTest_16x16

And lastly, here’s 16×16 with smooth step. Not freaking bad for a 16×16 texture right??!!!
MoustacheSmooth_16x16

Shadow Maps

Apparently another great use for these is to encode a shadow map as a distance field texture. This does a great job of keeping your shadow line smooth, effectively letting you use a much lower resolution texture to store the shadow maps.

The unreal engine allows this as an option in fact, check this link for more info:
Distance Field Shadows

This is a no brainer for static shadows, but dynamic shadows this may not be as useful, as it seems like you’d need to generate the full sized texture to make the distance field texture, so would require some extra memory and processing when generated at runtime. There may be some clever tricks to avoiding that though, not sure.

Comments

comments

About Demofox

I'm a game and engine programmer at Blizzard Entertainment and have been making games since 1990 (starting out with QBasic and TI-85 games) My shipped titles include: * Heroes of the Storm * StarCraft II: Heart of the Swarm & Legacy of the void * Insanely Twisted Shadow Planet (PC) * Gotham City Impostors (PC, 360, PS3) * Line Rider (PC, Wii, DS) I also like hiking, making music, learning cool new stuff and attempting the impossible.