Files
skills/shader-dev/techniques/sdf-3d.md
shihao 6487becf60 Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:52:49 +08:00

22 KiB

3D Signed Distance Fields (3D SDF) Skill

Use Cases

  • Real-time rendering of 3D geometry in ShaderToy / fragment shaders (no traditional meshes needed)
  • Complex scenes composed from basic primitives (sphere, box, cylinder, torus, etc.)
  • Smooth organic blending (character modeling, fluid blobs, biological forms)
  • Infinitely repeating architectural/pattern structures (corridors, gear arrays, grids)
  • Precise boolean operations (drilling holes, cutting, intersection) for sculpting geometry

Core Principles

An SDF returns the signed distance from any point in space to the nearest surface: positive = outside, negative = inside, zero = surface.

Sphere Tracing: advance along a ray, stepping by the current SDF value (the safe marching distance) at each step. The SDF guarantees no surface exists within that radius. A hit is registered when the distance falls below epsilon.

Key math:

  • Sphere: f(p) = |p| - r
  • Box: f(p) = |max(|p|-b, 0)| + min(max(|p-b|), 0)
  • Union: min(d1, d2) / Subtraction: max(d1, -d2)
  • Smooth union: min(d1,d2) - h^2/4k, h = max(k-|d1-d2|, 0)
  • Normal = SDF gradient: n = normalize(gradient of f(p)) (finite difference approximation)

Rendering Pipeline Overview

  1. SDF Primitive Library -- sdSphere, sdBox, sdEllipsoid, sdTorus, sdCapsule, sdCylinder
  2. Boolean Operations -- opUnion/opSubtraction/opIntersection + smooth variants smin/smax
  3. Scene Definition -- map(p) returns vec2(distance, materialID), combining all primitives
  4. Ray Marching -- raycast(ro, rd) sphere tracing loop (128 steps, adaptive threshold SURF_DIST * t)
  5. Normal Calculation -- tetrahedral differencing (4 map calls, ZERO macro to prevent inlining)
  6. Soft Shadows -- quadratic stepping with k*h/t to estimate occlusion softness, Hermite smoothing
  7. Ambient Occlusion -- 5-layer sampling along the normal, comparing SDF values with expected distances
  8. Camera + Rendering -- look-at matrix, multiple lights (sun + sky + SSS), gamma correction, fog

Full Code Template

Runs directly in ShaderToy. Includes multi-primitive scene, smooth blending, soft shadows, AO, and material system.

IMPORTANT: When using the vec2(distance, materialID) material system, smin needs to handle vec2 types. The template includes a vec2 smin(vec2 a, vec2 b, float k) overload that ensures the material ID is correctly passed through during smooth blending (taking the material of the closer distance).

// 3D SDF Full Rendering Pipeline Template - Runs in ShaderToy
#define AA 1                // Anti-aliasing (1=off, 2=4xAA, 3=9xAA)
#define MAX_STEPS 128
#define MAX_DIST 40.0
#define SURF_DIST 0.0001
#define SHADOW_STEPS 24
#define SHADOW_SOFTNESS 8.0
#define SMOOTH_K 0.3
#define ZERO (min(iFrame, 0))

// === SDF Primitives ===
float sdSphere(vec3 p, float r) { return length(p) - r; }
float sdBox(vec3 p, vec3 b) {
    vec3 d = abs(p) - b;
    return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}
float sdEllipsoid(vec3 p, vec3 r) {
    float k0 = length(p / r); float k1 = length(p / (r * r));
    return k0 * (k0 - 1.0) / k1;
}
float sdTorus(vec3 p, vec2 t) {
    return length(vec2(length(p.xz) - t.x, p.y)) - 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, vec2 h) {
    vec2 d = abs(vec2(length(p.xz), p.y)) - h;
    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}

// === Extended SDF Primitives ===
float sdRoundBox(vec3 p, vec3 b, float r) {
    vec3 q = abs(p) - b + r;
    return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - r;
}

