Back to shaders

Shader test bench

20260409

glsl-daily-practice generative glsl runnable fragment MIT
Source
runnable fragment

Complete GLSL fragment shader. Stronghold runs it directly when the browser can compile it.

Code

precision mediump float;
#define TDOutputSwizzle(c) (c)

uniform float iTime;
layout(location = 0) #define fragColor gl_FragColor

// ─── Palette ──────────────────────────────────────────────────
const vec3 cBg1    = vec3(0.039, 0.059, 0.051);
const vec3 cBg2    = vec3(0.051, 0.129, 0.216);
const vec3 cBg3    = vec3(0.102, 0.102, 0.180);
const vec3 cGreen  = vec3(0.000, 1.000, 0.529);
const vec3 cCyan   = vec3(0.000, 0.831, 1.000);
const vec3 cPurple = vec3(0.482, 0.184, 1.000);
const vec3 cPink   = vec3(1.000, 0.176, 0.478);
const vec3 cMint   = vec3(0.690, 1.000, 0.910);
const vec3 cIce    = vec3(0.878, 0.969, 1.000);

#define MAX_STEPS 140
#define MAX_DIST  20.0
#define SURF_DIST 0.0008
#define TAU  6.28318530718
#define PI   3.14159265359

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

// ─── Shape library (all parameterized 0..TAU) ─────────────────

// A: (3,5) torus knot — 8-crossing star knot
vec3 shapeA(float phi) {
    float p = 3.0, q = 5.0;
    float r = cos(q*phi) + 2.0;
    return vec3(
        r * cos(p*phi),
        r * sin(p*phi),
        -sin(q*phi)
    ) * 0.26;
}

// B: Triple helix (3 strands braided — Borromean spirit)
vec3 shapeB(float phi) {
    float braid = sin(phi * 3.0) * 0.22;
    float R = 0.55 + braid;
    float lift = cos(phi * 3.0) * 0.28;
    return vec3(R*cos(phi), lift, R*sin(phi));
}

// C: Viviani curve (figure-8 on sphere)
vec3 shapeC(float phi) {
    float s = 0.60;
    return vec3(
        s * (1.0 + cos(phi)) * 0.5 * cos(phi),
        s * sin(phi),
        s * (1.0 + cos(phi)) * 0.5 * sin(phi)
    );
}

// D: (2,7) torus knot — thin elegant 7-crossing
vec3 shapeD(float phi) {
    float p = 2.0, q = 7.0;
    float r = cos(q*phi) + 2.2;
    return vec3(
        r * cos(p*phi),
        r * sin(p*phi),
        -sin(q*phi)
    ) * 0.22;
}

// E: Seifert-inspired surface spine — double helical twist
vec3 shapeE(float phi) {
    float twist = phi * 2.5;
    float R = 0.50 + 0.20 * cos(phi * 4.0);
    return vec3(
        R * cos(phi),
        0.30 * sin(twist),
        R * sin(phi)
    );
}

// F: Lemniscate of Bernoulli extruded in 3D
vec3 shapeF(float phi) {
    float s   = sin(phi);
    float denom = 1.0 + s*s;
    float x   = 0.70 * cos(phi) / denom;
    float y   = 0.70 * cos(phi)*sin(phi) / denom;
    float z   = 0.25 * sin(phi * 3.0);
    return vec3(x, y, z);
}

