Back to shaders

Shader test bench

20260117

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)
// TouchDesigner GLSL TOP - Ethereal Garden
// Organic flowing forms with enhanced animations

uniform float u_time;
uniform vec2 u_resolution;

#define PI 3.14159265359
#define TAU 6.28318530718
#define PHI 1.61803398875

#define MAX_STEPS 100
#define MAX_DIST 80.0
#define SURF_DIST 0.001

// Color palette
const vec3 pal[10] = vec3[10](
    vec3(0.212, 0.176, 0.471), // deep purple
    vec3(0.322, 0.247, 0.639), // purple
    vec3(0.569, 0.424, 0.800), // light purple
    vec3(0.741, 0.631, 0.898), // lavender
    vec3(0.784, 0.753, 0.914), // pale lavender
    vec3(0.518, 0.729, 0.906), // sky blue
    vec3(0.318, 0.416, 0.831), // royal blue
    vec3(0.200, 0.247, 0.529), // navy
    vec3(0.161, 0.188, 0.224), // dark slate
    vec3(0.157, 0.212, 0.192)  // dark teal
);

// Smooth animated palette interpolation
vec3 getPalette(float t) {
    t = fract(t) * 10.0;
    int i = int(floor(t));
    float f = smoothstep(0.0, 1.0, fract(t));
    return mix(pal[i], pal[(i + 1) % 10], f);
}

// Animated palette - cycles through the purple/blue palette over time
vec3 animatedPalette(float t, float time) {
    // Offset by time for animation
    float animT = fract(t + time * 0.05);
    return getPalette(animT);
}

// Blend between specific palette colors with animation
vec3 blendPalette(int i1, int i2, float t, float time) {
    float blend = sin(t + time) * 0.5 + 0.5;
    return mix(pal[i1], pal[i2], blend);
}

// Accent colors - softer cyan/teal to match palette
const vec3 accentCyan = vec3(0.35, 0.75, 0.85);    // soft cyan
const vec3 accentTeal = vec3(0.25, 0.55, 0.58);    // muted teal
const vec3 accentAqua = vec3(0.4, 0.7, 0.75);      // gentle aqua

// Pink/Magenta accent colors - soft and ethereal
const vec3 accentPink = vec3(0.85, 0.45, 0.65);    // soft pink
const vec3 accentMagenta = vec3(0.75, 0.35, 0.6);  // muted magenta
const vec3 accentRose = vec3(0.9, 0.55, 0.7);      // gentle rose

// Get cyan accent with pulse
vec3 getAccentCyan(float t, float time) {
    float pulse = sin(time * 2.0 + t * 5.0) * 0.5 + 0.5;
    return mix(accentTeal, accentCyan, pulse);
}

// Get pink accent with pulse
vec3 getAccentPink(float t, float time) {
    float pulse = sin(time * 1.8 + t * 4.0) * 0.5 + 0.5;
    return mix(accentMagenta, accentPink, pulse);
}

// Noise for organic motion
float hash(vec3 p) {
    p = fract(p * 0.3183099 + 0.1);
    p *= 17.0;
    return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
}

float noise(vec3 p) {
    vec3 i = floor(p);
    vec3 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    return mix(
        mix(mix(hash(i + vec3(0,0,0)), hash(i + vec3(1,0,0)), f.x),
            mix(hash(i + vec3(0,1,0)), hash(i + vec3(1,1,0)), f.x), f.y),
        mix(mix(hash(i + vec3(0,0,1)), hash(i + vec3(1,0,1)), f.x),
            mix(hash(i + vec3(0,1,1)), hash(i + vec3(1,1,1)), f.x), f.y), f.z);
}

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

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

// Gyroid minimal surface
float gyroid(vec3 p, float scale) {
    p *= scale;
    return dot(sin(p), cos(p.zxy)) / scale;
}

float sphere(vec3 p, float r) {
    return length(p) - r;
}

float torus(vec3 p, vec2 t) {
    vec2 q = vec2(length(p.xz) - t.x, p.y);
    return length(q) - t.y;
}

// Scene with material ID
vec2 map(vec3 p, float t) {
    float matId = 0.0;

    // Breathing animation for whole scene
    float breath = 1.0 + 0.08 * sin(t * 0.8);

    // Slow rotation with wobble
    vec3 q = p;
    q.xz *= rot(t * 0.15 + sin(t * 0.3) * 0.1);
    q.xy *= rot(t * 0.1 + cos(t * 0.25) * 0.08);

    // Central organic core - pulsing and morphing
    float corePulse = 1.8 + 0.3 * sin(t * 1.2);
    float core = sphere(q, corePulse);
    // Organic surface displacement
    core += sin(q.x * 4.0 + t * 1.5) * 0.15 * sin(t * 0.7 + 1.0);
    core += sin(q.y * 3.0 - t * 1.2) * 0.12 * cos(t * 0.5);
    core += sin(q.z * 3.5 + t * 0.9) * 0.13 * sin(t * 0.6 + 2.0);
    // Add noise-based organic detail
    core += noise(q * 3.0 + t * 0.5) * 0.1;

    float d = core;
    matId = 1.0;

    // Inner gyroid - breathing and rotating
    vec3 g1 = p;
    g1.xz *= rot(-t * 0.12);
    g1.xy *= rot(t * 0.05);
    float gyScale1 = 1.0 + 0.15 * sin(t * 0.6);
    float gyThick1 = 0.12 + 0.04 * sin(t * 0.9);
    float gy1 = abs(gyroid(g1, gyScale1)) - gyThick1;
    float innerR = 5.5 + 0.3 * sin(t * 0.7);
    float outerR = 4.0 + 0.2 * sin(t * 0.8 + 1.0);
    gy1 = max(gy1, sphere(p, innerR));
    gy1 = max(gy1, -sphere(p, outerR));

    if (gy1 < d) {
        d = gy1;
        matId = 2.0;
    }

    // Outer gyroid - counter-rotating, morphing
    vec3 g2 = p;
    g2.yz *= rot(t * 0.07);
    g2.xz *= rot(-t * 0.04);
    float gyScale2 = 0.5 + 0.1 * sin(t * 0.5 + 1.5);
    float gy2 = abs(gyroid(g2, gyScale2)) - 0.08;
    float outer2 = 8.0 + 0.5 * sin(t * 0.4);
    float inner2 = 6.5 + 0.4 * sin(t * 0.5 + 0.5);
    gy2 = max(gy2, sphere(p, outer2));
    gy2 = max(gy2, -sphere(p, inner2));

    if (gy2 < d) {
        d = gy2;
        matId = 3.0;
    }

    // Torus rings - undulating and wobbling
    vec3 t1 = p;
    t1.xz *= rot(t * 0.2);
    t1.y += sin(atan(t1.z, t1.x) * 3.0 + t * 2.0) * 0.15; // Wave along ring
    float tor1Radius = 3.2 + 0.2 * sin(t * 0.8);
    float tor1 = torus(t1, vec2(tor1Radius, 0.15 + 0.03 * sin(t * 1.5)));
    if (tor1 < d) {
        d = tor1;
        matId = 4.0;
    }

    vec3 t2 = p;
    t2.xy *= rot(PI * 0.5 + sin(t * 0.3) * 0.1);
    t2.xz *= rot(t * 0.15);
    t2.y += sin(atan(t2.z, t2.x) * 4.0 - t * 1.8) * 0.12;
    float tor2 = torus(t2, vec2(3.6 + 0.15 * sin(t * 0.7), 0.12));
    if (tor2 < d) {
        d = tor2;
        matId = 5.0;
    }

    vec3 t3 = p;
    t3.yz *= rot(PI * 0.5 + cos(t * 0.25) * 0.08);
    t3.xz *= rot(-t * 0.12);
    t3.y += sin(atan(t3.z, t3.x) * 5.0 + t * 2.2) * 0.1;
    float tor3 = torus(t3, vec2(4.0 + 0.25 * sin(t * 0.6 + 1.0), 0.1));
    if (tor3 < d) {
        d = tor3;
        matId = 6.0;
    }

    // Orbiting spheres - dancing in spiral patterns
    for (int i = 0; i < 21; i++) {
        float fi = float(i);
        float theta = fi * TAU / PHI;
        float phi = acos(1.0 - 2.0 * (fi + 0.5) / 21.0);

        // Dynamic radius with individual timing
        float r = 5.0 + sin(t * 0.8 + fi * 0.3) * 0.8
                + cos(t * 0.5 + fi * 0.7) * 0.3;

        // Spiral motion
        float spiralOffset = sin(t * 0.6 + fi * 0.5) * 0.5;

        vec3 op = r * vec3(
            sin(phi + spiralOffset) * cos(theta + t * 0.2 + fi * 0.02),
            cos(phi) + sin(t * 0.9 + fi * 0.4) * 0.3,
            sin(phi + spiralOffset) * sin(theta + t * 0.2 + fi * 0.02)
        );

        // Pulsing size
        float sz = 0.2 + 0.08 * sin(t * 2.5 + fi * 1.2)
                 + 0.04 * cos(t * 1.8 + fi * 0.9);
        float orb = sphere(p - op, sz);
        if (orb < d) {
            d = orb;
            matId = 7.0 + fi * 0.1;
        }
    }

    // Extra: floating particles ring
    for (int i = 0; i < 12; i++) {
        float fi = float(i);
        float angle = fi * TAU / 12.0 + t * 0.3;
        float particleR = 7.0 + sin(t + fi) * 0.5;
        float height = sin(t * 1.2 + fi * 0.8) * 1.5;

        vec3 pp = vec3(cos(angle) * particleR, height, sin(angle) * particleR);
        float particle = sphere(p - pp, 0.08 + 0.03 * sin(t * 3.0 + fi));

        if (particle < d) {
            d = particle;
            matId = 9.0;
        }
    }

    return vec2(d, matId);
}

vec3 calcNormal(vec3 p, float t) {
    vec2 e = vec2(0.001, 0.0);
    return normalize(vec3(
        map(p + e.xyy, t).x - map(p - e.xyy, t).x,
        map(p + e.yxy, t).x - map(p - e.yxy, t).x,
        map(p + e.yyx, t).x - map(p - e.yyx, t).x
    ));
}

vec3 march(vec3 ro, vec3 rd, float t) {
    float d = 0.0;
    float matId = 0.0;
    float hit = 0.0;
    for (int i = 0; i < MAX_STEPS; i++) {
        vec3 p = ro + rd * d;
        vec2 res = map(p, t);
        float h = res.x;
        matId = res.y;
        d += h * 0.8;
        if (abs(h) < SURF_DIST) {
            hit = 1.0;
            break;
        }
        if (d > MAX_DIST) break;
    }
    return vec3(d, matId, hit);
}

float calcAO(vec3 p, vec3 n, float t) {
    float occ = 0.0;
    float w = 1.0;
    for (int i = 0; i < 5; i++) {
        float h = 0.02 + 0.12 * float(i);
        occ += (h - map(p + h * n, t).x) * w;
        w *= 0.75;
    }
    return clamp(1.0 - occ * 1.5, 0.0, 1.0);
}

#define fragColor gl_FragColor

