Combining Shapes

Boolean operations on distance fields

The Power of Composition

Individual SDFs are useful, but the real power emerges when we combine them. Using simple mathematical operations, we can create union, intersection, and subtraction of shapes. These boolean operations let us build arbitrarily complex geometry from simple primitives.

The key insight: since SDFs encode distance as a number, we can combine shapes using arithmetic on those numbers.

Union: Combining Shapes

To create the union of two shapes (where either shape exists), we take the minimum of their distances:

float unionSDF(float d1, float d2) {
  return min(d1, d2);
}
glsl

At any point, the combined distance is the distance to the nearest shape. Inside either shape (where either distance is negative), the result is negative. This is exactly what we want for a union.

Interactive: Union

Union: min(circle, box) keeps either shape

Move the shapes and watch how min() seamlessly combines them. Where they overlap, both distances are negative, and min() picks the more negative one. Where they are separate, min() picks whichever shape is closer.

Intersection: Where Both Shapes Exist

Intersection keeps only the region where both shapes overlap. We use max() instead of min():

float intersectionSDF(float d1, float d2) {
  return max(d1, d2);
}
glsl

A point is inside the intersection only when it is inside both shapes, meaning both distances are negative. The max() of two negative numbers is the less negative one, which remains negative only in the overlap region.

Interactive: Intersection

Intersection: max(d1, d2) keeps only the overlap

The intersection region shrinks as the shapes move apart. When they do not overlap at all, the intersection disappears entirely. Notice how the boundary of the intersection follows whichever shape's boundary is closer to the inside.

Subtraction: Cutting Shapes

Subtraction removes one shape from another. We negate the second SDF and take the maximum:

float subtractionSDF(float d1, float d2) {
  return max(d1, -d2);
}
glsl

Negating an SDF flips it inside-out: what was inside becomes outside, and vice versa. Taking max(d1, -d2) keeps the first shape but only where we are outside the second shape (where -d2 is negative, meaning d2 is positive).

Interactive: Subtraction

Subtraction: max(box, -circle) carves the circle out

The second shape acts as a cookie cutter, removing material from the first. This is how you create holes, notches, and carved-out regions in your geometry.

Smooth Blending with SmoothMin

Hard boolean operations create sharp corners where shapes meet. Often we want a smooth blend instead. The smoothMin function creates organic-looking unions:

float smoothMin(float a, float b, float k) {
  float h = max(k - abs(a - b), 0.0) / k;
  return min(a, b) - h * h * k * 0.25;
}
glsl

The parameter k controls the blend radius. Larger values create more gradual blending; smaller values approach a hard union.

Interactive: Smooth Union

Smooth union creates organic blending between shapes

Watch how the shapes melt together as they approach. The smoothMin creates a fillet-like transition that looks much more natural than the sharp crease of a regular union.

Rounding Corners

Any SDF can be rounded by simply subtracting a radius:

float roundedSDF = originalSDF - radius;
glsl

This shifts the entire distance field inward. What was the boundary at distance 0 is now at distance -radius. The new boundary (at distance 0) is radius units further out, creating rounded corners.

Interactive: Rounding

Subtracting a radius rounds corners

Subtracting from the SDF expands the shape outward, and corners naturally become rounded because the distance field spreads evenly. Larger radii create more pronounced rounding.

Hollowing: Creating Shells

To create a hollow shell from a solid shape, use abs():

float hollowSDF = abs(originalSDF) - thickness;
glsl

The abs() function reflects the interior distances to be positive, creating a new surface at both the inner and outer boundaries. Subtracting thickness then shrinks this double-sided surface to the desired shell width.

Interactive: Hollowing

Hollowing with abs(sdf) - thickness

The interior and exterior boundaries of the shell both come from the same underlying SDF. This technique works with any shape, creating consistent wall thickness regardless of the geometry.

Building Complex Shapes

With these operations, you can construct almost anything:

  • Union multiple primitives to build up a shape
  • Subtraction to carve details and create holes
  • Intersection for masking and clipping
  • Smooth union for organic forms
  • Rounding for soft edges
  • Hollowing for shells and outlines

Interactive: Shape Builder

Shape 1
Shape 2

min(d1, d2) - Either shape

Experiment with different combinations. Each operation transforms the distance field in a predictable way, and they compose freely. A complex model is just a tree of simple operations on simple primitives.

The Operations at a Glance

OperationFormulaEffect
Unionmin(d1, d2)Either shape
Intersectionmax(d1, d2)Both shapes
Subtractionmax(d1, -d2)First minus second
Smooth unionsmoothMin(d1, d2, k)Blended merge
Roundingd - rExpand with rounded corners
Hollowingabs(d) - tShell of thickness t

These six operations are your toolkit for constructive solid geometry in shaders. Master them, and you can build anything.

Key Takeaways

  • min(d1, d2) creates a union where either shape exists
  • max(d1, d2) creates an intersection where both shapes exist
  • max(d1, -d2) subtracts the second shape from the first
  • smoothMin() blends shapes together organically
  • Subtracting a radius from an SDF rounds corners
  • abs(sdf) - thickness creates hollow shells
  • Complex shapes are built by composing these simple operations