Combining Distance Fields
by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026
Run the shader to adjust these controls.
What it demonstrates
The reason distance fields matter is that they compose. Combining two shapes needs no geometry, no clipping and no stencil buffer: union is a minimum, intersection is a maximum, and subtraction is a maximum against a negated field. This shader applies all three to the same box and circle so the operator, not the shapes, is what changes.
Shader inputs
void mainImage(out vec4 fragColor, in vec2 fragCoord)
Called once per pixel. Write the colour to fragColor.
-
iResolutionvec3 - Viewport size in pixels (z is the pixel aspect ratio).
-
iTimefloat - Seconds since the shader started.
-
iTimeDeltafloat - Seconds since the previous frame.
-
iFrameRatefloat - Frames per second, smoothed.
-
iFrameint - Frames rendered since the start.
-
iMousevec4 - Mouse position: xy while held, zw of the last click.
-
iDatevec4 - Year, month, day, and seconds within the day.
-
iChannel0sampler2D - Texture bound to channel 0.
-
iChannel1sampler2D - Texture bound to channel 1.
-
iChannel2sampler2D - Texture bound to channel 2.
-
iChannel3sampler2D - Texture bound to channel 3.
-
iChannelResolutionvec3[4] - Pixel size of each bound channel texture.
-
iChannelTimefloat[4] - Playback time of each channel, in seconds.
-
iSampleRatefloat - Audio sample rate, always 44100.
Main Image6
uniform int uOperation; // @param 0..2 = 2 "Operation"
uniform float uSeparation; // @param 0.0..0.40 = 0.14 "Separation"
uniform float uSize; // @param 0.10..0.34 = 0.22 "Shape size"
float sdCircle(vec2 p, float radius) {
return length(p) - radius;
}
float sdBox(vec2 p, vec2 halfSize) {
vec2 d = abs(p) - halfSize;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
float opUnion(float a, float b) {
return min(a, b);
}
float opIntersect(float a, float b) {
return max(a, b);
}
float opSubtract(float a, float b) {
return max(a, -b);
}
vec3 shadeField(float d) {
vec3 color = (d < 0.0) ? vec3(0.18, 0.42, 0.72) : vec3(0.96, 0.74, 0.34);
color *= 1.0 - exp(-7.0 * abs(d));
color *= 0.88 + 0.12 * cos(140.0 * d);
return mix(color, vec3(1.0), 1.0 - smoothstep(0.0, 0.006, abs(d)));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
float box = sdBox(p + vec2(uSeparation, 0.0), vec2(uSize * 0.95));
float circle = sdCircle(p - vec2(uSeparation, 0.0), uSize);
float d;
if (uOperation == 0) {
d = opUnion(box, circle);
} else if (uOperation == 1) {
d = opIntersect(box, circle);
} else {
d = opSubtract(box, circle);
}
fragColor = vec4(shadeField(d), 1.0);
}
Learn from this shader
How it works
Each shape is evaluated at the same point, offset in opposite directions so Separation moves them through each other. Union takes the smaller distance, which is correct because the nearest surface is the nearer of the two. Intersection takes the larger, which is exactly right only inside the overlap and overestimates outside it — the reason intersections can look subtly wrong under lighting. Subtraction negates the second field, turning its inside into outside, and intersects with that. The visualization keeps the contour bands so you see the operator acting on the whole field, not just the silhouette.
Try changing
Step Operation with Separation held part way so the shapes overlap, and watch the bands break at the seams where the minimum or maximum switches which shape wins. Notice the sharp crease every operator leaves: that discontinuity in the field is exactly what the next lesson smooths away. Drive Separation to zero and the subtraction hollows the box out completely.
Using it in a game
This is how procedural geometry, destructible holes, damage cavities and boolean level shapes are expressed in raymarched or distance based renderers. The caveat is the approximation: a union is exact, but an intersection or subtraction field is not a true distance any more, so cone marching and thick outline effects need extra care once you have used them.
Explore the techniques
Continue with curated explanations and progressively related examples.
Discussion