Raymarching a first SDF scene
Learning objectives
- learner can define signed distance functions and combine primitives with min-based scene composition and boolean operations
- learner can march a ray through an SDF field using sphere tracing to find surface hits
- learner can composite 2D reference SDF primitives — positioned with an offset and given visible thickness — as a screen-space overlay on the raymarched image
Capstone — one whole task that evidences the objectives
Raymarch a small 3D SDF scene that unions and subtracts several primitives and displays the surface hits, then composite a 2D SDF overlay on top of the image in screen space: a thickness-offset segment and an offset-positioned reference-library shape, thresholded with step/smoothstep.
Prerequisite modules
This module is where your visuals stop being flat gradients and become geometry. In a live set, raymarched SDF scenes are the workhorse of projector visuals: an entire evolving 3D world lives in one fragment shader, hot-reloaded on save, with no meshes, no asset pipeline — just distance formulas you can mutate mid-performance. The whole task is a first working raymarcher: several primitives unioned and carved into a small 3D scene whose surface hits show up on screen, finished with a flat 2D SDF overlay composited over the image.
Start supported. First get one shape on canvas by understanding what a signed distance function actually returns — positive outside, negative inside, zero at the boundary — and model the camera ray as a parametric function so “where does this ray hit?” becomes “solve for t”. Then wire the sphere-tracing loop: step along the ray by exactly the SDF’s value, terminate on a hit threshold or a far miss. From there the scene grows by repetition of two micro-moves you should drill to automaticity inside the build: inserting a min() to union in a new object, and max(distance, -cutter) to subtract one. The final layer is deliberately 2D: the same distance-field math evaluated in screen space and thresholded with step()/smoothstep(), drawn over the raymarched result. Don’t derive these shapes — the segment SDF and the offset-adaptation workflow for reference-library shapes are your just-in-time how-tos, remembering that thin 1D primitives need a thickness offset subtracted to be visible at all.
Every required atom gates the capstone: without composition, subtraction, or the march loop the 3D scene cannot render, and without the segment SDF, the thickness offset, and the library drop-in workflow the 2D overlay cannot appear. The supporting atoms enrich rather than gate — why primitive-based SDFs dominate demoscene and Shadertoy practice, material IDs for per-object shading later, and the checkpoint-stage workflow that keeps a live build recoverable.
Runnable examples
Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.
feedback-trail
osc(4).modulate(src(o0), 0.6).out(o0)
hydra-0022 · CC0-1.0
function draw(){ fill(0, 20); rect(0, 0, width, height); circle(mouseX, mouseY, 40) }
p5live-0003 · CC0-1.0
tiling-repeat
osc(10).repeat(3, 3).out()
hydra-0008 · CC0-1.0
tile [4,4] (circle 0 0.3) >> add
punctual-0020 · CC0-1.0
radial-symmetry
osc(10).kaleid(5).out()
hydra-0010 · CC0-1.0
// sandbox
osc(10, 0.05, 1.3).kaleid(8).out()
// sandbox
p5live-0037 · CC0-1.0
scale-pulse
uv *= 1.0 + 0.3 * sin(u_time * 2.0);
glsl-0030 · public-domain
updateAudio(); scale(1 + amp * 0.01)
p5live-0044 · CC0-1.0
sdf-shape
circle [0,0] 0.4 >> add
punctual-0018 · CC0-1.0
float d = length(uv) - r;
glsl-0003 · public-domain
mirror
uv = abs(uv);
glsl-0010 · public-domain
osc(10).kaleid(2).out()
hydra-0011 · CC0-1.0
vector-drawing
beginShape(); for(let p of pts) curveVertex(p.x, p.y); endShape()
p5live-0016 · CC0-1.0
raymarch-sdf
vec3 nrm(vec3 p){vec2 e=vec2(.001,0);return normalize(vec3(map(p+e.xyy)-map(p-e.xyy),map(p+e.yxy)-map(p-e.yxy),map(p+e.yyx)-map(p-e.yyx)));}
glsl-0033 · public-domain
boolean-sdf
float u = min(a, b); float s = max(a, -b);
glsl-0006 · public-domain
typography
let pts = font.textToPoints('P5', 0, 200, 200, {sampleFactor: 0.2})
p5live-0029 · CC0-1.0
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Live Visualist — zero to performing live-coded & generative visuals — Reactive & procedural — make it listen, and go to the GPU recommended
- Shader Artist — real-time GPU craft to a demoscene-grade visual — Raymarching and sculpting SDF worlds required
Unlocks — modules that require this one