float sdBoxFrame(vec3 p, vec3 b, float e) {
    p = abs(p) - b;
    vec3 q = abs(p + e) - e;
    return min(min(
        length(max(vec3(p.x, q.y, q.z), 0.0)) + min(max(p.x, max(q.y, q.z)), 0.0),
        length(max(vec3(q.x, p.y, q.z), 0.0)) + min(max(q.x, max(p.y, q.z)), 0.0)),
        length(max(vec3(q.x, q.y, p.z), 0.0)) + min(max(q.x, max(q.y, p.z)), 0.0));
}

float sdCone(vec3 p, vec2 c, float h) {
    vec2 q = h * vec2(c.x / c.y, -1.0);
    vec2 w = vec2(length(p.xz), p.y);
    vec2 a = w - q * clamp(dot(w, q) / dot(q, q), 0.0, 1.0);
    vec2 b = w - q * vec2(clamp(w.x / q.x, 0.0, 1.0), 1.0);
    float k = sign(q.y);
    float d = min(dot(a, a), dot(b, b));
    float s = max(k * (w.x * q.y - w.y * q.x), k * (w.y - q.y));
    return sqrt(d) * sign(s);
}

float sdCappedCone(vec3 p, float h, float r1, float r2) {
    vec2 q = vec2(length(p.xz), p.y);
    vec2 k1 = vec2(r2, h);
    vec2 k2 = vec2(r2 - r1, 2.0 * h);
    vec2 ca = vec2(q.x - min(q.x, (q.y < 0.0) ? r1 : r2), abs(q.y) - h);
    vec2 cb = q - k1 + k2 * clamp(dot(k1 - q, k2) / dot(k2, k2), 0.0, 1.0);
    float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0;
    return s * sqrt(min(dot(ca, ca), dot(cb, cb)));
}

float sdRoundCone(vec3 p, float r1, float r2, float h) {
    float b = (r1 - r2) / h;
    float a = sqrt(1.0 - b * b);
    vec2 q = vec2(length(p.xz), p.y);
    float k = dot(q, vec2(-b, a));
    if (k < 0.0) return length(q) - r1;
    if (k > a * h) return length(q - vec2(0.0, h)) - r2;
    return dot(q, vec2(a, b)) - r1;
}

float sdSolidAngle(vec3 p, vec2 c, float ra) {
    vec2 q = vec2(length(p.xz), p.y);
    float l = length(q) - ra;
    float m = length(q - c * clamp(dot(q, c), 0.0, ra));
    return max(l, m * sign(c.y * q.x - c.x * q.y));
}

float sdOctahedron(vec3 p, float s) {
    p = abs(p);
    float m = p.x + p.y + p.z - s;
    vec3 q;
    if (3.0 * p.x < m) q = p.xyz;
    else if (3.0 * p.y < m) q = p.yzx;
    else if (3.0 * p.z < m) q = p.zxy;
    else return m * 0.57735027;
    float k = clamp(0.5 * (q.z - q.y + s), 0.0, s);
    return length(vec3(q.x, q.y - s + k, q.z - k));
}

float sdPyramid(vec3 p, float h) {
    float m2 = h * h + 0.25;
    p.xz = abs(p.xz);
    p.xz = (p.z > p.x) ? p.zx : p.xz;
    p.xz -= 0.5;
    vec3 q = vec3(p.z, h * p.y - 0.5 * p.x, h * p.x + 0.5 * p.y);
    float s = max(-q.x, 0.0);
    float t = clamp((q.y - 0.5 * p.z) / (m2 + 0.25), 0.0, 1.0);
    float a = m2 * (q.x + s) * (q.x + s) + q.y * q.y;
    float b = m2 * (q.x + 0.5 * t) * (q.x + 0.5 * t) + (q.y - m2 * t) * (q.y - m2 * t);
    float d2 = min(q.y, -q.x * m2 - q.y * 0.5) > 0.0 ? 0.0 : min(a, b);
    return sqrt((d2 + q.z * q.z) / m2) * sign(max(q.z, -p.y));
}

float sdHexPrism(vec3 p, vec2 h) {
    const vec3 k = vec3(-0.8660254, 0.5, 0.57735);
    p = abs(p);
    p.xy -= 2.0 * min(dot(k.xy, p.xy), 0.0) * k.xy;
    vec2 d = vec2(length(p.xy - vec2(clamp(p.x, -k.z * h.x, k.z * h.x), h.x)) * sign(p.y - h.x), p.z - h.y);
    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}