void main() {
    vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
    float t = u_time * 0.6;

    // Camera
    float camDist = 39.0 + sin(t * 0.3) * 1.5;
    float camAngle = t * 0.06;
    float camHeight = 2.5 * sin(t * 0.05);

    vec3 ro = vec3(camDist * sin(camAngle), camHeight, camDist * cos(camAngle));
    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 + 2.0 * ww);

    // Animated background with palette colors
    vec3 bgBot = pal[8] * 0.3;  // dark slate base
    vec3 bgTop = pal[7] * 0.4;  // navy top
    vec3 bg = mix(bgBot, bgTop, uv.y * 0.5 + 0.5);
    // Add deep purple tint
    bg = mix(bg, pal[0] * 0.2, 0.3);

    // Aurora effect with palette colors
    float aurora1 = sin(uv.x * 3.0 + t * 0.3) * sin(uv.y * 2.0 - t * 0.2) * 0.5 + 0.5;
    aurora1 *= smoothstep(0.0, 0.5, uv.y + 0.3);
    float aurora2 = sin(uv.x * 2.0 - t * 0.25) * sin(uv.y * 3.0 + t * 0.15) * 0.5 + 0.5;
    aurora2 *= smoothstep(-0.2, 0.4, uv.y);
    // Blend aurora colors from palette
    bg += pal[5] * aurora1 * 0.1;  // sky blue
    bg += pal[6] * aurora2 * 0.06; // royal blue
    bg += pal[2] * sin(uv.x * 5.0 - t * 0.4) * 0.04; // light purple waves

    // Twinkling stars with palette tint + cyan accents
    vec2 starUV = uv * 30.0;
    float starField = hash(vec3(floor(starUV), 1.0));
    float twinkle = sin(t * 3.0 + starField * 20.0) * 0.5 + 0.5;
    float star = smoothstep(0.97, 1.0, starField) * twinkle;
    vec3 starColor = mix(pal[4], pal[5], starField);
    // Some stars have accent tints
    if (starField > 0.99) {
        starColor = mix(pal[5], accentCyan, 0.5);
    } else if (starField > 0.98 && starField <= 0.99) {
        starColor = mix(pal[3], accentPink, 0.4);
    }
    bg += starColor * star * 0.7;

    vec3 col = bg;

    // March
    vec3 res = march(ro, rd, t);
    float d = res.x;
    float matId = res.y;
    float hit = res.z;

    if (hit > 0.5) {
        vec3 p = ro + rd * d;
        vec3 n = calcNormal(p, t);
        vec3 v = normalize(ro - p);
        vec3 r = reflect(-v, n);

        // Strong directional lights
        vec3 l1 = normalize(vec3(sin(t * 0.5) * 2.0, 3.0, cos(t * 0.5) * 2.0));
        vec3 l2 = normalize(vec3(-1.5, 1.0, sin(t * 0.3) * 2.0));
        vec3 l3 = normalize(vec3(0.0, -1.0, 0.0)); // Bottom fill

        float diff1 = max(dot(n, l1), 0.0);
        float diff2 = max(dot(n, l2), 0.0);
        float diff3 = max(dot(n, l3), 0.0) * 0.3;
        float spec = pow(max(dot(r, l1), 0.0), 48.0);
        float fres = pow(1.0 - max(dot(v, n), 0.0), 3.0);
        float ao = calcAO(p, n, t);

        // Material color based on ID - animated with palette + cyan accents
        vec3 mat;
        float emission = 0.0;

        if (matId < 1.5) {
            // Core - pulsing purple with pink inner glow, cyan edge
            float pulse = sin(t * 1.5) * 0.5 + 0.5;
            float radial = length(p) * 0.2;
            mat = animatedPalette(radial + t * 0.08, t);
            mat = mix(mat, pal[2] * 1.3, pulse * 0.4);
            // Pink warm glow from center
            float pinkGlow = exp(-length(p) * 0.8) * (sin(t * 1.2) * 0.3 + 0.5);
            mat = mix(mat, accentRose, pinkGlow * 0.3);
            // Cyan accent on edges
            float edgeFactor = pow(1.0 - abs(dot(n, v)), 2.0);
            mat = mix(mat, accentCyan, edgeFactor * 0.2 * (sin(t * 2.0) * 0.3 + 0.2));
            emission = 0.3 + 0.15 * sin(t * 2.0);
        } else if (matId < 2.5) {
            // Inner gyroid - blue with cyan streaks and pink highlights
            float wave = sin(p.y * 2.0 + p.x * 1.5 + t * 1.2) * 0.5 + 0.5;
            mat = mix(pal[5], pal[6], wave);
            mat = mix(mat, pal[1], sin(p.z * 2.0 + t * 0.6) * 0.15 + 0.1);
            // Cyan streaks
            float streak = pow(sin(p.y * 4.0 + p.x * 3.0 + t * 1.5) * 0.5 + 0.5, 3.0);
            mat = mix(mat, accentAqua, streak * 0.2);
            // Subtle pink shimmer
            float pinkShimmer = pow(sin(p.x * 3.0 - p.z * 2.0 + t * 1.8) * 0.5 + 0.5, 4.0);
            mat = mix(mat, accentPink, pinkShimmer * 0.15);
            emission = 0.12 + 0.05 * sin(t * 1.8) + streak * 0.08;
        } else if (matId < 3.5) {
            // Outer gyroid - lavender with pink and teal accents
            float shift = sin(p.x * 1.5 + p.z * 1.2 - t * 0.8) * 0.5 + 0.5;
            mat = mix(pal[3], pal[4], shift);
            mat = mix(mat, pal[2], sin(t * 0.5 + p.y) * 0.2);
            // Subtle teal accent
            mat = mix(mat, accentTeal, sin(p.y * 2.0 - t) * 0.06 + 0.03);
            // Soft pink highlights
            float pinkHighlight = pow(sin(p.z * 2.5 + t * 0.9) * 0.5 + 0.5, 3.0);
            mat = mix(mat, accentRose, pinkHighlight * 0.12);
            emission = 0.1 + pinkHighlight * 0.05;
        } else if (matId < 7.0) {
            // Torus rings - alternating cyan and pink accents
            float ringIdx = matId - 4.0;
            float colorCycle = sin(t * 0.6 + ringIdx * 1.5) * 0.5 + 0.5;
            mat = mix(pal[5], pal[6], colorCycle);
            mat = mix(mat, pal[1], sin(t * 0.4 + ringIdx * 2.0) * 0.3 + 0.2);
            // Alternate between cyan and pink based on ring
            float accentPulse = pow(sin(t * 3.0 + ringIdx * 2.5) * 0.5 + 0.5, 2.0);
            if (ringIdx < 1.0) {
                mat = mix(mat, accentCyan, accentPulse * 0.25);
            } else if (ringIdx < 2.0) {
                mat = mix(mat, accentPink, accentPulse * 0.22);
            } else {
                mat = mix(mat, accentMagenta, accentPulse * 0.2);
            }
            emission = 0.18 + 0.08 * sin(t * 2.5 + ringIdx * 2.0) + accentPulse * 0.1;
        } else if (matId < 9.0) {
            // Orbiting spheres - cyan and pink alternating
            float orbIdx = (matId - 7.0) / 2.1;
            int sphereNum = int(mod(orbIdx * 21.0, 21.0));
            float sphereMod = mod(float(sphereNum), 7.0);
            if (sphereMod < 1.0) {
                // Cyan accent spheres
                mat = mix(pal[5], getAccentCyan(orbIdx, t), 0.5);
                emission = 0.35 + 0.15 * sin(t * 4.0 + orbIdx * 3.0);
            } else if (sphereMod < 2.0) {
                // Pink accent spheres
                mat = mix(pal[3], getAccentPink(orbIdx, t), 0.5);
                emission = 0.35 + 0.15 * sin(t * 3.5 + orbIdx * 2.5);
            } else {
                mat = animatedPalette(orbIdx * 0.8 + t * 0.1, t);
                mat = mix(mat, pal[4], 0.2);
                emission = 0.25 + 0.1 * sin(t * 3.0 + orbIdx * 5.0);
            }
        } else {
            // Floating particles - alternating cyan and pink sparkles
            float sparkle = sin(t * 5.0 + matId * 3.0) * 0.5 + 0.5;
            float particleType = mod(matId * 7.0, 2.0);
            if (particleType < 1.0) {
                mat = mix(accentTeal, mix(accentCyan, pal[5], 0.3), sparkle);
            } else {
                mat = mix(accentMagenta, mix(accentPink, pal[3], 0.3), sparkle);
            }
            emission = 0.4 + 0.25 * sin(t * 5.0 + matId);
        }

        // Rim lighting with alternating cyan/pink accent
        vec3 rimColor = mix(pal[5], pal[3], fres);
        // Cycle between cyan and pink rim
        float rimCycle = sin(t * 1.2) * 0.5 + 0.5;
        vec3 rimAccent = mix(accentCyan, accentPink, rimCycle);
        rimColor = mix(rimColor, rimAccent, 0.18);

        // Compose lighting - brighter overall
        col = mat * 0.15; // Ambient
        col += mat * vec3(1.0, 0.95, 0.9) * diff1 * 0.9;
        col += mat * pal[5] * diff2 * 0.4;
        col += mat * pal[1] * diff3;
        col += vec3(1.0, 0.9, 0.85) * spec * 0.7;
        col += rimColor * fres * 0.6;  // Strong rim
        col += mat * emission;
        col *= ao;

        // Subtle edge highlight
        float edge = 1.0 - abs(dot(v, n));
        col += pal[4] * pow(edge, 4.0) * 0.3;

        // Less fog to keep things clear
        float fog = 1.0 - exp(-d * 0.025);
        col = mix(col, bg, fog * 0.4);
    }

    // Animated central glow - palette colors with dual accents
    float glowPulse = 0.35 + 0.1 * sin(t * 1.2);
    vec3 glowColor = animatedPalette(t * 0.06, t);
    glowColor = mix(glowColor, pal[3], 0.3);
    col += glowColor * exp(-length(uv) * 2.0) * glowPulse;
    col += pal[5] * exp(-length(uv) * 3.5) * 0.25;
    col += pal[2] * exp(-length(uv) * 5.0) * 0.18;
    // Alternating cyan and pink glow pulses
    float cyanGlowPulse = pow(sin(t * 2.5) * 0.5 + 0.5, 2.0);
    float pinkGlowPulse = pow(sin(t * 2.0 + 1.5) * 0.5 + 0.5, 2.0);
    col += accentCyan * exp(-length(uv) * 4.0) * cyanGlowPulse * 0.12;
    col += accentRose * exp(-length(uv) * 3.8) * pinkGlowPulse * 0.1;

    // Light rays from center - cycling palette colors
    float rays = sin(atan(uv.y, uv.x) * 8.0 + t * 0.5) * 0.5 + 0.5;
    rays *= exp(-length(uv) * 2.5);
    vec3 rayColor = mix(pal[4], pal[3], sin(t * 0.4) * 0.5 + 0.5); // lavender tones
    col += rayColor * rays * 0.12;

    // Subtle vignette with purple tint
    float vig = 1.0 - length(uv) * 0.25;
    col *= vig;
    col = mix(col, col * (pal[0] * 2.0 + vec3(0.5)), (1.0 - vig) * 0.3);

    // Tone mapping
    col = col / (col + 0.85);
    col = pow(col, vec3(0.44));

    // Boost saturation
    float luma = dot(col, vec3(0.299, 0.587, 0.114));
    col = mix(vec3(luma), col, 1.25);

    // Color grading - enhance purples and blues
    col.b = mix(col.b, col.b * 1.08, smoothstep(0.2, 0.6, luma));
    col.r = mix(col.r, col.r * 1.03, smoothstep(0.3, 0.7, col.b));

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