Back to shaders

Shader test bench

20260415

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

#define TAU  6.28318530718
#define PI   3.14159265359

// ─── Palette (black ink on white) ────────────────────────────
const vec3 cWhite  = vec3(1.000, 1.000, 1.000);
const vec3 cBlack  = vec3(0.000, 0.000, 0.000);
const vec3 cGold   = cBlack;
const vec3 cRose   = cBlack;
const vec3 cIndigo = cBlack;
const vec3 cTeal   = cBlack;
const vec3 cAmber  = cBlack;
const vec3 cViolet = cBlack;

// ─── Cycle: 5 patterns, 8s each ───────────────────────────────
#define PERIOD     40.0
#define PER_SHAPE   6.0

float cycleT()    { return mod(iTime, PERIOD); }
int   cycleIdx()  { return int(cycleT() / PER_SHAPE) % 5; }
float cycleBlend(){
    float seg = cycleT() / PER_SHAPE;
    float raw = fract(seg);
    float b   = smoothstep(0.72, 1.0, raw);
    return b*b*b*(b*(b*6.0-15.0)+10.0);
}

// ─── Color per pattern ────────────────────────────────────────
vec3 patternColor(int idx) {
    if (idx == 0) return cGold;    // Seed of Life
    if (idx == 1) return cTeal;    // Flower of Life
    if (idx == 2) return cIndigo;  // Metatron's Cube
    if (idx == 3) return cRose;    // Sri Yantra
    return cViolet;                // Golden spiral rings
}

vec3 accentColor(int idx) {
    if (idx == 0) return cAmber;
    if (idx == 1) return cGold;
    if (idx == 2) return cViolet;
    if (idx == 3) return cGold;
    return cRose;
}

// ─── SDF Primitives ───────────────────────────────────────────
float sdCircle(vec2 p, vec2 c, float r) {
    return length(p - c) - r;
}

float sdCircleStroke(vec2 p, vec2 c, float r, float w) {
    return abs(sdCircle(p, c, r)) - w;
}

// Line segment SDF
float sdSeg(vec2 p, vec2 a, vec2 b, float w) {
    vec2 ba = b - a, pa = p - a;
    float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
    return length(pa - ba*h) - w;
}

// Angular reveal mask — 0 outside reveal arc, 1 inside
float revealMask(vec2 p, float startAngle, float revealArc) {
    float a = mod(atan(p.y, p.x) - startAngle + TAU*2.0, TAU);
    return step(a, revealArc);
}

// Soft stroke renderer — returns [0,1] ink value
float stroke(float sdf, float aa) {
    return 1.0 - smoothstep(-aa, aa, sdf);
}

// ─── Pattern 0: Seed of Life (7 circles) ─────────────────────
float seedOfLifeSDF(vec2 p, float r) {
    float w  = r * 0.028;
    float d  = sdCircleStroke(p, vec2(0.0), r, w);
    d = min(d, sdCircle(p, vec2(0.0), r*0.04) );  // center dot fill
    for (int i = 0; i < 6; i++) {
        float a = float(i) * TAU / 6.0;
        d = min(d, sdCircleStroke(p, vec2(cos(a),sin(a))*r, r, w));
    }
    return d;
}

// ─── Pattern 1: Flower of Life (19 circles) ───────────────────
float flowerOfLifeSDF(vec2 p, float r) {
    float w = r * 0.022;
    float d = sdCircleStroke(p, vec2(0.0), r, w);
    // Ring 1: 6 circles
    for (int i = 0; i < 6; i++) {
        float a = float(i) * TAU / 6.0;
        vec2 c  = vec2(cos(a), sin(a)) * r;
        d = min(d, sdCircleStroke(p, c, r, w));
    }
    // Ring 2: 12 circles
    for (int i = 0; i < 6; i++) {
        float a  = float(i) * TAU / 6.0;
        float a2 = a + PI / 6.0;
        vec2  c1 = vec2(cos(a),  sin(a))  * r * 2.0;
        vec2  c2 = vec2(cos(a2), sin(a2)) * r * sqrt(3.0);
        d = min(d, sdCircleStroke(p, c1, r, w));
        d = min(d, sdCircleStroke(p, c2, r, w));
    }
    return d;
}

// ─── Pattern 2: Metatron's Cube ───────────────────────────────
float metatronSDF(vec2 p, float r) {
    float wc = r * 0.020;  // circle width
    float wl = r * 0.010;  // line width
    float d  = sdCircleStroke(p, vec2(0.0), r*0.01, wc*0.5); // center

    // 13 circles: center + 6 inner + 6 outer
    vec2 centers[13];
    centers[0]  = vec2(0.0);
    for (int i = 0; i < 6; i++) {
        float a = float(i) * TAU / 6.0;
        centers[i+1]  = vec2(cos(a), sin(a)) * r;
        centers[i+7]  = vec2(cos(a), sin(a)) * r * 2.0;
    }
    for (int i = 0; i < 13; i++)
        d = min(d, sdCircleStroke(p, centers[i], r, wc));

    // Connecting lines — all 13 nodes to all others (78 lines total, draw subset)
    for (int i = 0; i < 13; i++)
        for (int j = i+1; j < 13; j++)
            d = min(d, sdSeg(p, centers[i], centers[j], wl));

    return d;
}

// ─── Pattern 3: Sri Yantra (9 nested triangles, interlocking) ─
float sriYantraSDF(vec2 p, float r) {
    float w = r * 0.018;
    float d = 1e4;
    for (int tier = 0; tier < 9; tier++) {
        float scale = r * (1.0 - float(tier) * 0.085);
        float flip  = (tier % 2 == 0) ? 1.0 : -1.0;
        for (int side = 0; side < 3; side++) {
            float a0 = float(side)   * TAU/3.0 + flip*PI*0.5;
            float a1 = float(side+1) * TAU/3.0 + flip*PI*0.5;
            vec2  v0 = vec2(cos(a0), sin(a0)) * scale;
            vec2  v1 = vec2(cos(a1), sin(a1)) * scale;
            // Arc bow outward
            vec2  edge  = v1 - v0;
            vec2  perp  = normalize(vec2(-edge.y, edge.x));
            float bow   = flip * scale * 0.10;
            for (int k = 0; k < 8; k++) {
                float f0 = float(k)   / 8.0;
                float f1 = float(k+1) / 8.0;
                vec2  p0 = mix(v0, v1, f0) + perp * bow * sin(f0 * PI);
                vec2  p1 = mix(v0, v1, f1) + perp * bow * sin(f1 * PI);
                d = min(d, sdSeg(p, p0, p1, w));
            }
        }
    }
    // Outer enclosing circle
    d = min(d, sdCircleStroke(p, vec2(0.0), r, w));
    return d;
}

// ─── Pattern 4: Golden Spiral Rings ───────────────────────────
float goldenSpiralSDF(vec2 p, float r) {
    float w     = r * 0.022;
    float phi   = 1.61803398875;
    float d     = 1e4;
    float ri    = r * 0.06;
    for (int i = 0; i < 9; i++) {
        d  = min(d, sdCircleStroke(p, vec2(0.0), ri, w));
        ri *= phi * 0.72;
    }
    // Phi-ratio dividing lines
    for (int i = 0; i < 8; i++) {
        float a  = float(i) * PI / 4.0 + TAU * 0.05;
        vec2  v0 = vec2(0.0);
        vec2  v1 = vec2(cos(a), sin(a)) * r * 1.1;
        d = min(d, sdSeg(p, v0, v1, w * 0.5));
    }
    return d;
}

// ─── Assemble pattern with blend & reveal ─────────────────────
float patternSDF(vec2 p, int idx, float r) {
    if (idx == 0) return seedOfLifeSDF(p, r);
    if (idx == 1) return flowerOfLifeSDF(p, r);
    if (idx == 2) return metatronSDF(p, r);
    if (idx == 3) return sriYantraSDF(p, r);
    return goldenSpiralSDF(p, r);
}

// ─── 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 r   = 0.70;  // base radius

    // Slow global breath
    float breath = 1.0 + 0.025 * sin(t * 0.5);
    r *= breath;

    // Slow rotation of entire canvas
    float rotAngle = t * 0.04;
    float rc = cos(rotAngle), rs = sin(rotAngle);
    vec2  pr = vec2(rc*st.x - rs*st.y, rs*st.x + rc*st.y);

    int   idx0 = cycleIdx();
    int   idx1 = (idx0 + 1) % 5;
    float blend= cycleBlend();

    vec3  col0 = patternColor(idx0);
    vec3  col1 = patternColor(idx1);
    vec3  acc0 = accentColor(idx0);
    vec3  acc1 = accentColor(idx1);

    // Per-pattern local rotation (each pattern has its own slow spin)
    float spinSpeed[5];
    spinSpeed[0] = 0.05;
    spinSpeed[1] = 0.03;
    spinSpeed[2] = 0.02;
    spinSpeed[3] = 0.06;
    spinSpeed[4] = 0.08;

    float ra0 = t * spinSpeed[idx0];
    float ra1 = t * spinSpeed[idx1];
    float c0  = cos(ra0), s0 = sin(ra0);
    float c1  = cos(ra1), s1 = sin(ra1);
    vec2  p0  = vec2(c0*pr.x - s0*pr.y, s0*pr.x + c0*pr.y);
    vec2  p1  = vec2(c1*pr.x - s1*pr.y, s1*pr.x + c1*pr.y);

    float sdf0 = patternSDF(p0, idx0, r);
    float sdf1 = patternSDF(p1, idx1, r);

    float aa = 0.004;

    // Ink layer 0
    float ink0 = stroke(sdf0, aa);
    // Ink layer 1
    float ink1 = stroke(sdf1, aa);

    // Radial reveal wipe on the incoming pattern
    float revealProgress = blend;
    float revealArc = revealProgress * TAU;
    float mask1 = revealMask(p1, -PI * 0.5, revealArc);
    ink1 *= mask1;

    // Blend the two inks
    float inkFinal = max(ink0 * (1.0 - blend * 0.85), ink1);

    // Color the ink: primary + accent glow based on distance from center
    float dist    = length(pr);
    float radFade = smoothstep(r * 1.15, r * 0.1, dist);
    vec3  inkCol0 = mix(col0, acc0, radFade * 0.55);
    vec3  inkCol1 = mix(col1, acc1, radFade * 0.55);
    vec3  inkCol  = mix(inkCol0, inkCol1, blend);

    // White background — faint concentric ghost rings (light grey)
    vec3 bg = cWhite;
    for (int i = 1; i <= 6; i++) {
        float gr = r * float(i) * 0.22;
        float gd = abs(length(pr) - gr) - r * 0.003;
        bg -= vec3(0.88) * 0.10 * stroke(gd, aa * 1.5);
    }

    // Compose ink onto background
    vec3 col = mix(bg, inkCol, inkFinal);

    // Soft radial vignette — edges stay paper-white
    float vig = smoothstep(1.10, 0.55, length(st));
    col = mix(cWhite, col, vig);

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