// ─── Sequential blend (like the version you liked) ────────────
// 6 shapes, 5s each, 1.2s crossfade — clear A→B→C→D→E→F→A
vec3 spine(float phi, float t) {
    float period  = 12.0;   // total cycle
    float perShape= period / 6.0;   // 2s per shape
    float p       = mod(t, period);

    // Which segment and local blend
    float seg  = p / perShape;       // 0..6
    int   i0   = int(floor(seg)) % 6;
    int   i1   = (i0 + 1) % 6;
    float raw  = fract(seg);

    // Ease: flat hold then smooth crossfade in last 0.40 of segment
    float fadeStart = 0.60;
    float blend = smoothstep(fadeStart, 1.0, raw);
    // Quintic ease for extra smoothness
    blend = blend*blend*blend*(blend*(blend*6.0-15.0)+10.0);

    // Evaluate both shapes
    vec3 s0, s1;
    if      (i0==0) s0=shapeA(phi); else if(i0==1) s0=shapeB(phi);
    else if (i0==2) s0=shapeC(phi); else if(i0==3) s0=shapeD(phi);
    else if (i0==4) s0=shapeE(phi); else            s0=shapeF(phi);
    if      (i1==0) s1=shapeA(phi); else if(i1==1) s1=shapeB(phi);
    else if (i1==2) s1=shapeC(phi); else if(i1==3) s1=shapeD(phi);
    else if (i1==4) s1=shapeE(phi); else            s1=shapeF(phi);

    return mix(s0, s1, blend);
}

// Active shape index 0..5 (fractional for color blend)
float shapeIdx(float t) {
    float period  = 12.0;
    float perShape= period / 6.0;
    return mod(t, period) / perShape;  // 0..6
}

// ─── Tube radius breathes independently ───────────────────────
float tubeRadius(float t) {
    return 0.060 + 0.014 * sin(t * 0.8) + 0.007 * sin(t * 2.1);
}

// ─── SDF: dual interlocking tubes ─────────────────────────────
// Two tubes on same spine, offset by PI — they interlock
float sdDualTube(vec3 p, float t) {
    float d   = MAX_DIST;
    int   N   = 110;
    float tr  = tubeRadius(t);

    // Tube A (phi 0..TAU)
    vec3 prevA = spine(0.0,    t);
    // Tube B (phi offset by PI — interlocked)
    vec3 prevB = spine(PI,     t);

    for (int i = 1; i <= N; i++) {
        float phiA = TAU * float(i) / float(N);
        float phiB = phiA + PI;

        vec3 curA = spine(phiA, t);
        vec3 curB = spine(phiB, t);

        // Capsule A
        vec3 paA = p - prevA, baA = curA - prevA;
        float hA = clamp(dot(paA,baA)/dot(baA,baA), 0.0, 1.0);
        d = min(d, length(paA - baA*hA) - tr);

        // Capsule B
        vec3 paB = p - prevB, baB = curB - prevB;
        float hB = clamp(dot(paB,baB)/dot(baB,baB), 0.0, 1.0);
        d = min(d, length(paB - baB*hB) - tr * 0.85);  // slightly thinner

        prevA = curA;
        prevB = curB;
    }
    return d;
}

// ─── Scene ────────────────────────────────────────────────────
float scene(vec3 p) {
    float t = iTime;
    p.xz = rot(t * 0.13) * p.xz;
    p.xy = rot(t * 0.06) * p.xy;
    return sdDualTube(p, t);
}

// ─── Raymarcher ───────────────────────────────────────────────
float rayMarch(vec3 ro, vec3 rd) {
    float d = 0.0;
    for (int i = 0; i < MAX_STEPS; i++) {
        float s = scene(ro + rd * d);
        if (s < SURF_DIST || d > MAX_DIST) break;
        d += s * 0.88;
    }
    return d;
}

vec3 getNormal(vec3 p) {
    float e = 0.001;
    return normalize(vec3(
        scene(p+vec3(e,0,0)) - scene(p-vec3(e,0,0)),
        scene(p+vec3(0,e,0)) - scene(p-vec3(0,e,0)),
        scene(p+vec3(0,0,e)) - scene(p-vec3(0,0,e))
    ));
}

// ─── Color palette per shape ──────────────────────────────────
vec3 paletteForShape(float idx) {
    // 6 distinct hue identities
    vec3 cols[6];
    cols[0] = cGreen;   // A: trefoil-like → green
    cols[1] = cCyan;    // B: triple helix → cyan
    cols[2] = cPurple;  // C: Viviani → purple
    cols[3] = cPink;    // D: 7-knot → pink
    cols[4] = cMint;    // E: Seifert → mint
    cols[5] = cIce;     // F: Lemniscate → ice

    int   i0    = int(floor(idx)) % 6;
    int   i1    = (i0 + 1) % 6;
    float blend = (1.0 - cos(fract(idx) * PI)) * 0.5;
    return mix(cols[i0], cols[i1], blend);
}

