SDF โ Agent Context for Generative Signed-Distance Graphics
SDF โ Agent Context for Generative Signed-Distance Graphics
Purpose: a compact, copy-paste-ready mental model for an agent (or human) that generates 2D/3D graphics by combining SDF primitives with boolean/domain operations. This is the "skin" that turns the scattered references (Inigo Quilez's distance-function articles, the ShaderToy SDF playlist) into a working recipe. Drop this into any graphics-generation prompt.
Sources synthesized here:
- Inigo Quilez โ Distance Functions (3D)
- Inigo Quilez โ Distance Functions (2D)
- Inigo Quilez โ Code / Articles
- ShaderToy SDF playlist (dbbsphere
4lyfzw, sdBoxMl3fWj, torusMt3BDj, + stubsMlcBDj/ltcfDj/lt3BW2/Xds3zN)
1. Core idea (the skin)
An SDF map(p) returns the distance from point p to the nearest surface.
d < 0โ insided = 0โ on surfaced > 0โ outside
Rendering = raymarch: step along a ray by d each time until |d| โ 0.
float map(vec3 p){ return <combined primitives + ops>; }
vec3 ro = ...; vec3 rd = ...; // ray origin, ray direction
float t = 0.0;
for(int i=0;i<96;i++){
vec3 p = ro + rd*t;
float d = map(p);
if(d < 0.001) break; // hit
if(t > 50.0) break; // miss
t += d; // safe step (Lipschitz-1)
}
Rule of thumb: only use
t += dwhen every operation inmap()is distance-preserving (exact primitives + exact booleans). Domain repetitions, twists, and non-uniform scaling break Lipschitz-1 โ dividedby a factor.
2. Primitives (copy these)
3D (from iQ distfunctions)
float sdSphere(vec3 p, float r){ return length(p) - r; }
float sdBox(vec3 p, vec3 b){
vec3 q = abs(p) - b;
return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}
float sdTorus(vec3 p, vec2 t){
vec2 q = vec2(length(p.xz) - t.x, p.y);
return length(q) - t.y;
}
float sdCapsule(vec3 p, vec3 a, vec3 b, float r){
vec3 pa = p-a, ba = b-a;
float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
return length(pa - ba*h) - r;
}
float sdCylinder(vec3 p, float h, float r){
vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(r, h);
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
float sdPlane(vec3 p, vec3 n, float h){ return dot(p,n) + h; }
2D (from iQ distfunctions2d)
float sdCircle(vec2 p, float r){ return length(p) - r; }
float sdBox(vec2 p, vec2 b){
vec2 q = abs(p) - b;
return length(max(q,0.0)) + min(max(q.x,q.y),0.0);
}
float sdSegment(vec2 p, vec2 a, vec2 b){
vec2 pa = p-a, ba = b-a;
float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
return length(pa - ba*h);
}
Smooth versions (
smin) are what make SDF art look organic โ see ยง3.
3. Operations (combine primitives)
Boolean (exact, distance-preserving)
float opUnion(float a, float b){ return min(a,b); }
float opSubtract(float a, float b){ return max(-a, b); } // remove b from a
float opIntersect(float a, float b){ return max(a,b); }
Smooth union (organic blends)
float smin(float a, float b, float k){
float h = clamp(0.5 + 0.5*(b-a)/k, 0.0, 1.0);
return mix(b, a, h) - k*h*(1.0-h);
}
// opSmoothUnion(a,b,k) = smin(a,b,k)
Domain ops (warp space BEFORE evaluating)
// repetition (infinite grid) โ divide d by cell count to stay marchable
vec3 q = p;
q.x = mod(p.x+0.5*s, s) - 0.5*s; // repeat every s along x
// rotation about Y
mat2 rot(float a){ float c=cos(a),s=sin(a); return mat2(c,-s,s,c); }
// twist (non-uniform โ reduce step size: t += d*0.5)
float twist(vec3 p){ float c=cos(p.y),s=sin(p.y); mat2 m=mat2(c,-s,s,c);
vec3 q=vec3(m*p.xz, p.y); return map(q); }
Skin memory: every domain op that isn't a pure rotation/translation needs a step-size divisor (
t += d * 0.5or smaller) or the ray overshoots.
4. The workbench recepy (recombine)
| Goal | Combine |
|---|---|
| Blobby creature | smin(sphere A, sphere B, k) + smin(.., capsule, k) |
| Mechanical part | opSubtract(box, cylinder) (bolt holes) |
| Endless field | mod() repetition of one primitive |
| Ring / donut | sdTorus directly |
| Double-orb | two sdSpheres via opUnion/smin (see ShaderToy 4lyfzw) |
| Box-with-hole | opSubtract(sdBox, sdSphere) |
| Twisted column | twist(p) โ sdBox |
ShaderToy studied here: 4lyfzw (double-sphere), Ml3fWj (sdBox field),
Mt3BDj (torus). The MlcBDj/ltcfDj/lt3BW2/Xds3zN pages are
untitled stubs on ShaderToy (no code) โ treat as empty references.
5. Lighting / shading (make it read as 3D)
vec3 calcNormal(vec3 p){
vec2 e = vec2(0.001, 0.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)));
}
// Lambert + a rim term:
vec3 n = calcNormal(p);
float diff = clamp(dot(n, lightDir), 0.0, 1.0);
vec3 col = baseColor * (0.2 + 0.8*diff);
6. Drop-in agent instruction (paste into a generation prompt)
"Generate a raymarched SDF scene. Define
map(p)from primitives (sdSphere,sdBox,sdTorus,sdCapsule) combined with exact booleans (opUnion/opSubtract/opIntersect) andsminfor organic blends. Use domain repetition/mod()for fields androt()for orientation. Keep all ops distance-preserving; if you twist or non-uniformly scale, divide the march step by ~0.5. Shade with a normal-based Lambert + rim. Output GLSL."
7. Why this "skill" helps an agent
- Closures, not raw links: the agent gets primitives + ops + the one gotcha (Lipschitz step size) instead of a reading list.
- Composable: any object = primitives ร booleans ร domain warps.
- Portable: the ยง6 block is self-contained for a text-to-shader prompt.