Back to shaders

Shader test bench

20260503

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)
// ============================================================
// AURORA NEBULA — flowing volumetric ribbons & cosmic dust
// TouchDesigner GLSL TOP — 20260503
// Required uniforms (Vectors tab):
//   iTime  (float, 1x1)  ← Timer CHOP
// Optional (declare in TD as separate float uniforms; default 0 if unbound):
//   iWarp  (float, 1x1)  ← 0..1 extra turbulence over baseline
//   iBeat  (float, 1x1)  ← 0..1 pulse input (bloom + chromatic flash)
// Palette intentionally identical to 20260426 (Sacred Mandala).
// ============================================================

uniform float iTime;
uniform float iWarp;
uniform float iBeat;

layout(location = 0) #define fragColor gl_FragColor

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

// ── Palette (matches 20260426) ─────────────────────────────
const vec3 cBg1    = vec3(0.039, 0.059, 0.051); // deep forest night
const vec3 cBg2    = vec3(0.051, 0.129, 0.216); // ocean midnight
const vec3 cBg3    = vec3(0.102, 0.102, 0.180); // deep violet dusk
const vec3 cGreen  = vec3(0.000, 1.000, 0.529); // electric green
const vec3 cCyan   = vec3(0.000, 0.831, 1.000); // neon cyan
const vec3 cPurple = vec3(0.482, 0.184, 1.000); // electric purple
const vec3 cPink   = vec3(1.000, 0.176, 0.478); // hot pink
const vec3 cMint   = vec3(0.690, 1.000, 0.910); // jade mist
const vec3 cIce    = vec3(0.878, 0.969, 1.000); // ice halo
const vec3 cWhite  = vec3(1.000, 1.000, 1.000);

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

float hash21(vec2 p) {
    p = fract(p * vec2(127.619, 311.713));
    p += dot(p, p + 47.31);
    return fract(p.x * p.y);
}

float vnoise(vec2 p) {
    vec2 i = floor(p), f = fract(p);
    float a = hash21(i);
    float b = hash21(i + vec2(1.0, 0.0));
    float c = hash21(i + vec2(0.0, 1.0));
    float d = hash21(i + vec2(1.0, 1.0));
    vec2 u = f * f * (3.0 - 2.0 * f);
    return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}

float fbm(vec2 p) {
    float v = 0.0, a = 0.5;
    mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
    for (int i = 0; i < 6; i++) {
        v += a * vnoise(p);
        p = m * p;
        a *= 0.5;
    }
    return v;
}

// Domain-warped flow field
vec2 flowField(vec2 p, float t) {
    vec2 q = vec2(fbm(p + vec2(0.0, t * 0.15)),
                  fbm(p + vec2(5.2, 1.3) - t * 0.12));
    vec2 r = vec2(fbm(p + 4.0 * q + vec2(1.7, 9.2) + t * 0.10),
                  fbm(p + 4.0 * q + vec2(8.3, 2.8) - t * 0.08));
    return r;
}

// Cosine palette (Inigo Quilez) — tuned to match Mandala iridescence
vec3 iqPal(float t) {
    vec3 a = vec3(0.50, 0.55, 0.55);
    vec3 b = vec3(0.45, 0.40, 0.45);
    vec3 c = vec3(1.00, 1.00, 1.00);
    vec3 d = vec3(0.50, 0.20, 0.67);
    return a + b * cos(TAU * (c * t + d));
}

// Single ribbon strand: returns intensity in [0..1]
float ribbon(vec2 p, float seed, float t) {
    // Spatially-varying offset from a flow field
    vec2 fp = p * 1.4 + vec2(seed * 31.7, -seed * 17.3);
    vec2 flow = flowField(fp, t + seed * 9.13);

    // Base sinuous curve
    float yCurve = 0.55 * sin(p.x * 1.5 + t * 0.45 + seed * 7.0)
                 + 0.25 * sin(p.x * 3.1 - t * 0.7 + seed * 3.0)
                 + 0.45 * (flow.y - 0.5);

    // Slight slope offset per ribbon (vertical stacking)
    float baseY = (seed - 0.5) * 1.4;

    float dist = abs(p.y - baseY - yCurve);

    // Ribbon thickness, modulated by flow
    float thick = 0.06 + 0.05 * flow.x;
    float core  = smoothstep(thick, 0.0, dist);
    float halo  = exp(-dist * dist * 18.0);

    return core * 0.65 + halo * 0.55;
}

// Star field with twinkle
float starField(vec2 p, float t) {
    vec2 g = floor(p * 60.0);
    vec2 f = fract(p * 60.0) - 0.5;
    float h = hash21(g);
    float s = step(0.992, h);
    float r = length(f);
    float tw = 0.5 + 0.5 * sin(t * 3.0 + h * 60.0);
    return s * smoothstep(0.32, 0.0, r) * tw;
}

