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);
}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);
}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);
}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;
}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;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;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
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
| Operation | Formula | Effect |
|---|---|---|
| Union | min(d1, d2) | Either shape |
| Intersection | max(d1, d2) | Both shapes |
| Subtraction | max(d1, -d2) | First minus second |
| Smooth union | smoothMin(d1, d2, k) | Blended merge |
| Rounding | d - r | Expand with rounded corners |
| Hollowing | abs(d) - t | Shell 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 existsmax(d1, d2)creates an intersection where both shapes existmax(d1, -d2)subtracts the second shape from the firstsmoothMin()blends shapes together organically- Subtracting a radius from an SDF rounds corners
abs(sdf) - thicknesscreates hollow shells- Complex shapes are built by composing these simple operations