A Signed Distance Field is a mathematical construct where the distance to a closed surface is computed along a set of positions, with the sign of the distance used to indicate whether the position is inside or outside the surface. The positions are typically chosen to be on a regular grid and they work well in both 2D and 3D. They were made popular in computer graphics by this SIGGRAPH 2007 paper by Valve. If you haven’t already read it, it’s definitely worth a read!
In this post we’ll investigate using a simple 2D SDF to approximate a shape. It’s by no means the only use of SDF, but it’s one that’s easy to visualize and has practical use in computer graphics.
We’ll start with the surface shown below. It’s a closed surface so that we can tell whether a point is inside or outside its perimeter.
Next, we’ll overlay a regular grid over the surface. As you might expect, the size of the grid is important, the finer it is, the better the approximation will be.
For each grid cell, we can calculate the distance of the cell center to the nearest point of the surface. We’ll represent that with a circle. We’ll also indicate whether the cell center is inside the shape or not by coloring in the circle. Note that we only store one value per cell, calculated at its center, so while a cell may overlap the surface boundary, we only care about its center.
Go ahead and move the shape around (shift + mouse drag) to see how the cell distance values change.
Note that for reasons of clarity I’m only showing the circles that have a diameter of about one cell. The other cells are computed as well and their values will be used later on for the approximation.
Ok, so now we have a regular grid where we store the distance to the shape boundary as well as a bit that tells us whether we’re inside or outside the shape. How can we use that information to reconstruct the original shape?
Let’s have a look at the diagram without the original shape.
Imagine we throw a dart onto the grid. If the dart lands exactly at the center of a cell we’ll accurately know our distance to the shape outline and whether we’re inside it or not. That information will only be accurate if it lands exactly at the center because that’s where we evaluated the distance and sign. If the dart lands anywhere else (and chances are that it will) then we’ll need to make an approximation by interpolating between the 4 nearest cells.
Let’s see what that looks like! Move the mouse cursor over the image.
We’re nearly there now. We have a way of estimating the distance to the surface at any point inside the grid. All we need to do now is visualize all these estimates somehow.
We could for example paint anywhere where the distance is ≤ 0 to shade the interior of the shape. In the diagram below we’re drawing an approximation of the outline of the surface by painting anywhere where the absolute distance is within a threshold. The threshold value controls the ‘thickness’ of the approximate outline.
Have a play with the controls to see what effect it has on the approximation. Use shift + mouse drag to move the shape within the grid.
SDF vs Textures
So what’s the point of all this?
Let’s compare a low resolution SDF of a surface to a same resolution texture that stores the surface color.
You can see that despite being the same, low, resolution, the SDF representation can provide a very high resolution outline of the shape compared to the very pixelated result of the texture approach. Before jumping to conclusions, we need to understand that we’re comparing apples to oranges! The SDF is a high level representation of the shape surface. The image is a capture of the surface color (and alpha). We may use both representations to achieve visually comparable results, but they’re fundamentally different things. Both representations need to be evaluated per pixel.
In the case of the texture this evaluation gives us the interpolated color values, producing the gradients at the edges of the pixels. The image can store different/arbitrary colors per texel, that don’t have to conform to any particular function, but the look of texture is (generally) fixed (e.g. we cannot easily add an outline). Color and opacity can be stored and handled independently.
In the case of the SDF the interpolation is giving us a smooth approximation of the distance to the surface of the shape. We’re free to translate that into a pixel color value using a function selected at runtime. The same SDF can be used with multiple functions to produce different results. However, unlike in the texture case, the entire SDF has to go through the same translation (i.e. it’s not trivial to produce a different color for an arbitrary part of the surface). This translation is a high resolution operation, so we don’t get any pixelation artifacts. Instead, the effects of the low resolution are seen on the accuracy of the approximation. You can see that the sharp corners of the original shape have disappeared and been replaced with rounded points instead.
Looking at the SDF we’ve created so far it should feel quite intuitive that we would store it in a low resolution texture. Ignoring format considerations for now, we’d store the distance and the sign at each texel. At runtime we can evaluate the entire SDF by sampling that texture in a pixel shader and use that to shade a quad. This is more or less what described in the Valve paper mentioned at the beginning. The quad will be rendered at a high resolution (e.g. screen resolution) and the interpolation (done for ‘free’ by the GPU) will give us the lovely crisp outlines we saw earlier.
However this is not always what we want (or are able) to do. While 2D SDF are easy to visualize, there is nothing inherently 2D about SDF in general. A 3D SDF can be used to represent a volumetric surface. How do we visualize that? We could render it as a point cloud, evaluating the SDF at each point, but that would get tricky and expensive. What about cases where we don’t care to visualize the SDF? What if it represents a surface we want to do collision detection against?
As it turns out Ray Marching is a good answer to both of these questions and when it comes to ray marching SDF there are a couple of cool tricks we can use. I’ll save the diagrams for this for part 2 of this post, but in the meantime have a read through this blog post and maybe have a look at Claybook to see how cool things can be made using SDF!