Back to shaders

Shader test bench

20251208

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 fragColor gl_FragColor

uniform float u_time;
uniform vec2  u_resolution;

// Theme color palette - deep purples to teals
const vec3 COL0 = vec3(0.212, 0.176, 0.471); // #362d78
const vec3 COL1 = vec3(0.322, 0.247, 0.639); // #523fa3
const vec3 COL2 = vec3(0.569, 0.424, 0.800); // #916ccc
const vec3 COL3 = vec3(0.741, 0.631, 0.898); // #bda1e5
const vec3 COL4 = vec3(0.784, 0.753, 0.914); // #c8c0e9
const vec3 COL5 = vec3(0.518, 0.729, 0.906); // #84bae7
const vec3 COL6 = vec3(0.318, 0.416, 0.831); // #516ad4
const vec3 COL7 = vec3(0.200, 0.247, 0.529); // #333f87
const vec3 COL8 = vec3(0.161, 0.188, 0.224); // #293039
const vec3 COL9 = vec3(0.157, 0.212, 0.192); // #283631

#define PI 3.14159265359
#define TAU 6.28318530718

// Rotation matrices
mat2 rot2D(float a) {
    float s = sin(a), c = cos(a);
    return mat2(c, -s, s, c);
}

mat3 rotateX(float a) {
    float s = sin(a), c = cos(a);
    return mat3(1, 0, 0, 0, c, -s, 0, s, c);
}

mat3 rotateY(float a) {
    float s = sin(a), c = cos(a);
    return mat3(c, 0, s, 0, 1, 0, -s, 0, c);
}

// Smooth operators
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);
}

float smax(float a, float b, float k) {
    return -smin(-a, -b, k);
}

// Noise functions
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);
}

float fbm(vec3 p) {
    float f = 0.0;
    f += 0.5000 * noise(p); p *= 2.02;
    f += 0.2500 * noise(p); p *= 2.03;
    f += 0.1250 * noise(p); p *= 2.01;
    f += 0.0625 * noise(p);
    return f / 0.9375;
}

// SDF primitives
float sdSphere(vec3 p, float r) {
    return length(p) - r;
}

float sdBox(vec3 p, vec3 b) {
    vec3 q = abs(p) - b;
    return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}

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

float sdCappedTorus(vec3 p, vec2 sc, float ra, float rb) {
    p.x = abs(p.x);
    float k = (sc.y * p.x > sc.x * p.y) ? dot(p.xy, sc) : length(p.xy);
    return sqrt(dot(p, p) + ra * ra - 2.0 * ra * k) - rb;
}

float sdOctahedron(vec3 p, float s) {
    p = abs(p);
    float m = p.x + p.y + p.z - s;
    vec3 q;
    if(3.0 * p.x < m) q = p.xyz;
    else if(3.0 * p.y < m) q = p.yzx;
    else if(3.0 * p.z < m) q = p.zxy;
    else return m * 0.57735027;
    float k = clamp(0.5 * (q.z - q.y + s), 0.0, s);
    return length(vec3(q.x, q.y - s + k, q.z - k));
}

float sdGyroid(vec3 p, float scale, float thickness) {
    p *= scale;
    return (abs(dot(sin(p), cos(p.zxy))) - thickness) / scale;
}

float sdMenger(vec3 p, int iterations) {
    float d = sdBox(p, vec3(1.0));
    float s = 1.0;
    for(int i = 0; i < 4; i++) {
        if(i >= iterations) break;
        vec3 a = mod(p * s, 2.0) - 1.0;
        s *= 3.0;
        vec3 r = abs(1.0 - 3.0 * abs(a));
        float c = (min(max(r.x, r.y), min(max(r.y, r.z), max(r.z, r.x))) - 1.0) / s;
        d = max(d, c);
    }
    return d;
}

// Kaleidoscopic fold
vec3 kaleido(vec3 p, float n) {
    float angle = PI / n;
    for(int i = 0; i < 6; i++) {
        p.xy = abs(p.xy);
        p.xy *= rot2D(angle);
    }
    return p;
}

// Global material ID for coloring
float gMatId = 0.0;

