Hey guys,
I ran a poll on X, and about half of my followers know what Distance Fields are and half don’t. Today’s tutorial is intended to be a quick overview of the topic for complete beginners and intermediate readers. This should give you a more comprehensive understanding, even if you already know a bit about SDFs (Signed Distance Fields).
I will be referring to Inigo Quilez’s work a lot today because he’s published many great articles on the topic, however, I’ll try to summarize as much as I can here.

In short, SDFs are functions that describe shapes. More specifically, the distance to a shape’s surface at any given point. The “Signed” part means it will return a negative distance value if the sample point is inside the shape and a positive distance if outside.
That’s it. Now you know what a Signed Distance Field is! These functions are far more useful than you might think.
Why you should learn this
What can you do with SDFs? Let’s look at a few examples
Shapes
Firstly, it’s a nice way to draw vector shapes with pure code. Some have even recreated Rick from Rick and Morty using just SDFs! There are many ways to draw shapes in shaders, but SDFs can make the process of building and editing these shapes more intuitive. There are lots of useful operations that can be applied to them.
I’ve recently written about the process of anti-aliasing, and it can be challenging to implement, but not with SDFs. With SDFs, it’s super easy to implement!
Outlines, lights and drop shadows
Better yet, we can easily add outlines of any thickness when we know the distance from each pixel to the shapes:
float outline_dist = dist - outline_thickness;
You can add glow effects or soft drop shadows when using the distance field as well.
I wrote in more detail about these effects here:
Raymarching
You can even do raycasting or physics simulations with SDFs. Raymarching with distance fields is more intuitive than raytracing, and it’s much easier to build and modify the shapes with raymarching. You can use raymarching in 2D for cheap soft shadows, but the formulas also work well for 3D rendering.
I wrote in more detail about raymarching here (the burgers shown below are fully modeled in code using SDFs too!):
I could go on with some cool technical goodies like: Ambient Occlusion, Depth of Field, collision detection, calculating normals, and more, but hopefully, this is enough to pique your interest! So now that you know a little about how they can be used, how do you actually find the formulas?
How to find them
As much as I like to reinvent the wheel, I can’t recommend writing your own SDFs just yet. Inigo Quilez has done a ton of work to find efficient and exact formulas for many different shapes.
And not just in 2D, but in 3D as well! If you’re just starting out, this is the best way to get a feel for SDFs. Once you get more comfortable, you can experiment with writing your own.
Modifications
Let’s look at some ways you can adapt your distance field for extra variations.
The first class of modifications is distance modifications:
Distance Mods
We can make any shape rounded by subtracting by the desired thickness:
//Set outline thickness around the shape
float round_dist = shape_dist - thickness;
Note: This will make the shape larger, so you will have to reduce the size by the thickness first, which will depend on the shape.
You can also create hollow shapes from any SDF like so:
//Make hollow and set thickness
float hollow_dist = abs(shape_dist) - thickness;
Just for fun, onion-layered distance fields can be made like this:
//Repeat in spaced-out layers
float layered_edge = mod(shape_dist + spacing/2.0, spacing) - spacing/2.0;
//Set layer thickness
float layered_dist = abs(layered_edge) - thickness;
Maybe it’s not the most practical, but it’s a neat trick.
Set Operations
You can combine (union) two distance fields together by taking the minimum of both:
//The union of two distances fields
float union_dist = min(shape_dist1, shape_dist2);
When you think about it, it makes sense. The distance to the closest shape exterior edge will be the lesser of the two distances. And of course, to combine 3 or more shapes, you can just repeat it as needed.
Note: it does break sometimes the interior distance, returning a greater distance than it should. These breaks aren’t usually a big deal, but they can break outline thickness or require more steps to raymarch!
To subtract one shape from another:
//Subtract shape 2 from shape 1
float sub_dist1 = max(shape_dist1, -shape_dist2);
//Subtract shape 1 from shape 2
float sub_dist2 = max(shape_dist2, -shape_dist1);
Or to find the overlapping/intersection of two shapes:
//The intersection of two distances fields
float intersect_dist = max(shape_dist1, shape_dist2);
Note: Union can break the interior distance field, while intersection and subtraction break the exterior distance field. To my knowledge, this is still an unsolved problem, requiring case-by-case solutions or JFA. Read iq’s interior distance article for more insights into this problem.
Spatial Mods
What about modifying the coordinate space itself?
You can mirror the y-axis by using the absolute of y (flipping the negative back to positive):
//Mirror the y-axis
pos.y = abs(pos.y);
//Use these mirrored coordinates with your SDF
float shape_dist = sdf(pos);
You can also flip across an arbitrary (normalized) direction like so:
//Flip in the direction "dir":
vec2 mirror_pos = pos - 2.0 * dir * max(dot(pos, dir), 0.0);
Note: This works best if you’re mirroring perpendicular to the shape’s edge. If you look closely at the left star example above, the mirrored shape creates concave edges, breaking the interior distance field. The new distance field doesn’t know there’s a corner there. Mirroring the bottom half will create a new concave edge and break the exterior.
You can also repeat shapes infinitely by tiling. The simplest way is with mod:
//Repeat the coordinates
vec2 repeat_pos = mod(pos + spacing/2.0, spacing) - spacing/2.0;
This method works for symmetrical shapes like circles and squares however for irregular shapes, it may take a little more work. For instance, these stars are symmetrical horizontally so the left and right sides tile seamlessly, but not vertically:
In this case, it can be solved by sampling the distance field one unit above and below to find the minimum. iq has a written great article on this and much more!
Distortion Mods
If we don’t care about exact accuracy, there are many distortions that can be done.
Minor displacements can be added directly to the distance field for distortions.
Twisting and rotating can be applied directly to the coordinate spaces.
Smooth union, subtraction, and intersections are possible with approximations:
//Exponential smooth minimum
float smin( float a, float b, float k )
{
float r = exp2(-a/k) + exp2(-b/k);
return -k*log2(r);
}
Rather than reinvent the wheel, I’ll refer to iq’s work with smooth blending functions here.
Again, these are no longer exact distance fields, but distorted approximations, and the more distorted they become, the more difficult they may be to work with. Now I think we’re ready to talk about constructing our own distance functions!
Keep reading with a 7-day free trial
Subscribe to GM Shaders to keep reading this post and get 7 days of free access to the full post archives.