float sdCutSphere(vec3 p, float r, float h) {
    float w = sqrt(r * r - h * h);
    vec2 q = vec2(length(p.xz), p.y);
    float s = max((h - r) * q.x * q.x + w * w * (h + r - 2.0 * q.y), h * q.x - w * q.y);
    return (s < 0.0) ? length(q) - r : (q.x < w) ? h - q.y : length(q - vec2(w, h));
}

float sdCappedTorus(vec3 p, vec2 sc, float ra, float rb) {
    p.x = abs(p.x);
    float k = (sc.y * p.x > sc.x * p.y) ? dot(p.xy, sc) : length(p.xy);
    return sqrt(dot(p, p) + ra * ra - 2.0 * ra * k) - rb;
}

float sdLink(vec3 p, float le, float r1, float r2) {
    vec3 q = vec3(p.x, max(abs(p.y) - le, 0.0), p.z);
    return length(vec2(length(q.xy) - r1, q.z)) - r2;
}

float sdPlane(vec3 p, vec3 n, float h) {
    return dot(p, n) + h;
}

float sdRhombus(vec3 p, float la, float lb, float h, float ra) {
    p = abs(p);
    vec2 b = vec2(la, lb);
    float f = clamp((dot(b, b - 2.0 * p.xz)) / dot(b, b), -1.0, 1.0);
    vec2 q = vec2(length(p.xz - 0.5 * b * vec2(1.0 - f, 1.0 + f)) * sign(p.x * b.y + p.z * b.x - b.x * b.y) - ra, p.y - h);
    return min(max(q.x, q.y), 0.0) + length(max(q, 0.0));
}

// Unsigned distance (exact)
float udTriangle(vec3 p, vec3 a, vec3 b, vec3 c) {
    vec3 ba = b - a; vec3 pa = p - a;
    vec3 cb = c - b; vec3 pb = p - b;
    vec3 ac = a - c; vec3 pc = p - c;
    vec3 nor = cross(ba, ac);
    return sqrt(
        (sign(dot(cross(ba, nor), pa)) +
         sign(dot(cross(cb, nor), pb)) +
         sign(dot(cross(ac, nor), pc)) < 2.0)
        ? min(min(
            dot(ba * clamp(dot(ba, pa) / dot(ba, ba), 0.0, 1.0) - pa,
                ba * clamp(dot(ba, pa) / dot(ba, ba), 0.0, 1.0) - pa),
            dot(cb * clamp(dot(cb, pb) / dot(cb, cb), 0.0, 1.0) - pb,
                cb * clamp(dot(cb, pb) / dot(cb, cb), 0.0, 1.0) - pb)),
            dot(ac * clamp(dot(ac, pc) / dot(ac, ac), 0.0, 1.0) - pc,
                ac * clamp(dot(ac, pc) / dot(ac, ac), 0.0, 1.0) - pc))
        : dot(nor, pa) * dot(nor, pa) / dot(nor, nor));
}

// === Boolean Operations ===
vec2 opU(vec2 d1, vec2 d2) { return (d1.x < d2.x) ? d1 : d2; }
float smin(float a, float b, float k) {
    float h = max(k - abs(a - b), 0.0);
    return min(a, b) - h * h * 0.25 / k;
}
vec2 smin(vec2 a, vec2 b, float k) {
    // vec2 smin: x=distance (smooth blend), y=materialID (take material of closer distance)
    float h = max(k - abs(a.x - b.x), 0.0);
    float d = min(a.x, b.x) - h * h * 0.25 / k;
    float m = (a.x < b.x) ? a.y : b.y;
    return vec2(d, m);
}
float smax(float a, float b, float k) {
    float h = max(k - abs(a - b), 0.0);
    return max(a, b) + h * h * 0.25 / k;
}

// === Deformation Operators ===

// Round: soften edges of any SDF
// Usage: sdRound(sdBox(p, vec3(1.0)), 0.1)
float opRound(float d, float r) { return d - r; }

