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));
glsl

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));
glsl

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);
glsl

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));
glsl

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);
glsl

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 origin
  • step(radius, distance) creates hard-edged circles
  • smoothstep() creates soft transitions for anti-aliased edges
  • abs(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