// Scene definition
float map(vec3 p) {
    float t = u_time * 0.4;

    // Rotation animation
    mat3 rot = rotateY(t * 0.3) * rotateX(t * 0.2);

    // Central crystalline structure
    vec3 p1 = rot * p;

    // Morphing between shapes
    float morph = sin(t * 0.5) * 0.5 + 0.5;
    float octa = sdOctahedron(p1, 1.2);

    // Add gyroid surface detail
    float gyroid = sdGyroid(p1 + vec3(t * 0.1), 4.0, 0.03);
    float crystal = smax(octa, -gyroid, 0.1);

    // Inner glowing core
    float core = sdSphere(p1, 0.5 + 0.1 * sin(t * 2.0));
    core = smin(core, sdOctahedron(p1, 0.7), 0.2);

    // Orbiting ring system
    vec3 p2 = p;
    p2.xy *= rot2D(t * 0.5);
    float ring1 = sdTorus(p2, vec2(2.0, 0.08));

    vec3 p3 = p;
    p3.xz *= rot2D(t * 0.4);
    p3.yz *= rot2D(PI * 0.25);
    float ring2 = sdTorus(p3, vec2(2.2, 0.06));

    vec3 p4 = p;
    p4.yz *= rot2D(t * 0.3);
    p4.xy *= rot2D(PI * 0.35);
    float ring3 = sdTorus(p4, vec2(2.4, 0.05));

    // Capped torus arcs
    vec3 p5 = p;
    p5.xz *= rot2D(t * 0.6);
    float arc1 = sdCappedTorus(p5.xzy, vec2(sin(1.0), cos(1.0)), 1.8, 0.1);

    vec3 p6 = p;
    p6.xy *= rot2D(t * 0.5 + PI);
    float arc2 = sdCappedTorus(p6.yxz, vec2(sin(1.2), cos(1.2)), 1.9, 0.08);

    // Floating particles
    float particles = 1e10;
    for(int i = 0; i < 8; i++) {
        float fi = float(i);
        float angle = fi * TAU / 8.0 + t * 0.3;
        float radius = 2.8 + sin(fi * 2.3 + t) * 0.5;
        float height = sin(fi * 1.7 + t * 0.8) * 1.5;
        vec3 orbPos = vec3(cos(angle) * radius, height, sin(angle) * radius);
        float size = 0.08 + 0.04 * sin(fi * 3.1 + t * 2.0);
        particles = min(particles, sdSphere(p - orbPos, size));
    }

    // DNA helix structure
    float helix = 1e10;
    for(int i = 0; i < 12; i++) {
        float fi = float(i);
        float helixT = fi / 12.0 * TAU + t * 0.5;
        vec3 hp1 = vec3(cos(helixT) * 0.4, fi * 0.25 - 1.5, sin(helixT) * 0.4);
        vec3 hp2 = vec3(cos(helixT + PI) * 0.4, fi * 0.25 - 1.5, sin(helixT + PI) * 0.4);
        helix = min(helix, sdSphere(p - hp1, 0.06));
        helix = min(helix, sdSphere(p - hp2, 0.06));
    }
    vec3 helixP = p;
    helixP.xz *= rot2D(t * 0.3);
    helix = min(helix, sdTorus(vec3(helixP.x, mod(helixP.y + t * 0.5, 0.5) - 0.25, helixP.z), vec2(0.4, 0.02)));

    // Combine with material IDs
    float d = crystal;
    gMatId = 0.0;

    if(core < d) { d = core; gMatId = 1.0; }

    float rings = min(ring1, min(ring2, ring3));
    if(rings < d) { d = smin(d, rings, 0.1); gMatId = 2.0; }

    float arcs = min(arc1, arc2);
    d = smin(d, arcs, 0.15);

    if(particles < d) { d = particles; gMatId = 3.0; }

    d = smin(d, helix * 0.8, 0.2);

    // Add subtle noise displacement
    d += fbm(p * 3.0 + t * 0.2) * 0.02;

    return d * 0.8;
}

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

float calcAO(vec3 p, vec3 n) {
    float occ = 0.0;
    float sca = 1.0;
    for(int i = 0; i < 5; i++) {
        float h = 0.01 + 0.12 * float(i);
        float d = map(p + h * n);
        occ += (h - d) * sca;
        sca *= 0.95;
    }
    return clamp(1.0 - 3.0 * occ, 0.0, 1.0);
}

float softShadow(vec3 ro, vec3 rd, float mint, float maxt, float k) {
    float res = 1.0;
    float t = mint;
    for(int i = 0; i < 48; i++) {
        float h = map(ro + rd * t);
        res = min(res, k * max(h, 0.0) / t);
        t += clamp(h, 0.01, 0.15);
        if(res < 0.001 || t > maxt) break;
    }
    return clamp(res, 0.0, 1.0);
}