// Onion: hollow out any SDF into a shell
// Usage: opOnion(sdSphere(p, 1.0), 0.1) — sphere shell of thickness 0.1
float opOnion(float d, float t) { return abs(d) - t; }

// Elongate: stretch a shape along axes
// Usage: elongate a sphere into a capsule-like shape
float opElongate(in vec3 p, in vec3 h, in vec3 center, in vec3 size) {
    // Generic elongation: subtract h from abs(p), clamp to 0
    vec3 q = abs(p) - h;
    // Then evaluate original SDF with max(q, 0.0)
    // Return: sdOriginal(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0)
    return sdBox(max(q, 0.0), size) + min(max(q.x, max(q.y, q.z)), 0.0); // example with box
}

// Twist: rotate around Y axis based on height
vec3 opTwist(vec3 p, float k) {
    float c = cos(k * p.y);
    float s = sin(k * p.y);
    mat2 m = mat2(c, -s, s, c);
    return vec3(m * p.xz, p.y);
}

// Cheap Bend: bend along X axis based on X position
vec3 opCheapBend(vec3 p, float k) {
    float c = cos(k * p.x);
    float s = sin(k * p.x);
    mat2 m = mat2(c, -s, s, c);
    vec2 q = m * p.xy;
    return vec3(q, p.z);
}

// Displacement: add procedural detail to surface
float opDisplace(float d, vec3 p) {
    float displacement = sin(20.0 * p.x) * sin(20.0 * p.y) * sin(20.0 * p.z);
    return d + displacement * 0.02;
}

// === 2D-to-3D Constructors ===

// Revolution: rotate a 2D SDF around the Y axis to create a 3D solid of revolution
// sdf2d: any 2D SDF function, o: offset from axis
float opRevolution(vec3 p, float sdf2d_result, float o) {
    vec2 q = vec2(length(p.xz) - o, p.y);
    // Example: revolve a 2D circle to make a torus
    // float d2d = length(q) - 0.3;  // 2D circle as cross-section
    // return d2d;
    return sdf2d_result; // pass pre-computed 2D SDF of vec2(length(p.xz)-o, p.y)
}

// Extrusion: extend a 2D SDF along the Z axis with finite height
float opExtrusion(vec3 p, float d2d, float h) {
    vec2 w = vec2(d2d, abs(p.z) - h);
    return min(max(w.x, w.y), 0.0) + length(max(w, 0.0));
}

// Usage example: extruded 2D star
// float d2d = sdStar2D(p.xy, 0.5, 5, 2.0);  // any 2D SDF
// float d3d = opExtrusion(p, d2d, 0.2);       // extrude 0.2 units

// === Symmetry Operators ===

// Mirror across X axis (most common — bilateral symmetry)
// Place this at the beginning of map() to model only one half
vec3 opSymX(vec3 p) { p.x = abs(p.x); return p; }

// Mirror across X and Z (four-fold symmetry)
vec3 opSymXZ(vec3 p) { p.xz = abs(p.xz); return p; }

// Mirror across arbitrary direction
vec3 opMirror(vec3 p, vec3 dir) {
    return p - 2.0 * dir * max(dot(p, dir), 0.0);
}

// === Scene ===
vec2 map(vec3 pos) {
    vec2 res = vec2(pos.y, 0.0);
    // Animated blob cluster
    float dBlob = 2.0;
    for (int i = 0; i < 8; i++) {
        float fi = float(i);
        float t = iTime * (fract(fi * 412.531 + 0.513) - 0.5) * 2.0;
        vec3 offset = sin(t + fi * vec3(52.5126, 64.627, 632.25)) * vec3(2.0, 2.0, 0.8);
        float radius = mix(0.3, 0.6, fract(fi * 412.531 + 0.5124));
        dBlob = smin(dBlob, sdSphere(pos + offset, radius), SMOOTH_K);
    }
    res = opU(res, vec2(dBlob, 1.0));
    float dBox = sdBox(pos - vec3(3.0, 0.4, 0.0), vec3(0.3, 0.4, 0.3));
    res = opU(res, vec2(dBox, 2.0));
    float dTorus = sdTorus((pos - vec3(-3.0, 0.5, 0.0)).xzy, vec2(0.4, 0.1));
    res = opU(res, vec2(dTorus, 3.0));
    // CSG subtraction: sphere minus box
    float dCSG = sdSphere(pos - vec3(0.0, 0.5, 3.0), 0.5);
    dCSG = max(dCSG, -sdBox(pos - vec3(0.0, 0.5, 3.0), vec3(0.3)));
    res = opU(res, vec2(dCSG, 4.0));
    return res;
}

