SDF Raymarching Mode

One shader · One draw call · All geometry is math

What It Is

A single fullscreen quad. The fragment shader marches rays through signed distance fields. All geometry is mathematical — no vertices, no triangles, no scene graph. One shader, one draw call. The GPU evaluates sceneSDF(p) at every pixel, stepping along the ray until the distance is close enough to call it a surface.

1
draw call
0
vertices
0
scene graph
~8ms
frame time

How It Works

Rendering Pipeline

camera ray march loop (96) sceneSDF(p) hit? normal shade tonemap pixel

For each pixel, a ray is cast from the camera through the view plane. The march loop advances in steps: at each position p, it evaluates sceneSDF(p) to get the distance d to the nearest surface, then steps forward by d. If d < ε, the surface is hit. The normal is computed, lighting is applied, and the color is tonemapped to the final pixel value.

March Loop (the hot path)

The march loop is the frame budget bottleneck. Every pixel runs up to 96 steps, and each step evaluates sceneSDF(p) — the entire scene distance function. Fewer steps = missed geometry. More steps = slower frames. 96 is the current balance point.

SDF Primitive Catalog

All primitives are Lipschitz-1: gradient magnitude ≤ 1 everywhere. This guarantees t += d never overshoots the surface.

sdSphere
float sdSphere(vec3 p, float r)
Exact distance to a sphere centered at origin. The simplest SDF. length(p) - r.
Lipschitz-1
sdCapsule
float sdCapsule(vec3 p, vec3 a, vec3 b, float r)
Exact distance to a line segment from a to b, inflated by radius r. Used for tree trunks, limbs, bones.
Lipschitz-1
sdBox
float sdBox(vec3 p, vec3 b)
Exact distance to an axis-aligned box with half-extents b. Sharp edges, useful for structures and AABB guards.
Lipschitz-1
sdEllipsoid
float sdEllipsoid(vec3 p, vec3 r)
Approximate distance to an ellipsoid with radii r. Not exact but close enough for organic forms — canopies, clouds, torsos.
Lipschitz-1
smin
float smin(float a, float b, float k)
Smooth minimum (polynomial blend). Merges two surfaces with a smooth fillet of radius k. min(a,b) - h²·k/4. Preserves Lipschitz-1.
smooth union
smax
float smax(float a, float b, float k)
Smooth maximum (smooth intersection). The complement of smin. Carves one surface from another with a smooth blend.
smooth intersection

Performance

Metric SDF Raymarcher Polygon Mesh Ratio
Draw calls 1 456+ 456x fewer
Frame time ~8ms ~12ms 1.5x faster
March steps/pixel 96 N/A (rasterize)
Scene graph None Object3D tree Eliminated
JS modules 1 SDF module ~26 modules 26x fewer
Memory ~40MB ~120MB 3x less
GC pauses 2-5ms / 500ms 2-5ms / 500ms Same (both JS)

Frame Budget

At 60fps the budget is 16.7ms per frame. The march loop consumes the majority of it. Each pixel evaluates sceneSDF up to 96 times. On a 1920×1080 display that is ~199 million SDF evaluations per frame. The bottleneck is always the march loop.

Height-field terrain

Terrain uses p.y - terrainH(p.xz). This is not Lipschitz-1 — the FBM noise terrain has gradient magnitude > 1, meaning the marcher could overshoot and miss thin features. Solution: a 0.879 relaxation factor — multiply the step distance by 0.879 to ensure safe convergence. Costs ~14% more steps but prevents artifacts.

Advanced Techniques

Height Early-Out

Skip tree evaluation when p.y > maxH. If the current march position is above the maximum height any tree could reach, skip the entire tree SDF — it cannot contribute to the distance. Eliminates ~40% of tree SDF calls on sky-facing rays.

AABB Guards

Wrap expensive objects in cheap axis-aligned bounding box checks. If the march point is outside the AABB, return the box distance instead of evaluating the full SDF. Turns O(n) per step into O(1) for distant objects.

Tetrahedral Normals

Compute normals with 4 SDF taps in a tetrahedral pattern instead of 6 (central differences). No axis bias — the tetrahedron samples uniformly on the sphere. Uses e = vec2(1,-1) * 0.0005 with 4 permutations. Saves 2 SDF calls per normal computation (33% fewer).

Analytical Terrain Gradients

Instead of finite-difference normals on the terrain (6 extra SDF calls), compute the terrain gradient analytically from the noise function. 6x fewer SDF calls for terrain normals. The FBM derivatives are carried through the octave sum.

Material ID Tracking

sceneSDF_id(vec3 p) returns both the distance and a material ID. After the march loop finds a hit, one extra call to sceneSDF_id identifies which object was hit — terrain, tree, rock, structure — so the shader can apply the correct material without branching during the march.

Relaxed Over-Stepping

For Lipschitz-1 primitives, t += d is safe. For height-field terrain (L > 1), step by t += d * 0.879. The relaxation factor is derived from the maximum terrain gradient magnitude, preventing surface penetration while minimizing wasted steps.

Limitations

Lithos Compatibility

sceneSDF(vec3 p) is the Lithos kernel seam.

The function is the named entry point for the future Lithos native blob call. When Lithos compiles sceneSDF to AGX bytecode, the GLSL implementation becomes the reference oracle — the native dispatch must produce byte-identical output. Every SDF primitive (sdSphere, sdCapsule, sdBox, sdEllipsoid, smin, smax) maps directly to a Lithos font-table entry.

sceneSDF (GLSL) lithos-emit.mjs Lithos font table AGX megakernel 406µs dispatch

Available Routes

RouteModeNotes
/sign/sdf SDF Raymarcher Explicit SDF mode for any home
/sign SDF (default for Virgo) Virgo defaults to SDF when no mode is specified

SDF Drawing Philosophy

Essence over geometry. Sensation over shape.

When drawing any object in SDF, pursue the essence of the thing, not its geometry. Encode sensation — texture, weight, wetness, warmth, roughness — into light and material response. Shape is necessary but insufficient; life comes from how light interacts with surface.

Never impose specifics into general methodology. Prescribing camera angles, petal counts, or segment numbers produces rigid recipes that work once and break on the next subject. The approach must be discovered from the subject itself.

If iterating, preserve what already has essence. Destroy what does not. Each pass must visibly change the result — safe incremental tweaks are failure.