SDF combining distance fields

 from Red Blob Games’s Blog
Blog post: 27 Sep 2024

Learning about font rendering, I was looking at text closely last time, and I noticed another issue. The shadows of each letter overlap the previous letter. That’s because I’m drawing one letter at a time. So for example in the fl, I draw the f’s letter, outline, and shadow, then I draw l’s letter, outline, and shadow. So l’s shadow is drawn on top of f’s letter.

Shadows of each letter overlap the previous letter
Closeup showing the shadow drawn over the character to the left

The first thing I considered was to draw all the shadows first and then draw all the letters. But a second problem here, harder to see, is that shadows are drawn on top of the adjacent letter’s shadows, and that causes them to be darker than they should be. The distance fields for adjacent letters always overlap:

Sprite distance fields always overlap

It’s only when the letters are close enough that you can see artifacts of the double rendering.

To solve both problems, I can generate a new distance field which is the min() of the distance fields for each character. The min() of signed distance fields is the union of the shapes. I used the blendEquation()[1] function with MAX_EXT instead of the default FUNC_ADD (it’s max instead of min because msdfgen’s encoding is inverted). The MAX_EXT extension seems to have 100% support[2] in WebGL 1, and is always included with WebGL 2.

Adding an intermediate distance field texture

The output texture holds a combined distance field. I then use the distance field shader to draw that combined distance field to the map.

I could do this once per string I want to draw or once for the entire scene. I decided to do it once per string, because that allows me to use different colors and styles (thickness, outline width, shadow, halo) per string.

I ran into a few bugs with this:

One way I compared parameters was by rendering them in alternating frames. Here’s an example showing that the combined distance field resolution matters (slightly):

Comparison of 3X vs 5X resolution on the combined distance field

Here’s the final result, showing that the overlapped drawing is fixed, especially at the bottom left of the second g:

 
Before and after combining distance fields

The combined distance field approach solves the problem but it means I need to write each string to a separate intermediate texture, which leads to a rabbit hole of having to allocate texture space during rendering, possibly reusing textures, and dealing with gpu pipeline stalls. That’s an area I don’t understand well. Fortunately I don’t need it to be optimized for my project. But for a game project, I might choose to do the two pass approach instead, letting the shadows get drawn twice in overlapping areas. Are there better approaches? I don’t know.

Email me , or tweet @redblobgames, or comment: