Back to shaders

Shader test bench

20260121

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 - Celtic Sacred Geometry
// Black & white sacred geometry with Celtic knot patterns

uniform float u_time;
uniform vec2 u_resolution;

#define PI 3.14159265359
#define TAU 6.28318530718

// ============================================
// UTILITY FUNCTIONS
// ============================================

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

// ============================================
// SDF PRIMITIVES
// ============================================

float sdCircle(vec2 p, float r) {
    return length(p) - r;
}

float sdRing(vec2 p, float r, float w) {
    return abs(length(p) - r) - w;
}

float sdArc(vec2 p, float r, float w, float startAngle, float sweep) {
    float angle = atan(p.y, p.x);
    float midAngle = startAngle + sweep * 0.5;
    float halfSweep = sweep * 0.5;

    float angDist = abs(mod(angle - midAngle + PI, TAU) - PI);

    if (angDist < halfSweep) {
        return abs(length(p) - r) - w;
    } else {
        vec2 p1 = r * vec2(cos(startAngle), sin(startAngle));
        vec2 p2 = r * vec2(cos(startAngle + sweep), sin(startAngle + sweep));
        return min(length(p - p1), length(p - p2)) - w;
    }
}

float sdVesicaPiscis(vec2 p, float r, float d) {
    p = abs(p);
    float b = sqrt(r * r - d * d);
    return ((p.y - b) * d > p.x * b)
        ? length(p - vec2(0.0, b))
        : length(p - vec2(-d, 0.0)) - r;
}

// ============================================
// CELTIC KNOT COMPONENTS
// ============================================

float sdTriquetraLobe(vec2 p, float scale, float w) {
    p /= scale;
    float r = 0.5;
    vec2 center = vec2(0.0, r * 0.577);
    float d = sdRing(p - center, r, w / scale);
    return d * scale;
}

float sdTriquetra(vec2 p, float scale, float w) {
    float d = 1e10;
    for (int i = 0; i < 3; i++) {
        vec2 rp = p * rot(float(i) * TAU / 3.0);
        d = min(d, sdTriquetraLobe(rp, scale, w));
    }
    return d;
}

// ============================================
// FLOWER OF LIFE
// ============================================

float sdFlowerOfLife(vec2 p, float r, float w) {
    float d = 1e10;

    // Central circle
    d = min(d, sdRing(p, r, w));

    // First ring of 6
    for (int i = 0; i < 6; i++) {
        float angle = float(i) * TAU / 6.0;
        vec2 center = r * vec2(cos(angle), sin(angle));
        d = min(d, sdRing(p - center, r, w));
    }

    // Second ring of 6 (rotated)
    for (int i = 0; i < 6; i++) {
        float angle = float(i) * TAU / 6.0 + TAU / 12.0;
        vec2 center = r * 1.732 * vec2(cos(angle), sin(angle));
        d = min(d, sdRing(p - center, r, w));
    }

    // Outer ring of 12
    for (int i = 0; i < 12; i++) {
        float angle = float(i) * TAU / 12.0;
        vec2 center = r * 2.0 * vec2(cos(angle), sin(angle));
        d = min(d, sdRing(p - center, r, w));
    }

    return d;
}

// ============================================
// CELTIC KNOT WEAVE
// ============================================

struct Weave {
    float dist;
    float depth;
};

Weave celticKnot(vec2 p, float t) {
    Weave w;
    w.dist = 1e10;
    w.depth = 0.0;

    float scale = 0.8;
    float strokeW = 0.022;
    float animAngle = t * 0.1;

    // === CENTRAL TRIQUETRA ===
    vec2 p1 = p * rot(animAngle);
    float tri = sdTriquetra(p1, 0.35 * scale, strokeW);
    float triAngle = atan(p1.y, p1.x);
    float triDepth = sin(triAngle * 3.0 + t * 0.5) * 0.5 + 0.5;

    if (tri < w.dist) {
        w.dist = tri;
        w.depth = triDepth;
    }

    // === OUTER RINGS ===
    vec2 p2 = p * rot(-animAngle * 0.5);
    float ring1 = sdRing(p2, 0.55 * scale, strokeW);
    float ring1Depth = sin(atan(p2.y, p2.x) * 6.0 - t) * 0.5 + 0.5;

    if (ring1 < w.dist) {
        w.dist = ring1;
        w.depth = ring1Depth;
    }

    vec2 p3 = p * rot(animAngle * 0.7);
    float ring2 = sdRing(p3, 0.42 * scale, strokeW);
    float ring2Depth = sin(atan(p3.y, p3.x) * 4.0 + t * 0.8) * 0.5 + 0.5;

    if (ring2 < w.dist) {
        w.dist = ring2;
        w.depth = ring2Depth;
    }

    // === INTERLOCKING ARCS ===
    for (int i = 0; i < 6; i++) {
        float angle = float(i) * TAU / 6.0 + animAngle;
        vec2 arcCenter = 0.48 * scale * vec2(cos(angle), sin(angle));

        vec2 ap = p - arcCenter;
        ap *= rot(-angle);

        float arc = sdArc(ap, 0.18 * scale, strokeW, PI * 0.3, PI * 1.4);
        float arcDepth = sin(float(i) * 1.5 + t) * 0.5 + 0.5;
        arcDepth = mod(float(i), 2.0) < 0.5 ? arcDepth : 1.0 - arcDepth;

        if (arc < w.dist) {
            w.dist = arc;
            w.depth = arcDepth;
        }
    }

    // === SMALL DECORATIVE TRIQUETRAS ===
    for (int i = 0; i < 6; i++) {
        float angle = float(i) * TAU / 6.0 - animAngle * 0.3;
        vec2 pos = 0.7 * scale * vec2(cos(angle), sin(angle));

        vec2 tp = (p - pos) * rot(angle + t * 0.2);
        float smallTri = sdTriquetra(tp, 0.1 * scale, strokeW * 0.7);
        float smallDepth = sin(float(i) + t * 0.6) * 0.5 + 0.5;

        if (smallTri < w.dist) {
            w.dist = smallTri;
            w.depth = smallDepth;
        }
    }

    return w;
}

// ============================================
// BACKGROUND SACRED GEOMETRY
// ============================================

float sacredBackground(vec2 p, float t) {
    float d = 1e10;

    // Flower of Life
    float fol = sdFlowerOfLife(p * 1.5, 0.2, 0.003);
    d = min(d, fol);

    // Seed of Life at corners
    vec2 corners[4];
    corners[0] = vec2(-0.6, -0.4);
    corners[1] = vec2(0.6, -0.4);
    corners[2] = vec2(-0.6, 0.4);
    corners[3] = vec2(0.6, 0.4);

    for (int i = 0; i < 4; i++) {
        vec2 cp = p - corners[i];
        cp *= rot(t * 0.05 * (mod(float(i), 2.0) * 2.0 - 1.0));

        float seed = sdRing(cp, 0.08, 0.002);
        for (int j = 0; j < 6; j++) {
            float angle = float(j) * TAU / 6.0;
            vec2 sc = 0.08 * vec2(cos(angle), sin(angle));
            seed = min(seed, sdRing(cp - sc, 0.08, 0.002));
        }
        d = min(d, seed);
    }

    // Vesica Piscis elements
    for (int i = 0; i < 4; i++) {
        float angle = float(i) * TAU / 4.0 + PI / 4.0 + t * 0.03;
        vec2 vp = 0.85 * vec2(cos(angle), sin(angle));

        vec2 vpp = p - vp;
        vpp *= rot(angle + PI / 2.0);

        float vesica = abs(sdVesicaPiscis(vpp, 0.12, 0.06)) - 0.003;
        d = min(d, vesica);
    }

    return d;
}

// ============================================
// SHADING
// ============================================

vec3 shadeKnot(Weave w, vec2 p, float t) {
    vec3 col = vec3(0.0);

    if (w.dist < 0.0) {
        float edge = smoothstep(0.0, -0.008, w.dist);
        float shade = 0.7 + 0.3 * w.depth;

        float highlight = smoothstep(-0.015, -0.005, w.dist);
        shade += highlight * 0.2;

        float edgeDark = smoothstep(-0.022, -0.008, w.dist);
        shade *= 0.8 + 0.2 * edgeDark;

        col = vec3(shade) * edge;

        float innerShadow = smoothstep(-0.018, -0.005, w.dist);
        col *= 0.85 + 0.15 * innerShadow;
    } else {
        float glow = exp(-w.dist * 80.0) * 0.3;
        col = vec3(glow);
    }

    return col;
}

float shadeBackground(float d, float t) {
    float col = 0.0;

    if (d < 0.0) {
        col = 0.15;
    } else {
        float glow = exp(-d * 150.0) * 0.1;
        col = glow;
    }

    return col;
}

// ============================================
// MAIN
// ============================================

#define fragColor gl_FragColor

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

    // Background
    float bgGrad = 1.0 - length(uv) * 0.3;
    vec3 col = vec3(0.02) * bgGrad;

    // Sacred geometry background
    float bgPattern = sacredBackground(uv, t);
    float bgShade = shadeBackground(bgPattern, t);
    col += vec3(bgShade) * 0.5;

    // Main Celtic knot
    Weave knot = celticKnot(uv, t);
    vec3 knotCol = shadeKnot(knot, uv, t);

    // Composite
    float knotMask = smoothstep(0.01, -0.01, knot.dist);
    col = mix(col, knotCol, knotMask + (1.0 - knotMask) * knotCol);

    // Pulse that highlights the knot strands as it radiates outward
    float pulseEffect = sin(length(uv) * 15.0 - t * 2.0) * smoothstep(0.02, -0.01, knot.dist) * 0.15;

    col += vec3(pulseEffect);

    // Central glow
    float centerGlow = exp(-length(uv) * 4.0) * 0.15;
    col += vec3(centerGlow);

    // Vignette
    float vignette = 1.0 - pow(length(uv) * 1.1, 2.0);
    vignette = clamp(vignette, 0.0, 1.0);
    col *= vignette;

    // Film grain
    float grain = fract(sin(dot(uv * t * 0.1, vec2(12.9898, 78.233))) * 43758.5453);
    col += (grain - 0.5) * 0.02;

    // Contrast
    col = smoothstep(0.0, 1.0, col);

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