Files
skills/shader-dev/techniques/domain-repetition.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

9.7 KiB

Domain Repetition & Space Folding

Use Cases

  • Infinite repeating scenes: render infinitely extending geometry from a single SDF primitive (corridors, cities, star fields)
  • Kaleidoscope/symmetry effects: N-fold rotational symmetry, mirror symmetry, polyhedral symmetry
  • Fractal geometry: generate self-similar structures through iterative space folding (Apollonian, Kali-set)
  • Architectural/mechanical structures: build complex yet regular scenes using repetition + variation
  • Spiral/toroidal topology: repeat geometry along polar or spiral paths

Core value: define geometry in a single cell, render infinite space.

Core Principles

The essence of domain repetition is coordinate transformation: before computing the SDF, fold/map point p into a finite "fundamental domain".

Three fundamental operations:

Operation Formula Effect
mod repetition p = mod(p + c/2, c) - c/2 Infinite translational repetition along an axis
abs mirroring p = abs(p) Mirror symmetry across an axis plane
Rotational folding angle = mod(atan(p.y,p.x), TAU/N) N-fold rotational symmetry

Key math: mod(x,c) -> periodic mapping to [0,c); abs(x) -> reflection symmetry; fract(x) = mod(x,1.0) -> normalized period.

Implementation Steps

Step 1: Cartesian Domain Repetition (mod repetition)

// Infinite translational repetition along one or more axes
vec3 domainRepeat(vec3 p, vec3 period) {
    return mod(p + period * 0.5, period) - period * 0.5;
}

float map(vec3 p) {
    vec3 q = domainRepeat(p, vec3(4.0)); // repeat every 4 units
    return sdBox(q, vec3(0.5));
}

Step 2: Symmetric Folding (abs-mod triangle wave)

// Boundary-continuous symmetric folding, coordinates oscillate 0->tile->0
vec3 symmetricFold(vec3 p, float tile) {
    return abs(vec3(tile) - mod(p, vec3(tile * 2.0)));
}

// Star Nest classic usage
p = abs(vec3(tile) - mod(p, vec3(tile * 2.0)));

Step 3: Angular Domain Repetition (Polar Coordinate Folding)

// N-way rotational symmetry (kaleidoscope)
vec2 pmod(vec2 p, float count) {
    float angle = atan(p.x, p.y) + PI / count;
    float sector = TAU / count;
    angle = floor(angle / sector) * sector;
    return p * rot(-angle);
}

p1.xy = pmod(p1.xy, 5.0); // 5-fold symmetry

Step 4: fract Domain Folding (Fractal Iteration)

// Apollonian fractal core loop
float map(vec3 p, float s) {
    float scale = 1.0;
    vec4 orb = vec4(1000.0);

    for (int i = 0; i < 8; i++) {
        p = -1.0 + 2.0 * fract(0.5 * p + 0.5); // centered fract folding
        float r2 = dot(p, p);
        orb = min(orb, vec4(abs(p), r2));
        float k = s / r2;    // spherical inversion scaling
        p *= k;
        scale *= k;
    }
    return 0.25 * abs(p.y) / scale;
}

Step 5: Iterative abs Folding (IFS / Kali-set)

// IFS abs folding fractal
float ifsBox(vec3 p) {
    for (int i = 0; i < 5; i++) {
        p = abs(p) - 1.0;
        p.xy *= rot(iTime * 0.3);
        p.xz *= rot(iTime * 0.1);
    }
    return sdBox(p, vec3(0.4, 0.8, 0.3));
}

// Kali-set variant: mod repetition + IFS + dot(p,p) scaling
vec2 de(vec3 pos) {
    vec3 tpos = pos;
    tpos.xz = abs(0.5 - mod(tpos.xz, 1.0));
    vec4 p = vec4(tpos, 1.0);               // w tracks scaling
    for (int i = 0; i < 7; i++) {
        p.xyz = abs(p.xyz) - vec3(-0.02, 1.98, -0.02);
        p = p * (2.0) / clamp(dot(p.xyz, p.xyz), 0.4, 1.0)
            - vec4(0.5, 1.0, 0.4, 0.0);
        p.xz *= rot(0.416);
    }
    return vec2(length(max(abs(p.xyz)-vec3(0.1,5.0,0.1), 0.0)) / p.w, 0.0);
}

Step 6: Reflection Folding (Polyhedral Symmetry)

// Plane reflection
float pReflect(inout vec3 p, vec3 planeNormal, float offset) {
    float t = dot(p, planeNormal) + offset;
    if (t < 0.0) p = p - (2.0 * t) * planeNormal;
    return sign(t);
}

// Icosahedral folding
void pModIcosahedron(inout vec3 p) {
    vec3 nc = vec3(-0.5, -cos(PI/5.0), sqrt(0.75 - cos(PI/5.0)*cos(PI/5.0)));
    p = abs(p);
    pReflect(p, nc, 0.0);
    p.xy = abs(p.xy);
    pReflect(p, nc, 0.0);
    p.xy = abs(p.xy);
    pReflect(p, nc, 0.0);
}

Step 7: Toroidal/Cylindrical Domain Warping

// Bend the xz plane into a toroidal topology
vec2 displaceLoop(vec2 p, float radius) {
    return vec2(length(p) - radius, atan(p.y, p.x));
}

pDonut.xz = displaceLoop(pDonut.xz, donutRadius);
pDonut.z *= donutRadius; // unfold angle to linear length

Step 8: 1D Centered Domain Repetition (with Cell ID)

// Returns cell index, usable for random variations
float pMod1(inout float p, float size) {
    float halfsize = size * 0.5;
    float c = floor((p + halfsize) / size);
    p = mod(p + halfsize, size) - halfsize;
    return c;
}