// Soft volumetric nebula clouds
float nebula(vec2 p, float t) {
    vec2 q = p * 0.9;
    q += 0.6 * (flowField(q * 0.7, t * 0.3) - 0.5);
    float n = fbm(q + t * 0.05);
    return smoothstep(0.35, 0.95, n);
}

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

    float t = iTime;

    // Global breathing zoom + beat compression
    float breath = 1.0 + 0.025 * sin(t * 0.45);
    float beatK  = clamp(iBeat, 0.0, 1.0);
    st /= breath;
    st *= 1.0 - 0.05 * beatK;

    // Slow whole-canvas drift rotation
    st *= rot(sin(t * 0.07) * 0.10);

    // Optional turbulence boost
    float warpAmt = clamp(0.45 + iWarp * 0.55, 0.0, 1.0);

    // Domain warp the canvas itself (subtle)
    vec2 warpField = flowField(st * 0.6, t) - 0.5;
    vec2 stW = st + warpField * 0.18 * warpAmt;

    // ── Background: deep forest → ocean → violet gradient ──
    float r2  = dot(st, st);
    float ang = atan(st.y, st.x);
    vec3 bg   = mix(cBg1, cBg2, smoothstep(0.0, 0.6, r2));
    bg        = mix(bg, cBg3, 0.45 * smoothstep(0.15, 1.4, r2));

    // Soft angular wash to break flat gradient
    float wash = 0.5 + 0.5 * sin(ang * 2.0 + t * 0.18);
    bg += iqPal(wash * 0.5 + t * 0.04) * 0.05;

    vec3 col = bg;

    // ── Nebula clouds — purple/cyan diffused haze ──────────
    float neb1 = nebula(stW + vec2( 0.6, -0.2), t * 1.1);
    float neb2 = nebula(stW * 1.3 + vec2(-0.8, 0.4), -t * 0.8);
    col += cPurple * neb1 * 0.22;
    col += cCyan   * neb2 * 0.20;
    col += mix(cBg3, cPurple, 0.5) * (neb1 * neb2) * 0.55;

    // ── Aurora ribbons — five strands, layered ─────────────
    float r1 = ribbon(stW, 0.12, t);
    float r2v = ribbon(stW * vec2(1.0, 1.05), 0.34, t * 1.05 + 1.7);
    float r3 = ribbon(stW * vec2(1.0, 1.10), 0.58, t * 0.92 - 2.3);
    float r4 = ribbon(stW * vec2(1.0, 1.00), 0.78, t * 1.12 + 4.1);
    float r5 = ribbon(stW * vec2(1.0, 0.95), 0.94, t * 0.85 - 5.7);

    col += cGreen  * r1 * 0.95;
    col += cCyan   * r2v * 0.90;
    col += cMint   * r3 * 0.75;
    col += cPurple * r4 * 0.85;
    col += cPink   * r5 * 0.80;

    // White-hot ribbon cores (thin centerlines)
    float coreSum = max(max(max(r1, r2v), max(r3, r4)), r5);
    col += cWhite * pow(coreSum, 6.0) * 0.9;
    col += cIce   * pow(coreSum, 3.0) * 0.35;

    // ── Cosmic dust — fibonacci-distributed glowing motes ──
    float dust = 0.0;
    float dustGlow = 0.0;
    float phi = 137.508 * PI / 180.0;
    for (int i = 0; i < 48; i++) {
        float fi = float(i);
        float a  = fi * phi + t * 0.12;
        float r  = sqrt(fi) * 0.11;
        vec2  c  = vec2(cos(a), sin(a)) * r;
        // gentle drift
        c += 0.05 * vec2(sin(t * 0.6 + fi), cos(t * 0.5 + fi * 1.3));
        float dl = length(stW - c);
        dust     += smoothstep(0.012, 0.0, dl);
        dustGlow += exp(-dl * dl * 240.0);
    }
    col += cIce   * dust * 0.35;
    col += iqPal(t * 0.06 + 0.20) * dustGlow * 0.18;

    // ── Star twinkle field ─────────────────────────────────
    float stars = starField(stW * 1.2, t);
    col += cIce * stars * 0.85;

    // ── Iridescent radial hue shift ────────────────────────
    float hueT = length(st) * 0.7 + t * 0.05;
    vec3  iri  = iqPal(hueT);
    col = mix(col, col * (0.6 + 0.6 * iri), 0.30);

    // ── Center bloom — cyan/ice focal light ────────────────
    float centerBloom = exp(-r2 * 3.0);
    col += mix(cCyan, cIce, 0.55) * centerBloom * (0.55 + 0.30 * sin(t * 0.4));

    // ── Outer chromatic aura ───────────────────────────────
    float aura = exp(-pow(length(st) - 0.92, 2.0) * 28.0);
    col += iqPal(ang / TAU + t * 0.1) * aura * 0.22;

    // ── Beat reactive pulse ────────────────────────────────
    col += iri * beatK * 0.12;

    // ── Soft intro fade (first 2 seconds) ──────────────────
    col *= smoothstep(0.0, 2.0, t);

    // ── Vignette using deep bg ─────────────────────────────
    float vig = smoothstep(1.45, 0.25, length(st));
    col = mix(cBg1 * 0.4, col, vig);

    // ── Tone map + gentle gamma ────────────────────────────
    col = col / (col + 0.85);
    col = pow(max(col, 0.0), vec3(0.92));

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