Skip to content
Fundamental · Distance fieldsIntermediate

SDF domain operations

Repeat, mirror, round, hollow and stretch a distance field by changing the point or the distance, not the shape.

How it works

A distance field is a function of a point. Two kinds of change follow from that. Change the point before the function reads it, and you can repeat a shape forever, mirror it or stretch it, for the cost of one evaluation. Change the distance after, and you can round or hollow the shape. Together these are the domain operations, and they are why a raymarched scene can show a thousand pillars while evaluating one.

Infinite repetition

Tiling with fract from the fract-and-mod fundamental gives every cell its own 0..1 coordinate. For distance fields the cell’s coordinate should be centered, so the shape sits in the middle of each cell:

vec2 spacing = vec2(0.5);
vec2 id = round(p / spacing); // which cell: integer coordinates, (0, 0) at the origin
vec2 q  = p - spacing * id;   // position inside the cell, -spacing/2..spacing/2
float d = sdCircle(q, 0.1);   // one circle, repeated in every cell

round(p / spacing) picks the nearest cell center, and subtracting it leaves the offset from that center. This is the same as mod(p + 0.5 * spacing, spacing) - 0.5 * spacing, written so that id comes out as well. A repeated shape costs no more than a single one.

The id makes copies differ. Hash it for a random size, color or rotation per cell, or use it directly, such as 0.05 + 0.02 * id.x for radii that grow across the row.

When repetition breaks

The trick assumes the nearest shape is always the one in the pixel’s own cell. That holds when every copy is the same and fits inside its cell with room to spare. It fails when a shape crosses its cell border (it gets cut off at the edge) or when copies differ, because then a neighbor’s bigger shape may be closer than your own cell’s smaller one. The distance is then wrong near the borders: shapes are clipped in 2D, and in 3D the raymarcher overshoots and surfaces get holes.

The fix is to evaluate the neighbors too and take the minimum:

float d = 1e10;
for (int y = -1; y <= 1; y++) {
    for (int x = -1; x <= 1; x++) {
        vec2 cell = id + vec2(float(x), float(y));
        float r = 0.1 + 0.1 * hash21(cell); // a different radius per cell
        d = min(d, sdCircle(p - spacing * cell, r));
    }
}

Nine evaluations instead of one. Depending on how much the copies vary, a smaller search can be enough; the domain repetition article in the sources works out when.

Limited repetition

To repeat a fixed number of times, clamp the cell index:

vec2 id = clamp(round(p / spacing), vec2(-2.0), vec2(2.0)); // 5 × 5 copies
vec2 q  = p - spacing * id;

Outside the range, id stays at the last cell, so the distance keeps growing correctly away from the grid instead of repeating.

Mirroring

abs folds space onto itself. p.x = abs(p.x) makes everything left of the y axis a mirror of the right side, so a shape drawn at x = 0.3 appears at both x = 0.3 and x = -0.3. abs on both axes gives four-way symmetry, the trick the box function itself uses. Mirroring preserves distances, so the result is still exact, provided the shape doesn’t cross the mirror line.

Rounding, onion and elongation

These change the distance, or stretch the space, rather than repeating it:

// grows the shape by 0.05 and rounds its convex corners
float rounded = d - 0.05;
// a 0.04-thick shell along the edge: a ring from a disk
float onion   = abs(d) - 0.02;
// the circle stretched 0.6 along x
vec2  e       = p - clamp(p, -vec2(0.3, 0.0), vec2(0.3, 0.0));
float pill    = sdCircle(e, 0.1);

The onion can be applied again to its own result for concentric shells. Elongation splits the shape at its center and pulls the halves apart: inside the stretch band e.x is zero, so every point there sees the circle’s center line and the middle is filled with a straight section. Unlike non-uniform scaling, it keeps the outside distance exact.

Transforms from the 2D transforms fundamental are domain operations too: translating and rotating p move the shape without breaking the distance. Polar repetition, copies around a circle, works like grid repetition with angle and radius in place of x and y.

Further reading