590 lines
22 KiB
Markdown
590 lines
22 KiB
Markdown
# 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).
|
|
|
|
```glsl
|
|
// 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)
|
|
|
|
```glsl
|
|
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)
|
|
|
|
```glsl
|
|
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
|
|
|
|
```glsl
|
|
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
|
|
|
|
```glsl
|
|
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
|
|
|
|
```glsl
|
|
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](../reference/sdf-3d.md)
|