// Subsurface scattering approximation
float subsurface(vec3 p, vec3 n, vec3 l, float thickness) {
    vec3 scatterDir = normalize(l + n * 0.5);
    float scatter = pow(clamp(dot(-scatterDir, n), 0.0, 1.0), 2.0);
    float backLight = pow(clamp(dot(l, -n), 0.0, 1.0), 3.0);
    return (scatter + backLight) * thickness;
}

// Iridescence
vec3 iridescence(float angle, float thickness) {
    float phase = TAU * thickness * cos(angle);
    return vec3(
        cos(phase),
        cos(phase + TAU / 3.0),
        cos(phase + 2.0 * TAU / 3.0)
    ) * 0.5 + 0.5;
}

// Color palette with smooth interpolation
vec3 getColor(float t, float variation) {
    t = fract(t + variation * 0.1);

    vec3 colors[10] = vec3[](COL0, COL1, COL2, COL3, COL4, COL5, COL6, COL7, COL8, COL9);

    float idx = t * 9.0;
    int i = int(idx);
    float f = fract(idx);
    f = f * f * (3.0 - 2.0 * f); // smoothstep

    return mix(colors[i], colors[(i + 1) % 10], f);
}

// Volumetric glow
vec3 volumetricGlow(vec3 ro, vec3 rd, float tmax) {
    vec3 glow = vec3(0.0);
    float dt = 0.15;
    float t = 0.1;

    for(int i = 0; i < 30; i++) {
        vec3 p = ro + rd * t;
        float d = map(p);

        if(d < 0.5) {
            float intensity = exp(-d * 4.0) * 0.02;
            vec3 glowCol = mix(COL2, COL5, sin(t + u_time) * 0.5 + 0.5);
            glow += glowCol * intensity * (1.0 - t / tmax);
        }

        t += dt;
        if(t > tmax) break;
    }

    return glow;
}