// === Normals ===
vec3 calcNormal(vec3 pos) {
    vec3 n = vec3(0.0);
    for (int i = ZERO; i < 4; i++) {
        vec3 e = 0.5773 * (2.0 * vec3((((i+3)>>1)&1), ((i>>1)&1), (i&1)) - 1.0);
        n += e * map(pos + 0.0005 * e).x;
    }
    return normalize(n);
}

// === Shadows ===
float calcSoftshadow(vec3 ro, vec3 rd, float mint, float tmax) {
    float res = 1.0, t = mint;
    for (int i = ZERO; i < SHADOW_STEPS; i++) {
        float h = map(ro + rd * t).x;
        float s = clamp(SHADOW_SOFTNESS * h / t, 0.0, 1.0);
        res = min(res, s);
        t += clamp(h, 0.01, 0.2);
        if (res < 0.004 || t > tmax) break;
    }
    res = clamp(res, 0.0, 1.0);
    return res * res * (3.0 - 2.0 * res);
}

// === AO ===
float calcAO(vec3 pos, vec3 nor) {
    float occ = 0.0, sca = 1.0;
    for (int i = ZERO; i < 5; i++) {
        float h = 0.01 + 0.12 * float(i) / 4.0;
        float d = map(pos + h * nor).x;
        occ += (h - d) * sca;
        sca *= 0.95;
        if (occ > 0.35) break;
    }
    return clamp(1.0 - 3.0 * occ, 0.0, 1.0) * (0.5 + 0.5 * nor.y);
}

// === Ray Marching ===
vec2 raycast(vec3 ro, vec3 rd) {
    vec2 res = vec2(-1.0);
    float t = 0.01;
    for (int i = 0; i < MAX_STEPS && t < MAX_DIST; i++) {
        vec2 h = map(ro + rd * t);
        if (abs(h.x) < SURF_DIST * t) { res = vec2(t, h.y); break; }
        t += h.x;
    }
    return res;
}

// === Camera ===
mat3 setCamera(vec3 ro, vec3 ta, float cr) {
    vec3 cw = normalize(ta - ro);
    vec3 cp = vec3(sin(cr), cos(cr), 0.0);
    vec3 cu = normalize(cross(cw, cp));
    vec3 cv = cross(cu, cw);
    return mat3(cu, cv, cw);
}

// === Rendering ===
vec3 render(vec3 ro, vec3 rd) {
    vec3 col = vec3(0.7, 0.7, 0.9) - max(rd.y, 0.0) * 0.3;
    vec2 res = raycast(ro, rd);
    float t = res.x, m = res.y;
    if (m > -0.5) {
        vec3 pos = ro + t * rd;
        vec3 nor = (m < 0.5) ? vec3(0.0, 1.0, 0.0) : calcNormal(pos);
        vec3 ref = reflect(rd, nor);
        vec3 mate = 0.2 + 0.2 * sin(m * 2.0 + vec3(0.0, 1.0, 2.0));
        if (m < 0.5) mate = vec3(0.15);
        float occ = calcAO(pos, nor);
        vec3 lin = vec3(0.0);
        // Key light
        {
            vec3 lig = normalize(vec3(-0.5, 0.4, -0.6));
            vec3 hal = normalize(lig - rd);
            float dif = clamp(dot(nor, lig), 0.0, 1.0);
            dif *= calcSoftshadow(pos, lig, 0.02, 2.5);
            float spe = pow(clamp(dot(nor, hal), 0.0, 1.0), 16.0);
            spe *= dif * (0.04 + 0.96 * pow(clamp(1.0 - dot(hal, lig), 0.0, 1.0), 5.0));
            lin += mate * 2.20 * dif * vec3(1.30, 1.00, 0.70);
            lin += 5.00 * spe * vec3(1.30, 1.00, 0.70);
        }
        // Sky light
        {
            float dif = sqrt(clamp(0.5 + 0.5 * nor.y, 0.0, 1.0)) * occ;
            lin += mate * 0.60 * dif * vec3(0.40, 0.60, 1.15);
        }
        // Subsurface scattering approximation
        {
            float dif = pow(clamp(1.0 + dot(nor, rd), 0.0, 1.0), 2.0) * occ;
            lin += mate * 0.25 * dif;
        }
        col = lin;
        col = mix(col, vec3(0.7, 0.7, 0.9), 1.0 - exp(-0.0001 * t * t * t));
    }
    return clamp(col, 0.0, 1.0);
}

