Back to shaders

Shader test bench

20260316

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;
uniform float u_time;
uniform vec2  uRes;

#define fragColor gl_FragColor

#define PI  3.14159265
#define TAU 6.28318530

// ── Palette ──────────────────────────────────
vec3 cBg1     = vec3(0.161, 0.188, 0.224);
vec3 cBg2     = vec3(0.157, 0.212, 0.192);
vec3 cDeep1   = vec3(0.051, 0.122, 0.176);
vec3 cDeep2   = vec3(0.039, 0.239, 0.180);
vec3 cGreen   = vec3(0.000, 1.000, 0.529);
vec3 cCyan    = vec3(0.000, 0.831, 1.000);
vec3 cPurple  = vec3(0.482, 0.184, 1.000);
vec3 cPink    = vec3(1.000, 0.176, 0.478);
vec3 cMint    = vec3(0.690, 1.000, 0.910);
vec3 cIce     = vec3(0.769, 0.941, 1.000);

// ── Noise & Helpers ──────────────────────────
float hash(float n) { return fract(sin(n) * 43758.5453); }
vec2 hash2(vec2 p) {
    p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
    return fract(sin(p) * 43758.5453);
}
mat2 rot(float a) { float c = cos(a), s = sin(a); return mat2(c, -s, s, c); }

// Simplex-like value noise for organic distortion
float vnoise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash(dot(i, vec2(1.0, 157.0)));
    float b = hash(dot(i + vec2(1, 0), vec2(1.0, 157.0)));
    float c = hash(dot(i + vec2(0, 1), vec2(1.0, 157.0)));
    float d = hash(dot(i + vec2(1, 1), vec2(1.0, 157.0)));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

// Fractal Brownian Motion – 4 octaves
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 < 4; i++) {
        v += a * vnoise(p);
        p = m * p;
        a *= 0.5;
    }
    return v;
}

float foldAngle(float a, float n) {
    float seg = TAU / n;
    float fa = mod(a, seg);
    return min(fa, seg - fa);
}

float ring(float r, float r0, float w) {
    return smoothstep(w, 0.0, abs(r - r0));
}

// ── Mandala Shapes ───────────────────────────
float petalShape(float r, float fa, float folds, float t) {
    float halfSeg = PI / folds;
    float breath = 1.0 + 0.18 * sin(t * 1.8 + folds * 0.7);
    float width = halfSeg * 0.65 * breath * (1.0 - pow(r * 1.1, 2.0));
    width = max(width, 0.0);
    float petal = smoothstep(width, width * 0.25, fa);
    float vein = exp(-fa * fa * folds * folds * 5.0) * 0.6;
    float scallop = 0.07 * sin(r * 35.0 - t * 3.0 + folds);
    petal *= smoothstep(0.0, 0.015, width - fa + scallop);
    return clamp(petal + vein, 0.0, 1.0);
}

float dropShape(float r, float fa, float folds, float t) {
    float halfSeg = PI / folds;
    float pulse = 1.0 + 0.1 * sin(t * 2.3 + folds);
    float tip = smoothstep(0.0, 0.3, r) * smoothstep(0.8, 0.2, r);
    float width = halfSeg * 0.35 * pulse * tip;
    float drop = smoothstep(width, width * 0.15, fa);
    float spine = exp(-fa * fa * folds * folds * 8.0) * 0.4 * tip;
    return clamp(drop + spine, 0.0, 1.0);
}

float dotRing(float r, float angle, float r0, float count, float size) {
    float da = foldAngle(angle, count);
    float dr = r - r0;
    float d = length(vec2(da * r0, dr));
    return smoothstep(size, size * 0.3, d);
}

float filigree(float r, float angle, float folds, float t) {
    float fa = foldAngle(angle, folds);
    float halfSeg = PI / folds;
    float curve = halfSeg * 0.5 * sin(r * PI * 2.5 + t * 0.8);
    float line = exp(-pow((fa - abs(curve)) * folds * 3.0, 2.0)) * 0.7;
    return line * smoothstep(0.0, 0.08, r) * smoothstep(1.0, 0.6, r);
}

// ── Floating particles ──────────────────────
float particles(vec2 uv, float t, float layer) {
    float acc = 0.0;
    for (int i = 0; i < 30; i++) {
        float id = float(i) + layer * 30.0;
        vec2 seed = hash2(vec2(id, id * 0.7));

        // Spiral orbit
        float orbitR = 0.15 + seed.x * 0.75;
        float orbitSpeed = (0.2 + seed.y * 0.3) * (mod(id, 2.0) < 1.0 ? 1.0 : -1.0);
        float orbitAngle = seed.x * TAU + t * orbitSpeed;

        vec2 pos = vec2(cos(orbitAngle), sin(orbitAngle)) * orbitR;
        // Vertical drift
        pos.y += 0.05 * sin(t * 0.7 + id);

        float d = length(uv - pos);
        float size = 0.003 + 0.004 * seed.y;
        float flicker = 0.5 + 0.5 * sin(t * (3.0 + seed.x * 4.0) + id);
        acc += smoothstep(size, 0.0, d) * flicker;
    }
    return acc;
}

