← snapshot
11523 bytes
/**
* Shader recipes for the orb lab.
*
* Every entry supplies a `map` (the distance field) and optionally a `shade`
* (how a hit is coloured). They are injected into one shared raymarching
* template, so the variants differ in the thing that actually matters — the
* geometry and its light — rather than in boilerplate.
*
* Available to every snippet: `uTime`, `uLevel` (0..1 speech), `uAttract`
* (0..1 interaction), `uLean` (pointer), `thread` (linear provenance colour),
* `tint`, and the helpers below.
*/
export const GLSL_PRELUDE = /* glsl */ `
mat2 rot(float a){ float c=cos(a), s=sin(a); return mat2(c,-s,s,c); }
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 sdOcta(vec3 p, float s){ p=abs(p); return (p.x+p.y+p.z-s)*0.5773502691; }
float sdCyl(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 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); }
float hash(vec3 p){ return fract(sin(dot(p, vec3(127.1,311.7,74.7)))*43758.5453); }
/** Self-similar displacement: doubling frequency, halving amplitude. */
float fbm(vec3 p, float spin){
float amp = 0.5, sum = 0.0;
for (int i = 0; i < 5; i++){
sum += amp * sin(p.x) * sin(p.y) * sin(p.z);
p = p * 2.02;
p.xy = rot(spin) * p.xy;
p.yz = rot(spin * 0.7) * p.yz;
amp *= 0.5;
}
return sum;
}
/** n-fold mirrored polar fold — kaleidoscope mirrors. */
vec3 foldN(vec3 p, float n){
float a = atan(p.z, p.x), r = length(p.xz);
float seg = 6.28318530718 / n;
a = mod(a + seg*0.5, seg) - seg*0.5;
a = abs(a);
return vec3(cos(a)*r, p.y, sin(a)*r);
}
float sigilRing(vec3 p, float radius, float thick, float n, float phase){
float a = atan(p.z, p.x) + phase;
float coarse = 0.5 + 0.5*cos(a*n);
float fine = 0.5 + 0.5*cos(a*n*3.0);
float notch = coarse * (0.72 + 0.28*fine);
vec2 q = vec2(length(p.xz) - (radius + notch*0.055), p.y);
return length(q) - thick * (0.18 + 1.45*notch);
}
`;
export interface GlslVariant {
id: string;
label: string;
blurb: string;
/** GLSL body of `vec2 mapAll(vec3 p)` — returns (distance, material). */
map: string;
/** Optional GLSL body run on a hit; sets `col`. */
shade?: string;
/** Camera distance from origin. */
dist?: number;
/** Marching steps; raise for thin or volumetric fields. */
steps?: number;
/** Step relaxation — lower for displaced or folded fields. */
relax?: number;
/**
* Smallest step the march will take. Surface fields want this tiny so they
* can settle onto a hit; volumetric fields want it large, because they never
* hit anything and would otherwise crawl a fraction of the way across the
* scene before running out of steps.
*/
minStep?: number;
}
export const GLSL_VARIANTS: GlslVariant[] = [
{
id: "soft-mandala",
label: "Soft mandala",
blurb: "Glowing core inside three counter-rotating sigil rings.",
dist: 4.15,
relax: 0.85,
map: `
vec3 q = foldN(p, 3.0) * 2.1 + vec3(0.0, uTime*0.22, 0.0);
float amp = 0.17 + uLevel*0.22 + uAttract*0.07;
float dc = (length(p) - (0.92 + amp*fbm(q, 0.55))) * 0.55;
float dr = 1e9;
for (int i=0;i<3;i++){
float fi=float(i);
vec3 r=p; float dir = mod(fi,2.0)<0.5 ? 1.0 : -1.0;
float sp = uTime*(0.22+fi*0.16)*dir + fi*1.7;
r.yz = rot(0.42+fi*0.62)*r.yz;
r.xz = rot(sp)*r.xz;
dr = min(dr, sigilRing(r, 1.32+fi*0.29, 0.030, 3.0*(1.0+fi), sp*2.0));
}
return dc < dr ? vec2(dc, 0.0) : vec2(dr, 1.0);`,
},
{
id: "voice-bloom",
label: "Voice bloom",
blurb: "A single breathing sphere, all bloom and no edges.",
dist: 3.1,
relax: 0.85,
map: `
vec3 q = foldN(p, 3.0)*2.3 + vec3(0.0, uTime*0.3, 0.0);
float amp = 0.16 + uLevel*0.30 + uAttract*0.06;
return vec2((length(p) - (1.0 + amp*fbm(q, 0.6)))*0.55, 0.0);`,
},
{
id: "mandala-only",
label: "Mandala",
blurb: "Rings alone — an armillary sigil with nothing at the centre.",
dist: 4.3,
relax: 0.9,
map: `
float dr = 1e9;
for (int i=0;i<5;i++){
float fi=float(i);
vec3 r=p; float dir = mod(fi,2.0)<0.5 ? 1.0 : -1.0;
float sp = uTime*(0.18+fi*0.13)*dir + fi*1.1;
r.yz = rot(0.2+fi*0.42)*r.yz;
r.xz = rot(sp)*r.xz;
dr = min(dr, sigilRing(r, 0.75+fi*0.32, 0.024+uLevel*0.012, 6.0+fi*4.0, sp*2.0));
}
return vec2(dr, 1.0);`,
},
{
id: "menger",
label: "Menger lattice",
blurb: "A punched card in three dimensions: holes all the way through.",
dist: 4.2,
relax: 0.85,
map: `
// The canonical sponge. Folding the space first (as a kaleidoscope
// would) breaks the distance bound and shatters the silhouette, so the
// rotation is applied to the whole solid instead.
vec3 q = p;
q.xz = rot(uTime*0.14)*q.xz;
q.xy = rot(0.62 + uLevel*0.10)*q.xy;
float d = sdBox(q, vec3(1.05));
// Three levels, not four: a fourth makes the finest holes 1/27 of the
// cube, which stops reading as perforation and starts reading as grain.
float s = 1.0;
for (int m=0;m<3;m++){
vec3 a = mod(q*s, 2.0) - 1.0;
s *= 3.0;
vec3 r = abs(1.0 - 3.0*abs(a));
float c = (min(max(r.x,r.y), min(max(r.y,r.z), max(r.z,r.x))) - 1.0)/s;
d = max(d, c);
}
return vec2(d, 0.0);`,
},
{
id: "mandelbulb",
label: "Mandelbulb",
blurb: "The classic power-eight bulb, breathing with the voice.",
dist: 3.0,
steps: 96,
relax: 0.75,
map: `
vec3 z = p; float dr = 1.0; float r = 0.0;
float power = 7.0 + sin(uTime*0.2)*1.0 + uLevel*1.5;
for (int i=0;i<7;i++){
r = length(z);
if (r > 2.0) break;
float th = acos(clamp(z.z/r,-1.0,1.0));
float ph = atan(z.y, z.x);
dr = pow(r, power-1.0)*power*dr + 1.0;
float zr = pow(r, power);
th *= power; ph *= power;
z = zr*vec3(sin(th)*cos(ph), sin(ph)*sin(th), cos(th)) + p;
}
return vec2(0.5*log(max(r,1e-6))*r/dr, 0.0);`,
},
{
id: "gyroid",
label: "Gyroid weave",
blurb: "A minimal surface that looks woven because, mathematically, it is.",
dist: 3.4,
relax: 0.6,
map: `
vec3 q = p; q.xz = rot(uTime*0.1)*q.xz;
float f = 3.0 + uLevel*1.2;
float g = dot(sin(q*f), cos(q.zxy*f));
float shell = length(p) - 1.35;
return vec2(max(shell, abs(g)*0.28 - 0.06), 0.0);`,
},
{
id: "kifs-tetra",
label: "KIFS tetra",
blurb: "Sierpinski folding — the sharpest, most crystalline option.",
dist: 4.4,
relax: 0.7,
map: `
// Sierpinski tetrahedron by nearest-vertex folding. Each pass scales by
// two toward the closest of the four corners, so the scale factor has to
// be divided back out at the end for the distance to stay honest.
vec3 v1 = vec3( 1.0, 1.0, 1.0);
vec3 v2 = vec3(-1.0, -1.0, 1.0);
vec3 v3 = vec3( 1.0, -1.0, -1.0);
vec3 v4 = vec3(-1.0, 1.0, -1.0);
vec3 q = p;
q.xz = rot(uTime*0.15)*q.xz;
q.yz = rot(0.35)*q.yz;
float s = 1.0;
for (int i=0;i<9;i++){
vec3 c = v1; float best = length(q - v1);
float d2 = length(q - v2); if (d2 < best){ c = v2; best = d2; }
float d3 = length(q - v3); if (d3 < best){ c = v3; best = d3; }
float d4 = length(q - v4); if (d4 < best){ c = v4; }
q = 2.0*q - c;
s *= 2.0;
}
return vec2(length(q)/s - (0.004 + uLevel*0.010), 0.0);`,
},
{
id: "wire-globe",
label: "Wire globe",
blurb: "A latitude/longitude cage — the loom's warp, closed into a sphere.",
dist: 3.3,
relax: 0.8,
map: `
vec3 q = p; q.xz = rot(uTime*0.16)*q.xz;
float R = 1.15 + uLevel*0.06;
float shell = abs(length(q) - R) - 0.012;
float lat = abs(sin(asin(clamp(q.y/max(length(q),1e-4),-1.0,1.0))*10.0));
float lon = abs(sin(atan(q.z,q.x)*12.0));
float cage = min(lat, lon);
return vec2(max(shell, cage*0.32 - 0.05), 1.0);`,
},
{
id: "tunnel",
label: "Kaleido shards",
blurb: "The mirror dimension: eight blades folded around a lit core.",
dist: 3.9,
steps: 90,
relax: 0.55,
map: `
vec3 q = p;
q.xz = rot(uTime*0.12)*q.xz;
q = foldN(q, 8.0);
q.x -= 1.05;
// Spin each blade about its own radial axis. Rotating in xy instead
// tips them all outward and the ring collapses into a crown.
q.yz = rot(uTime*0.35)*q.yz;
float blade = sdBox(q, vec3(0.05, 0.40 + uLevel*0.20, 0.20));
float core = sdSphere(p, 0.46 + uLevel*0.12);
float ring = sdTorus(p, vec2(1.05, 0.020));
float solid = min(blade, core);
return ring < solid ? vec2(ring, 1.0) : vec2(solid, 0.0);`,
},
{
id: "plasma-veil",
label: "Plasma veil",
blurb: "Volumetric cloud — no surface at all, only density.",
dist: 3.2,
steps: 120,
relax: 1.0,
// Nothing is ever hit here, so the march must stride rather than settle.
minStep: 0.045,
map: `
vec3 q = p*2.2;
q.y -= uTime*0.35;
q.xz = rot(uTime*0.15)*q.xz;
float n = fbm(q, 0.75);
float shell = length(p) - (1.28 + uLevel*0.14);
// Density, not surface: the marcher reads how far *inside* it is, so the
// noise has to shift that depth rather than carve a boundary.
float dens = 0.03 - n*(0.17 + uLevel*0.09);
return vec2(max(shell, dens), 2.0);`,
},
{
id: "torus-weave",
label: "Torus weave",
blurb: "Interlocking rings that pass over and under each other.",
dist: 3.9,
relax: 0.85,
map: `
float d = 1e9;
for (int i=0;i<4;i++){
float fi = float(i);
vec3 q = p;
q.xz = rot(uTime*0.18 + fi*1.5708)*q.xz;
q.yz = rot(1.0 + fi*0.4)*q.yz;
d = smin(d, sdTorus(q, vec2(1.0 + sin(uTime*0.3+fi)*0.06, 0.075)), 0.12);
}
return vec2(d, 1.0);`,
},
{
id: "ribbon-orbit",
label: "Ribbon orbit",
blurb: "Flat ribbons of thread streaming around a small core.",
dist: 3.8,
relax: 0.8,
map: `
float dc = length(p) - (0.34 + uLevel*0.10);
float dr = 1e9;
for (int i=0;i<3;i++){
float fi=float(i);
vec3 q=p;
q.yz = rot(0.5+fi*0.7)*q.yz;
q.xz = rot(uTime*(0.3+fi*0.12)+fi*2.1)*q.xz;
float wob = sin(atan(q.z,q.x)*3.0 + uTime)*0.10;
vec2 c = vec2(length(q.xz) - (1.05+fi*0.22+wob), q.y);
dr = min(dr, max(abs(c.x)-0.008, abs(c.y)-0.16));
}
return dc < dr ? vec2(dc,0.0) : vec2(dr,1.0);`,
},
{
id: "liquid-metal",
label: "Liquid metal",
blurb: "A mirror-smooth droplet, rippling with what it hears.",
dist: 3.1,
relax: 0.75,
shade: `
vec3 refl = reflect(rd, n);
float band = 0.5 + 0.5*sin(refl.y*6.0 + uTime*0.6);
col = mix(thread*0.35, tint, band);
col += vec3(1.0)*pow(1.0-clamp(dot(n,-rd),0.0,1.0), 4.0)*0.6;`,
map: `
vec3 q = p*2.6 + vec3(0.0, uTime*0.4, 0.0);
float amp = 0.09 + uLevel*0.16;
float d = length(p) - (1.0 + amp*fbm(q, 0.4));
return vec2(d*0.5, 0.0);`,
},
];