void main() {
    vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / min(u_resolution.x, u_resolution.y);

    float t = u_time;

    // Smooth camera orbit
    float camRadius = 5.5 + sin(t * 0.2) * 0.5;
    float camHeight = 2.5 + sin(t * 0.15) * 1.5;
    float camAngle = t * 0.15;

    vec3 ro = vec3(
        sin(camAngle) * camRadius,
        camHeight,
        cos(camAngle) * camRadius
    );
    vec3 ta = vec3(0.0, 0.0, 0.0);

    // Camera shake for organic feel
    ro += vec3(sin(t * 3.0), cos(t * 2.7), sin(t * 3.3)) * 0.02;

    // Camera matrix
    vec3 ww = normalize(ta - ro);
    vec3 uu = normalize(cross(ww, vec3(0.0, 1.0, 0.0)));
    vec3 vv = cross(uu, ww);

    // Slight lens distortion
    float lens = 1.8 + length(uv) * 0.1;
    vec3 rd = normalize(uv.x * uu + uv.y * vv + lens * ww);

    // Background - cosmic nebula
    vec3 bgCol = mix(COL8, COL0, uv.y * 0.5 + 0.5);
    bgCol = mix(bgCol, COL9, smoothstep(-0.3, 0.3, uv.x) * 0.4);

    // Add stars
    float stars = pow(hash(vec3(floor(uv * 200.0), 1.0)), 20.0);
    bgCol += stars * COL4 * 0.5;

    // Nebula clouds
    vec2 nebulaUV = uv * 2.0 + vec2(t * 0.02);
    float nebula = fbm(vec3(nebulaUV * 3.0, t * 0.1));
    bgCol += mix(COL1, COL6, nebula) * nebula * 0.15;

    vec3 col = bgCol;

    // Volumetric glow pass
    vec3 glow = volumetricGlow(ro, rd, 12.0);

    // Raymarching
    float tRay = 0.0;
    float tmax = 15.0;

    for(int i = 0; i < 120; i++) {
        vec3 p = ro + rd * tRay;
        float d = map(p);
        if(d < 0.0005 || tRay > tmax) break;
        tRay += d * 0.7;
    }

    if(tRay < tmax) {
        vec3 p = ro + rd * tRay;
        vec3 n = calcNormal(p);

        // Store material ID before recalculating
        map(p);
        float matId = gMatId;

        // Multiple light sources
        vec3 l1 = normalize(vec3(1.0, 1.0, 0.5));
        vec3 l2 = normalize(vec3(-0.5, 0.5, -1.0));
        vec3 l3 = normalize(vec3(0.0, -1.0, 0.0));

        // Dynamic base color
        float colorSeed = dot(p, vec3(0.15)) + t * 0.1 + matId * 0.3;
        vec3 baseCol = getColor(colorSeed, matId);

        // Enhance based on material
        if(matId == 1.0) {
            baseCol = mix(baseCol, COL3, 0.5); // Core - brighter
        } else if(matId == 3.0) {
            baseCol = mix(baseCol, COL5, 0.6); // Particles - cyan tint
        }

        // View-dependent effects
        float NdotV = max(dot(n, -rd), 0.0);
        float fresnel = pow(1.0 - NdotV, 4.0);

        // Iridescent sheen
        vec3 iridescentCol = iridescence(acos(NdotV), 2.0 + sin(t + p.y * 2.0) * 0.5);
        baseCol = mix(baseCol, baseCol * iridescentCol, 0.3);

        // Lighting calculations
        float diff1 = max(dot(n, l1), 0.0);
        float diff2 = max(dot(n, l2), 0.0) * 0.4;
        float diff3 = max(dot(n, l3), 0.0) * 0.2; // Bottom fill

        // Specular
        vec3 h1 = normalize(l1 - rd);
        vec3 h2 = normalize(l2 - rd);
        float spec1 = pow(max(dot(n, h1), 0.0), 64.0);
        float spec2 = pow(max(dot(n, h2), 0.0), 32.0);

        // Shadows
        float shadow1 = softShadow(p + n * 0.01, l1, 0.02, 8.0, 24.0);
        float shadow2 = softShadow(p + n * 0.01, l2, 0.02, 8.0, 16.0);

        // Ambient occlusion
        float ao = calcAO(p, n);

        // Subsurface scattering for core
        float sss = 0.0;
        if(matId == 1.0) {
            sss = subsurface(p, n, l1, 0.5);
        }

        // Combine lighting
        vec3 ambient = mix(COL0, COL7, 0.5) * 0.25;

        col = baseCol * ambient;
        col += baseCol * diff1 * shadow1 * COL4 * 1.2;
        col += baseCol * diff2 * shadow2 * COL6 * 0.8;
        col += baseCol * diff3 * COL9 * 0.5;

        // Specular highlights
        col += spec1 * shadow1 * COL3 * 0.8;
        col += spec2 * shadow2 * mix(COL5, COL2, 0.5) * 0.4;

        // Fresnel rim
        vec3 rimCol = mix(COL5, COL2, sin(t + p.y * 3.0) * 0.5 + 0.5);
        col += fresnel * rimCol * 0.5;

        // SSS glow
        col += sss * mix(COL2, COL3, 0.5) * 0.4;

        // Apply AO
        col *= ao * 0.7 + 0.3;

        // Inner glow for core
        if(matId == 1.0) {
            col += COL3 * 0.2 * (1.0 - NdotV);
        }

        // Distance fog with color
        float fog = 1.0 - exp(-tRay * 0.06);
        vec3 fogCol = mix(COL0, COL7, 0.3);
        col = mix(col, fogCol, fog * 0.7);
    }

    // Add volumetric glow
    col += glow;

    // Bloom effect (fake)
    vec2 bloomUV = uv * 0.5;
    float bloomIntensity = exp(-length(bloomUV) * 2.0) * 0.1;
    col += mix(COL2, COL5, sin(t) * 0.5 + 0.5) * bloomIntensity;

    // Chromatic aberration
    vec2 caOffset = uv * 0.002;
    vec3 ca = vec3(
        col.r,
        col.g,
        col.b
    );
    // Subtle shift
    ca.r = mix(col.r, col.r * 1.02, length(uv));
    ca.b = mix(col.b, col.b * 0.98, length(uv));
    col = ca;

    // Vignette
    float vignette = 1.0 - pow(length(uv * 0.8), 2.5);
    col *= vignette;

    // Film grain
    float grain = hash(vec3(gl_FragCoord.xy, fract(t))) * 0.03;
    col += grain - 0.015;

    // Tone mapping (ACES approximation)
    col = col * (2.51 * col + 0.03) / (col * (2.43 * col + 0.59) + 0.14);

    // Gamma correction
    col = pow(clamp(col, 0.0, 1.0), vec3(0.4545));

    // Final color grading - push towards theme
    col = mix(col, col * vec3(0.92, 0.88, 1.12), 0.25);

    // Subtle purple tint in shadows
    float luma = dot(col, vec3(0.299, 0.587, 0.114));
    col = mix(col, mix(col, COL1, 0.15), 1.0 - luma);

    fragColor = vec4(col, 1.0);
}