// === Main Function ===
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 mo = iMouse.xy / iResolution.xy;
    float time = 32.0 + iTime * 1.5;
    vec3 ta = vec3(0.0, 0.0, 0.0);
    vec3 ro = ta + vec3(4.5 * cos(0.1 * time + 7.0 * mo.x), 2.2,
                        4.5 * sin(0.1 * time + 7.0 * mo.x));
    mat3 ca = setCamera(ro, ta, 0.0);
    vec3 tot = vec3(0.0);
#if AA > 1
    for (int m = ZERO; m < AA; m++)
    for (int n = ZERO; n < AA; n++) {
        vec2 o = vec2(float(m), float(n)) / float(AA) - 0.5;
        vec2 p = (2.0 * (fragCoord + o) - iResolution.xy) / iResolution.y;
#else
        vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
#endif
        vec3 rd = ca * normalize(vec3(p, 2.5));
        vec3 col = render(ro, rd);
        col = pow(col, vec3(0.4545));
        tot += col;
#if AA > 1
    }
    tot /= float(AA * AA);
#endif
    fragColor = vec4(tot, 1.0);
}

Common Variants

Variant 1: Dynamic Organic Body (Smooth Blob Animation)

vec2 map(vec3 p) {
    float d = 2.0;
    for (int i = 0; i < 16; i++) {
        float fi = float(i);
        float t = iTime * (fract(fi * 412.531 + 0.513) - 0.5) * 2.0;
        d = smin(sdSphere(p + sin(t + fi * vec3(52.5126, 64.627, 632.25)) * vec3(2.0, 2.0, 0.8),
                          mix(0.5, 1.0, fract(fi * 412.531 + 0.5124))), d, 0.4);
    }
    return vec2(d, 1.0);
}

Variant 2: Infinite Repeating Corridor (Domain Repetition)

float repeat(float v, float c) { return mod(v, c) - c * 0.5; }

float amod(inout vec2 p, float count) {
    float an = 6.283185 / count;
    float a = atan(p.y, p.x) + an * 0.5;
    float c = floor(a / an);
    a = mod(a, an) - an * 0.5;
    p = vec2(cos(a), sin(a)) * length(p);
    return c;
}

vec2 map(vec3 p) {
    p.z = repeat(p.z, 4.0);
    p.x += 2.0 * sin(p.z * 0.1);
    float d = -sdBox(p, vec3(2.0, 2.0, 20.0));
    d = max(d, -sdBox(p, vec3(1.8, 1.8, 1.9)));
    d = min(d, sdCylinder(p - vec3(1.5, -2.0, 0.0), vec2(0.1, 2.0)));
    return vec2(d, 1.0);
}

Variant 3: Character/Creature Modeling

vec2 sdStick(vec3 p, vec3 a, vec3 b, float r1, float r2) {
    vec3 pa = p - a, ba = b - a;
    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
    return vec2(length(pa - ba * h) - mix(r1, r2, h * h * (3.0 - 2.0 * h)), h);
}