float cellID = pMod1(p.x, 2.0);
float salt = fract(sin(cellID * 127.1) * 43758.5453);

Full Code Template

Combined demo: Cartesian repetition + angular repetition + IFS folding. Runs directly in ShaderToy.

#define PI 3.14159265359
#define TAU 6.28318530718
#define MAX_STEPS 100
#define MAX_DIST 50.0
#define SURF_DIST 0.001
#define PERIOD 4.0
#define ANGULAR_COUNT 6.0
#define IFS_ITERS 5
#define IFS_OFFSET 1.2

mat2 rot(float a) {
    float c = cos(a), s = sin(a);
    return mat2(c, s, -s, c);
}

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

vec3 domainRepeat(vec3 p, vec3 period) {
    return mod(p + period * 0.5, period) - period * 0.5;
}

vec2 pmod(vec2 p, float count) {
    float a = atan(p.x, p.y) + PI / count;
    float n = TAU / count;
    a = floor(a / n) * n;
    return p * rot(-a);
}

float map(vec3 p) {
    vec3 q = domainRepeat(p, vec3(PERIOD));
    q.xz = pmod(q.xz, ANGULAR_COUNT);
    for (int i = 0; i < IFS_ITERS; i++) {
        q = abs(q) - IFS_OFFSET;
        q.xy *= rot(0.785);
        q.yz *= rot(0.471);
    }
    return sdBox(q, vec3(0.15, 0.4, 0.15));
}

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)
    ));
}

float raymarch(vec3 ro, vec3 rd) {
    float t = 0.0;
    for (int i = 0; i < MAX_STEPS; i++) {
        float d = map(ro + rd * t);
        if (d < SURF_DIST || t > MAX_DIST) break;
        t += d;
    }
    return t;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;

    float time = iTime * 0.5;
    vec3 ro = vec3(sin(time) * 6.0, 3.0 + sin(time * 0.7) * 2.0, cos(time) * 6.0);
    vec3 ta = vec3(0.0);
    vec3 ww = normalize(ta - ro);
    vec3 uu = normalize(cross(ww, vec3(0.0, 1.0, 0.0)));
    vec3 vv = cross(uu, ww);
    vec3 rd = normalize(uv.x * uu + uv.y * vv + 1.8 * ww);

    float t = raymarch(ro, rd);

    vec3 col = vec3(0.0);
    if (t < MAX_DIST) {
        vec3 p = ro + rd * t;
        vec3 n = calcNormal(p);
        vec3 lightDir = normalize(vec3(0.5, 0.8, -0.6));
        float diff = clamp(dot(n, lightDir), 0.0, 1.0);
        float amb = 0.5 + 0.5 * n.y;
        vec3 baseColor = 0.5 + 0.5 * cos(p * 0.5 + vec3(0.0, 2.0, 4.0));
        col = baseColor * (0.2 * amb + 0.8 * diff);
        col *= exp(-0.03 * t * t);
    }

    col = pow(col, vec3(0.4545));
    fragColor = vec4(col, 1.0);
}

Common Variants

1. Volumetric Light/Glow Rendering

float acc = 0.0, t = 0.0;
for (int i = 0; i < 99; i++) {
    float dist = map(ro + rd * t);
    dist = max(abs(dist), 0.02);
    acc += exp(-dist * 3.0);       // decay factor controls glow sharpness
    t += dist * 0.5;               // step scale <1 for denser sampling
}
vec3 col = vec3(acc * 0.01, acc * 0.011, acc * 0.012);

2. Single-Axis/Dual-Axis Selective Repetition

q.xz = mod(q.xz + 2.0, 4.0) - 2.0; // repeat only xz, y stays unchanged

3. Fractal fract Domain Folding (Apollonian Type)

float scale = 1.0;
for (int i = 0; i < 8; i++) {
    p = -1.0 + 2.0 * fract(0.5 * p + 0.5);
    float k = 1.2 / dot(p, p);
    p *= k;
    scale *= k;
}
return 0.25 * abs(p.y) / scale;

4. Multi-Layer Nested Repetition

float indexX = amod(p.xz, segments); // outer layer: angular repetition
p.x -= radius;
p.y = repeat(p.y, cellSize);         // inner layer: linear repetition
float salt = rng(vec2(indexX, floor(p.y / cellSize)));

5. Finite Domain Repetition (Clamp Limited)

vec3 domainRepeatLimited(vec3 p, float size, vec3 limit) {
    return p - size * clamp(floor(p / size + 0.5), -limit, limit);
}
// Repeat 5 times along x, 3 times along y/z
vec3 q = domainRepeatLimited(p, 2.0, vec3(2.0, 1.0, 1.0));

Performance & Composition Tips

Performance:

  • 5-8 fractal iterations are typically sufficient; use vec4.w to track scaling and avoid extra variables
  • Ensure geometry radius < period/2 to prevent inaccurate SDF at cell boundaries
  • Volumetric light step size should increase with distance: t += dist * (0.3 + t * 0.02)
  • Use clamp(dot(p,p), min, max) to prevent numerical explosion
  • Avoid normalize() inside loops; manually divide by length instead

Composition:

  • Domain Repetition + Ray Marching: the most fundamental combination, used by all reference shaders
  • Domain Repetition + Orbit Trap Coloring: record min(orb, abs(p)) during fractal iteration for coloring
  • Domain Repetition + Toroidal Warping: displaceLoop to bend space before applying linear/angular repetition
  • Domain Repetition + Noise Variation: cell ID -> pseudo-random number -> modulate geometry parameters
  • Domain Repetition + Polar Spiral: cartToPolar combined with pMod1 for spiral path repetition

Further Reading

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