// ─── Shading ──────────────────────────────────────────────────
vec3 shade(vec3 pos, vec3 nor, vec3 rd) {
    float t      = iTime;
    float NdotV  = max(dot(nor, -rd), 0.0);
    float fresnel= pow(1.0 - NdotV, 3.2);

    float idx    = shapeIdx(t);
    vec3  sigCol = paletteForShape(idx);
    vec3  nxtCol = paletteForShape(idx + 1.2);

    // Color wave flowing along tube
    float ang    = atan(pos.z, pos.x);
    float band   = ang * 2.5 + pos.y * 3.5 + t * 1.5;
    float wave   = sin(band) * 0.5 + 0.5;
    float wave2  = sin(band * 1.7 + 2.3) * 0.5 + 0.5;

    vec3 body    = mix(sigCol, nxtCol, wave * 0.45);
    body         = mix(body, cIce,    wave2 * 0.10);
    vec3 rimCol  = mix(sigCol, cPink,  fresnel * 0.8);
    // Second tube slightly cooler rim
    rimCol       = mix(rimCol, cCyan, wave2 * 0.3);

    vec3 L       = normalize(vec3(1.0, 1.8, -1.2));
    float diff   = max(dot(nor, L), 0.0);
    vec3 h       = normalize(L - rd);
    float spec   = pow(max(dot(nor, h), 0.0), 140.0);
    float rim1   = pow(1.0 - NdotV, 2.6);
    float rim2   = pow(1.0 - NdotV, 7.0);

    return body   * diff * 0.25
         + rimCol * rim1 * 2.5
         + rimCol * rim2 * 1.3
         + body   * 0.28
         + cIce   * spec * 1.1;
}

// ─── Background ───────────────────────────────────────────────
vec3 background(vec3 rd) {
    float y  = rd.y * 0.5 + 0.5;
    vec3  bg = mix(cBg1, mix(cBg2, cBg3, 0.5), y * y);
    bg += cBg3 * pow(max(1.0 - abs(rd.y), 0.0), 4.0) * 0.3;
    vec2 id = floor(rd.xy * 140.0);
    float h = fract(sin(dot(id, vec2(127.1, 311.7))) * 43758.5);
    if (h > 0.986) bg += cIce * (h - 0.986) * 55.0;
    return bg;
}

// ─── Main ─────────────────────────────────────────────────────
void main() {
    vec2 uv = vUV.st;
    vec2 st = uv * 2.0 - 1.0;
    st.x   *= uTDOutputInfo.res.z / uTDOutputInfo.res.w;

    float t      = iTime;
    float camR   = 2.9;
    float camAng = t * 0.10;
    float camY   = 0.42 * sin(t * 0.13) * cos(t * 0.06);
    vec3  ro     = vec3(sin(camAng)*camR, camY, cos(camAng)*camR);
    vec3  fwd    = normalize(-ro);
    vec3  rgt    = normalize(cross(vec3(0,1,0), fwd));
    vec3  up     = cross(fwd, rgt);
    vec3  rd     = normalize(fwd + st.x*rgt*0.82 + st.y*up*0.82);

    float d   = rayMarch(ro, rd);
    bool  hit = d < MAX_DIST;

    vec3 col;
    if (!hit) {
        col = background(rd);
    } else {
        vec3 pos = ro + rd * d;
        vec3 nor = getNormal(pos);
        col      = shade(pos, nor, rd);
        float fog = 1.0 - exp(-d * 0.08);
        col = mix(col, background(rd), fog * 0.35);
    }

    float vig = 1.0 - dot(st * 0.38, st * 0.38);
    col      *= smoothstep(0.0, 1.0, vig);
    col       = col / (col + 0.55);
    col       = pow(max(col, 0.0), vec3(0.88));

    fragColor = TDOutputSwizzle(vec4(col, 1.0));
}