// ── Radial energy pulse ─────────────────────
float energyPulse(float r, float t) {
    float p = 0.0;
    for (int i = 0; i < 3; i++) {
        float fi = float(i);
        float phase = t * 0.6 + fi * 2.1;
        float wavefront = fract(phase) * 1.2;
        float intensity = exp(-3.0 * fract(phase)); // fade as it expands
        p += ring(r, wavefront, 0.04) * intensity;
    }
    return p;
}

// ═══════════════════════════════════════════════
// MAIN
// ═══════════════════════════════════════════════
void main() {
    vec2 uv = (gl_FragCoord.xy - 0.5 * uRes) / min(uRes.x, uRes.y);
    float t = u_time * 0.35;

    // ── Organic spatial warp via fbm noise ──
    vec2 warpOffset = vec2(
        fbm(uv * 3.0 + t * 0.3) - 0.5,
        fbm(uv * 3.0 + t * 0.3 + 5.0) - 0.5
    );
    uv += warpOffset * 0.025;

    // Breathing zoom: the whole mandala pulses in/out
    float breathZoom = 1.0 + 0.03 * sin(t * 0.8);
    uv *= breathZoom;

    float r     = length(uv);
    float angle = atan(uv.y, uv.x);
    float a     = angle + t * 0.12;

    // ── Background: deep space with nebula swirl ──
    float nebula = fbm(uv * 2.0 - t * 0.15);
    vec3 bg = mix(cDeep1, cBg1, smoothstep(0.0, 1.4, r));
    bg = mix(bg, cDeep2, 0.25 + 0.2 * sin(a * 4.0 + t));
    bg += cPurple * 0.06 * nebula;
    bg += cCyan * 0.04 * fbm(uv * 3.5 + t * 0.1 + 10.0);
    // Subtle grain
    bg += 0.012 * (hash(dot(gl_FragCoord.xy, vec2(12.9898, 78.233)) + t * 0.1) - 0.5);

    vec3 col = bg;

    // ═══════ ENERGY PULSES (behind mandala) ═══════
    float pulse = energyPulse(r, t);
    col += cCyan * pulse * 0.25;
    col += cGreen * pulse * 0.15;

    // ═══════ LAYER 1: Outermost dots ═══════
    float dots1 = dotRing(r, a, 0.82, 24.0, 0.018);
    col = mix(col, cIce * 0.7, dots1 * 0.6);

    // ═══════ LAYER 2: Outer filigree ═══════
    float fil1 = filigree(r * 1.2, a + t * 0.05, 12.0, t);
    col += cPurple * fil1 * 0.35 * smoothstep(0.55, 0.85, r);

    // ═══════ LAYER 3: Outer petals – 12-fold ═══════
    float fa1 = foldAngle(a, 12.0);
    float p1  = petalShape(r, fa1, 12.0, t);
    float r1  = ring(r, 0.68 + 0.03 * sin(t * 1.2), 0.12);
    vec3 col1 = mix(cPurple, cPink, 0.3 + 0.3 * sin(r * 10.0 + t));
    col = mix(col, col1, p1 * r1 * 0.85);
    col += col1 * p1 * r1 * 0.18 * (0.5 + 0.5 * sin(t * 1.5));

    // ═══════ LAYER 4: Mid-outer teardrops ═══════
    float fa2d = foldAngle(a + PI / 12.0, 12.0);
    float d1   = dropShape(r * 1.5 - 0.2, fa2d, 12.0, t);
    float r2d  = ring(r, 0.55, 0.08);
    col = mix(col, cCyan * 0.9, d1 * r2d * 0.7);

    // ═══════ LAYER 5: Dot separator ═══════
    float dots2 = dotRing(r, a + PI / 24.0, 0.48, 24.0, 0.012);
    col = mix(col, cMint, dots2 * 0.7);

    // ═══════ LAYER 6: Mid petals – 8-fold ═══════
    float fa3 = foldAngle(a + t * 0.08, 8.0);
    float p3  = petalShape(r, fa3, 8.0, t);
    float r3  = ring(r, 0.40 + 0.025 * sin(t * 0.9), 0.12);
    vec3 col3 = mix(cCyan, cGreen, 0.4 + 0.4 * sin(a * 8.0 + t * 2.0));
    col = mix(col, col3, p3 * r3 * 0.85);
    col += col3 * p3 * r3 * 0.12;

    // ═══════ LAYER 7: Inner filigree ═══════
    float fil2 = filigree(r * 2.5, a - t * 0.1, 6.0, t * 1.3);
    col += cGreen * fil2 * 0.25 * smoothstep(0.15, 0.38, r) * smoothstep(0.45, 0.3, r);

    // ═══════ LAYER 8: Inner petals – 6-fold ═══════
    float fa4 = foldAngle(a - t * 0.15, 6.0);
    float p4  = petalShape(r, fa4, 6.0, t);
    float r4  = ring(r, 0.22 + 0.02 * sin(t * 1.5), 0.10);
    col = mix(col, cGreen, p4 * r4 * 0.9);
    col += cMint * p4 * r4 * 0.12;

    // ═══════ LAYER 9: Inner teardrops ═══════
    float fa5d = foldAngle(a - t * 0.15 + PI / 6.0, 6.0);
    float d2   = dropShape(r * 3.5 - 0.1, fa5d, 6.0, t);
    float r5d  = ring(r, 0.15, 0.06);
    col = mix(col, cPink * 0.85, d2 * r5d * 0.6);

    // ═══════ LAYER 10: Fine accent – 16-fold ═══════
    float fa6 = foldAngle(a + t * 0.2, 16.0);
    float p6  = petalShape(r, fa6, 16.0, t);
    float r6  = ring(r, 0.53, 0.035);
    col = mix(col, cPink, p6 * r6 * 0.6);

    // ═══════ LAYER 11: Circle outlines ═══════
    float line1 = smoothstep(0.003, 0.0, abs(r - 0.78));
    float line2 = smoothstep(0.002, 0.0, abs(r - 0.48));
    float line3 = smoothstep(0.002, 0.0, abs(r - 0.30));
    float line4 = smoothstep(0.002, 0.0, abs(r - 0.10));
    col += cIce * (line1 + line2 * 0.6 + line3 * 0.5 + line4 * 0.4) * 0.35;

    // ═══════ Center lotus bloom ═══════
    float c1 = exp(-r * r * 25.0);
    float c2 = exp(-r * r * 80.0);
    float c3 = exp(-r * r * 300.0);
    col += cMint  * c1 * (0.3 + 0.15 * sin(t * 2.0));
    col += cCyan  * c2 * (0.4 + 0.2 * sin(t * 2.5 + 1.0));
    col += cIce   * c3 * 0.8;

    float ca = foldAngle(a + t * 0.3, 6.0);
    float star = exp(-ca * ca * 200.0) * smoothstep(0.12, 0.0, r) * 0.8;
    col += cGreen * star;

    // ═══════ Outer bloom halo ═══════
    float halo = ring(r, 0.82, 0.08) * (0.25 + 0.2 * sin(a * 12.0 + t));
    col += cIce * halo * 0.3;
    float outerGlow = ring(r, 0.90, 0.15) * (0.15 + 0.1 * sin(a * 24.0 - t * 2.0));
    col += cPurple * outerGlow * 0.2;

    // ═══════ FLOATING PARTICLES ═══════
    // Two layers at different depths for parallax feel
    float part1 = particles(uv, t, 0.0);
    float part2 = particles(uv * 0.85, t * 0.7, 1.0);
    col += cMint * part1 * 0.4;
    col += cCyan * part2 * 0.25;
    // Warm particle accent near center
    float part3 = particles(uv * 1.5, t * 1.2, 2.0);
    col += cPink * part3 * 0.2 * smoothstep(0.5, 0.0, r);

    // ═══════ SACRED GEOMETRY OVERLAY ═══════
    // Rotating hexagram behind the mandala
    float hexA = foldAngle(a + t * 0.05, 6.0);
    float hexLine = smoothstep(0.004, 0.0, abs(hexA - 0.02)) * smoothstep(0.1, 0.2, r) * smoothstep(0.9, 0.75, r);
    col += cIce * hexLine * 0.12;

    // Counter-rotating triangle grid
    float triA = foldAngle(angle - t * 0.08, 3.0);
    float triLine = smoothstep(0.005, 0.0, abs(triA - 0.04)) * smoothstep(0.2, 0.35, r) * smoothstep(0.95, 0.7, r);
    col += cPurple * triLine * 0.08;

    // ═══════ POST-PROCESSING ═══════
    // Vignette – deeper for immersion
    col *= 1.0 - 0.6 * smoothstep(0.4, 1.5, r);

    // Chromatic color-shift shimmer
    col += 0.025 * vec3(
        sin(r * 20.0 + t * 3.0),
        sin(r * 20.0 + t * 3.0 + TAU / 3.0),
        sin(r * 20.0 + t * 3.0 + TAU * 2.0 / 3.0)
    ) * smoothstep(1.0, 0.3, r);

    // Slow global color breathing – shifts hue over time
    float hueShift = 0.03 * sin(t * 0.5);
    col.r += hueShift;
    col.b -= hueShift;

    // HDR tone-mapping (Reinhard)
    col = col / (1.0 + col * 0.15);

    // Slight saturation boost for vibrancy
    float lum = dot(col, vec3(0.299, 0.587, 0.114));
    col = mix(vec3(lum), col, 1.2);

    fragColor = vec4(col, 1.0);
}