sdt_dead_reckoning.h - Single header file library to create signed distance fields
c, graphics, sdf
Another small C function that came out of the project I'm currently working on. This one is to create a signed distance
field out of a black and white image (see pictures below). Signed distance fields are quite useful for rendering 2D or
3D shapes on the GPU. I came across them while investigating text rendering but they're very versatile constructs.
While searching for a function to create one I stumbled
across the paper "The dead reckoning signed distance transform" by George J. Grevera. It contained a quite nice pseudo-code
implementation of the algorithm so I ended up implementing it in C. This is how code using it looks like:
// Load the mask image with stb_image.h
int width = 0, height = 0;
uint8_t* mask = stbi_load("mask.png", &width, &height, NULL, 1);
// Allocate and create the distance field for the mask. Pixels > 16 in the
// mask are considered inside. Negative distances in the distance field are
// inside, positive ones outside.
float* distance_field = malloc(width * height * sizeof(float));
sdt_dead_reckoning(width, height, 16, mask, distance_field);
// Create an 8 bit version of the distance field by mapping the distance
// -128..127 to the brightness 0..255
uint8_t* distance_field_8bit = malloc(width * height * sizeof(uint8_t));
for(int n = 0; n < width * height; n++) {
float mapped_distance = distance_field[n] + 128;
float clamped_distance = fmaxf(0, fminf(255, mapped_distance))
distance_field_8bit[n] = clamped_distance;
}
The distance field itself is written into an float
buffer. I've added the "mapping" part above because in most use
cases you want to store the distance field not as floats but as something else (e.g. as 8 bit image). The function
leaves that to you because it would make the API quite a lot more bloated.
Below is an example of an 500x500 pixel black and white mask image and the distance field computed for it. On my old
Intel Core i5-3570K @3.40GHz it took about 5ms to compute it (single threaded, compiled with -O2 -ffast-math
). Not
useful for fancy realtime stuff but fast enough for my needs. And above all: Simple to use. 🙂
A mask image for a 2D shape. White pixels are considered inside the shape, black pixels outside (pretty intuitive for humans).
The resulting distance field. Each pixel contains the distance (in pixels) to the outline.
Gray (128) is a distance of 0, white (255) is 127 pixels outside of the outline and black (0) is 128
pixels inside the outline.
While it doesn't look like much a distance field is really easy to render with a GLSL shader. And you get high-quality
anti-aliasing almost for free. Another very handy thing is that you can shrink or grow the shape by simply adding or
subtracting a value from the distance in the shader. And a lot of other cool things.
Without going into to much detail… if you have to render 2D shapes look into signed distance fields. 🙂