Circles and Distance
Drawing with the distance function
The Distance Function
In the world of shaders, drawing a circle does not mean tracing pixels along a curved path. Instead, we ask a simpler question: how far is this pixel from the center?
The length() function gives us exactly that. For any point in UV space, length(uv) returns the distance from that point to the origin. This single function is the foundation for all circular shapes.
Interactive: Distance from Origin
Brightness represents distance from the center
Every pixel knows its distance from the center. Pixels close to the origin have small values (dark). Pixels far from the origin have large values (bright). This grayscale gradient is the distance field.
Drawing a Hard Circle
To turn a distance field into a shape, we need to make a decision: is this pixel inside or outside the circle?
The step() function is perfect for this. Given a threshold (the radius), step() returns 0 when the distance is below the threshold and 1 when it is above:
float circle = step(radius, length(uv));This gives us a hard-edged circle. Everything inside the radius is black (0), everything outside is white (1). Flip it with 1.0 - step(...) to get a white circle on a black background.
Soft Circles with Smoothstep
Hard edges rarely look good. The smoothstep() function gives us anti-aliased edges by creating a smooth transition between inside and outside:
float circle = smoothstep(radius - softness, radius + softness, length(uv));The transition zone, controlled by the softness parameter, blends smoothly between the two states. Larger softness values create more gradual falloffs, useful for glows and soft shadows.
Interactive: Soft Circle
Adjust the softness to control edge smoothness
Notice how adjusting the softness changes the character of the circle. A tiny softness gives crisp anti-aliased edges. A large softness creates a soft glow that fades into the background.
Drawing Rings
A ring is simply two circles working together. The most elegant approach uses abs():
float ring = abs(length(uv) - radius);This expression gives the distance to the circle's edge, not to its center. At the boundary (where length(uv) == radius), the value is zero. Moving either inward or outward increases the value. Apply smoothstep() to turn this into a visible ring:
float ring = smoothstep(thickness, 0.0, abs(length(uv) - radius));Interactive: Rings
A ring is the distance to the boundary, not the center
The stroke width controls how thick the ring appears. Thin strokes create delicate outlines. Thick strokes create bold bands. This same technique will extend to any shape once we learn about signed distance functions.
Multiple Circles
Shaders evaluate every pixel independently. To draw multiple circles, we simply compute the distance to each one and combine the results:
float d1 = length(uv - center1) - radius1;
float d2 = length(uv - center2) - radius2;
float circles = min(d1, d2);The min() function takes the smaller distance at each pixel, effectively creating a union of the two shapes. This is a preview of the boolean operations we will explore in depth later.
Interactive: Multiple Circles
Multiple circles combined with min()
Each circle has its own center and radius. Where they overlap, we see the combined shape. This additive approach scales to any number of shapes without any additional complexity.
Putting It All Together
Here is a complete interactive demonstration combining everything from this chapter. Adjust the radius, softness, and stroke width to see how they interact:
Interactive: Circle Controls
Filled circle with adjustable softness
The core insight is that distance fields are data, not drawings. They encode spatial relationships that we then interpret visually. A filled circle, a soft glow, and a thin ring all come from the same distance calculation, just with different interpretations.
Key Takeaways
length(uv)gives the distance from any point to the originstep(radius, distance)creates hard-edged circlessmoothstep()creates soft transitions for anti-aliased edgesabs(length(uv) - radius)gives distance to the circle boundary, perfect for rings- Multiple shapes combine with
min()to create unions - Distance fields are a representation of space, not just a drawing technique