vec2 map(vec3 pos) {
    float d = sdEllipsoid(pos, vec3(0.25, 0.3, 0.25));         // body
    d = smin(d, sdEllipsoid(pos - vec3(0.0, 0.35, 0.02),
             vec3(0.12, 0.15, 0.13)), 0.1);                     // head
    vec2 arm = sdStick(abs(pos.x) > 0.0 ? vec3(abs(pos.x), pos.yz) : pos,
                       vec3(0.18, 0.2, -0.05), vec3(0.35, -0.1, -0.15), 0.03, 0.05);
    d = smin(d, arm.x, 0.04);                                   // arms
    d = smax(d, -sdEllipsoid(pos - vec3(0.0, 0.3, 0.15),
             vec3(0.08, 0.03, 0.1)), 0.03);                     // mouth carving
    return vec2(d, 1.0);
}

Variant 4: Symmetry Optimization

vec2 rot45(vec2 v) { return vec2(v.x - v.y, v.y + v.x) * 0.707107; }

vec2 map(vec3 p) {
    float d = sdSphere(p, 0.12);
    // Octahedral symmetry: 18-gear evaluations reduced to 4
    vec3 qx = vec3(rot45(p.zy), p.x);
    if (abs(qx.x) > abs(qx.y)) qx = qx.zxy;
    vec3 qy = vec3(rot45(p.xz), p.y);
    if (abs(qy.x) > abs(qy.y)) qy = qy.zxy;
    vec3 qz = vec3(rot45(p.yx), p.z);
    if (abs(qz.x) > abs(qz.y)) qz = qz.zxy;
    vec3 qa = abs(p);
    qa = (qa.x > qa.y && qa.x > qa.z) ? p.zxy : (qa.z > qa.y) ? p.yzx : p.xyz;
    d = min(d, min(min(gear(qa, 0.0), gear(qx, 1.0)), min(gear(qy, 1.0), gear(qz, 1.0))));
    return vec2(d, 1.0);
}

Variant 5: PBR Material Rendering

float D_GGX(float NoH, float roughness) {
    float a = roughness * roughness; float a2 = a * a;
    float d = NoH * NoH * (a2 - 1.0) + 1.0;
    return a2 / (3.14159 * d * d);
}
vec3 F_Schlick(float VoH, vec3 f0) {
    return f0 + (1.0 - f0) * pow(1.0 - VoH, 5.0);
}
vec3 pbrLighting(vec3 pos, vec3 nor, vec3 rd, vec3 albedo, float roughness, float metallic) {
    vec3 lig = normalize(vec3(-0.5, 0.4, -0.6));
    vec3 hal = normalize(lig - rd);
    vec3 f0 = mix(vec3(0.04), albedo, metallic);
    float NoL = max(dot(nor, lig), 0.0);
    float NoH = max(dot(nor, hal), 0.0);
    float VoH = max(dot(-rd, hal), 0.0);
    vec3 spec = D_GGX(NoH, roughness) * F_Schlick(VoH, f0) * 0.25;
    vec3 diff = albedo * (1.0 - metallic) / 3.14159;
    float shadow = calcSoftshadow(pos, lig, 0.02, 2.5);
    return (diff + spec) * NoL * shadow * vec3(1.3, 1.0, 0.7) * 3.0;
}

Performance & Composition

Performance Optimization Tips

  • Bounding volume acceleration: test ray against AABB first to narrow tmin/tmax, avoiding wasted steps in empty regions
  • Sub-scene bounding: in map(), use a cheap sdBox to check proximity before computing the precise SDF
  • Adaptive step size: abs(h.x) < SURF_DIST * t -- looser tolerance at distance, stricter up close
  • Prevent compiler inlining: #define ZERO (min(iFrame, 0)) + loop prevents calcNormal from inlining map 4 times
  • Exploit symmetry: fold into the fundamental domain, reducing 18 evaluations to 4

Common Composition Techniques

  • Noise displacement: d += 0.05 * sin(p.x*10.)*sin(p.y*10.)*sin(p.z*10.) adds organic detail; breaks the Lipschitz condition, so step size should be multiplied by 0.5~0.7
  • Bump mapping: perturb only during normal calculation, leaving ray marching unaffected for better performance
  • Domain transforms: warp coordinates before entering map (bending, polar coordinate transforms, etc.)
  • Procedural animation: bone angles driven by time to position primitives, smin ensures smooth joints
  • Motion blur: multi-frame temporal sampling averaged

Further Reading

Full step-by-step tutorials, mathematical derivations, and advanced usage in reference