Back to shaders

Shader test bench

20260106

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;
// Enhanced Topology Shader for TouchDesigner
// Features: Klein bottle, Möbius forms, iridescence, bloom, subsurface scattering

#define fragColor gl_FragColor
uniform float u_time;
uniform vec2  u_resolution;

// Original color palette - topology theme (preserved)
const vec3 color1 = vec3(0.212, 0.176, 0.471); // #362d78 deep violet
const vec3 color2 = vec3(0.322, 0.247, 0.639); // #523fa3 royal purple
const vec3 color3 = vec3(0.569, 0.424, 0.800); // #916ccc lavender
const vec3 color4 = vec3(0.741, 0.631, 0.898); // #bda1e5 soft lilac
const vec3 color5 = vec3(0.784, 0.753, 0.914); // #c8c0e9 pale violet
const vec3 color6 = vec3(0.518, 0.729, 0.906); // #84bae7 sky blue
const vec3 color7 = vec3(0.318, 0.416, 0.831); // #516ad4 cobalt
const vec3 color8 = vec3(0.200, 0.247, 0.529); // #333f87 navy
const vec3 color9 = vec3(0.161, 0.188, 0.224); // #293039 charcoal
const vec3 color10 = vec3(0.157, 0.212, 0.192); // #283631 dark teal

// Additional accent colors for iridescence
const vec3 iridescentPink = vec3(0.95, 0.6, 0.8);
const vec3 iridescentCyan = vec3(0.4, 0.9, 0.95);
const vec3 iridescentGold = vec3(0.95, 0.85, 0.5);

// ═══════════════════════════════════════════════════════════════
// UTILITY FUNCTIONS
// ═══════════════════════════════════════════════════════════════

#define PI 3.14159265359
#define TAU 6.28318530718

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

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

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

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

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

// Exponential smooth min for organic blends
float sminExp(float a, float b, float k) {
    float res = exp2(-k * a) + exp2(-k * b);
    return -log2(res) / k;
}

// Hash functions for noise
float hash(float n) { return fract(sin(n) * 43758.5453123); }
float hash21(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }

vec3 hash33(vec3 p) {
    p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
             dot(p, vec3(269.5, 183.3, 246.1)),
             dot(p, vec3(113.5, 271.9, 124.6)));
    return fract(sin(p) * 43758.5453123);
}

// Smooth noise
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(dot(i, vec3(1, 57, 113))),
                hash(dot(i + vec3(1, 0, 0), vec3(1, 57, 113))), f.x),
            mix(hash(dot(i + vec3(0, 1, 0), vec3(1, 57, 113))),
                hash(dot(i + vec3(1, 1, 0), vec3(1, 57, 113))), f.x), f.y),
        mix(mix(hash(dot(i + vec3(0, 0, 1), vec3(1, 57, 113))),
                hash(dot(i + vec3(1, 0, 1), vec3(1, 57, 113))), f.x),
            mix(hash(dot(i + vec3(0, 1, 1), vec3(1, 57, 113))),
                hash(dot(i + vec3(1, 1, 1), vec3(1, 57, 113))), f.x), f.y), f.z);
}

// Fractal Brownian Motion
float fbm(vec3 p, int octaves) {
    float value = 0.0;
    float amplitude = 0.5;
    float frequency = 1.0;
    for(int i = 0; i < octaves; i++) {
        value += amplitude * noise(p * frequency);
        amplitude *= 0.5;
        frequency *= 2.0;
    }
    return value;
}

// ═══════════════════════════════════════════════════════════════
// SIGNED DISTANCE FUNCTIONS - TOPOLOGICAL FORMS
// ═══════════════════════════════════════════════════════════════

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

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

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 sdCylinder(vec3 p, float r, float h) {
    vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(r, h);
    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}

// ═══ KLEIN BOTTLE (Figure-8 immersion) ═══
float sdKleinBottle(vec3 p, float scale) {
    p /= scale;
    
    float u = atan(p.z, p.x);
    float v = atan(p.y, length(p.xz) - 2.0);
    
    // Figure-8 Klein bottle parametric approximation
    float r = 2.0 + cos(u * 0.5) * sin(v) - sin(u * 0.5) * sin(2.0 * v);
    
    vec3 klein;
    klein.x = (2.0 + cos(v)) * cos(u);
    klein.y = sin(v);
    klein.z = (2.0 + cos(v)) * sin(u);
    
    // Distance approximation with thickness
    float d = length(p - klein * 0.5) - 0.25;
    
    // Alternative: use torus-based approximation
    vec3 p1 = p;
    p1.xy *= rot2D(u * 0.5);
    float torus1 = sdTorus(p1, vec2(1.5, 0.35));
    
    vec3 p2 = p;
    p2.x -= 3.0;
    p2.xz *= rot2D(PI * 0.5);
    float torus2 = sdTorus(p2, vec2(1.5, 0.35));
    
    d = smin(torus1, torus2, 0.8);
    
    return d * scale;
}

// ═══ MÖBIUS STRIP ═══
float sdMobius(vec3 p, float R, float w, float thickness) {
    float u = atan(p.z, p.x);
    
    // Möbius parametric surface
    vec3 center = vec3(R * cos(u), 0.0, R * sin(u));
    vec3 toP = p - center;
    
    // Local frame that twists
    vec3 tangent = vec3(-sin(u), 0.0, cos(u));
    vec3 normal = vec3(cos(u) * cos(u * 0.5), sin(u * 0.5), sin(u) * cos(u * 0.5));
    vec3 binormal = cross(tangent, normal);
    
    // Project onto local frame
    float localX = dot(toP, normal);
    float localY = dot(toP, binormal);
    
    // Rectangle cross-section
    vec2 q = abs(vec2(localX, localY)) - vec2(w, thickness);
    return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0);
}

// ═══ TORUS KNOT (p,q) ═══
float sdTorusKnot(vec3 pos, float scale, float p_param, float q_param) {
    pos /= scale;
    
    float bestDist = 1e10;
    float R = 2.0;  // Major radius
    float r = 0.8;  // Minor radius
    float tubeR = 0.2;  // Tube thickness
    
    // Sample along the knot curve
    for(float i = 0.0; i < 64.0; i++) {
        float t = i / 64.0 * TAU;
        
        // Torus knot parametric equations
        float phi = q_param * t;
        float theta = p_param * t;
        
        vec3 knotPoint;
        knotPoint.x = (R + r * cos(phi)) * cos(theta);
        knotPoint.y = r * sin(phi);
        knotPoint.z = (R + r * cos(phi)) * sin(theta);
        
        bestDist = min(bestDist, length(pos - knotPoint));
    }
    
    return (bestDist - tubeR) * scale;
}

// ═══ TREFOIL KNOT ═══
float sdTrefoil(vec3 p, float scale) {
    p /= scale;
    
    float bestDist = 1e10;
    float tubeR = 0.25;
    
    for(float i = 0.0; i < 80.0; i++) {
        float t = i / 80.0 * TAU;
        
        // Trefoil parametric
        vec3 knot;
        knot.x = sin(t) + 2.0 * sin(2.0 * t);
        knot.y = cos(t) - 2.0 * cos(2.0 * t);
        knot.z = -sin(3.0 * t);
        
        bestDist = min(bestDist, length(p - knot * 0.5));
    }
    
    return (bestDist - tubeR) * scale;
}

// ═══ TWISTED TORUS with variable twist ═══
float sdTwistedTorus(vec3 p, float R, float r, float twist) {
    float angle = atan(p.z, p.x);
    p.xy *= rot2D(angle * twist);
    return sdTorus(p, vec2(R, r));
}

// ═══ GYROID (triply periodic minimal surface) ═══
float sdGyroid(vec3 p, float scale, float thickness) {
    p *= scale;
    float g = sin(p.x) * cos(p.y) + sin(p.y) * cos(p.z) + sin(p.z) * cos(p.x);
    return (abs(g) - thickness) / scale;
}

// ═══ SCHWARZ P-SURFACE ═══
float sdSchwarzP(vec3 p, float scale, float thickness) {
    p *= scale;
    float s = cos(p.x) + cos(p.y) + cos(p.z);
    return (abs(s) - thickness) / scale;
}

// ═══ CALABI-YAU inspired manifold ═══
float sdCalabiYau(vec3 p, float scale) {
    p /= scale;
    
    float r = length(p);
    float theta = acos(p.z / max(r, 0.001));
    float phi = atan(p.y, p.x);
    
    // 5-fold symmetry inspired by Calabi-Yau
    float n = 5.0;
    float k = 2.0;
    
    float surface = pow(r, n) - cos(n * theta) - cos(n * phi) * sin(k * theta);
    
    return (abs(surface) - 0.1) * scale * 0.3;
}

// ═══════════════════════════════════════════════════════════════
// DOMAIN OPERATIONS & REPETITION
// ═══════════════════════════════════════════════════════════════

vec3 opRep(vec3 p, vec3 c) {
    return mod(p + 0.5 * c, c) - 0.5 * c;
}

vec3 opRepLim(vec3 p, float c, vec3 l) {
    return p - c * clamp(round(p / c), -l, l);
}

float opOnion(float d, float thickness) {
    return abs(d) - thickness;
}

// ═══════════════════════════════════════════════════════════════
// MAIN SCENE COMPOSITION
// ═══════════════════════════════════════════════════════════════

vec2 map(vec3 p) {
    float t = u_time * 0.3;
    vec3 origP = p;
    
    // Material ID: 1=central, 2=orbiting, 3=gyroid, 4=mobius
    float matId = 1.0;
    
    // ─── Central Form: Morphing Klein-Torus hybrid ───
    vec3 p1 = p;
    p1 *= rotY(t * 0.2) * rotX(t * 0.15);
    
    // Twisted torus as base
    float central = sdTwistedTorus(p1, 2.2, 0.5, 1.5 + sin(t * 0.5) * 0.5);
    
    // Add onion layers for depth
    float onionLayer = opOnion(central, 0.08);
    central = smin(central, onionLayer, 0.1);
    
    // Breathing animation
    float breath = 1.0 + sin(t * 0.8) * 0.05;
    central /= breath;
    
    float d = central;
    
    // ─── Orbiting Trefoil Knots ───
    for(int i = 0; i < 3; i++) {
        float angle = float(i) * TAU / 3.0 + t * 0.25;
        float orbitR = 4.5 + sin(t * 0.3 + float(i)) * 0.5;
        
        vec3 p2 = p;
        p2 *= rotY(angle);
        p2.x -= orbitR;
        p2 *= rotY(-t * 0.4 + float(i)) * rotX(t * 0.3);
        
        float knot = sdTrefoil(p2, 0.7);
        
        if(knot < d) matId = 2.0;
        d = smin(d, knot, 0.4);
    }
    
    // ─── Gyroid Shell ───
    vec3 p3 = p;
    p3 *= rotY(t * 0.1) * rotZ(t * 0.08);
    float gyroid = sdGyroid(p3, 1.2, 0.15);
    float gyroidSphere = sdSphere(p3, 3.5);
    gyroid = max(gyroid, gyroidSphere); // Bound to sphere
    gyroid = max(gyroid, -sdSphere(p3, 2.8)); // Hollow center
    
    if(gyroid < d) matId = 3.0;
    d = smin(d, gyroid, 0.3);
    
    // ─── Floating Möbius Strips ───
    for(int i = 0; i < 2; i++) {
        float mAngle = float(i) * PI + t * 0.15;
        float mR = 6.0;
        
        vec3 pm = p;
        pm *= rotY(mAngle);
        pm.x -= mR;
        pm *= rotX(t * 0.2 + float(i) * 1.5) * rotZ(t * 0.25);
        
        float mobius = sdMobius(pm, 1.2, 0.4, 0.08);
        
        if(mobius < d) matId = 4.0;
        d = smin(d, mobius, 0.25);
    }
    
    // ─── Inner Pulsing Core ───
    vec3 pc = p;
    float pulse = 1.0 + sin(t * 2.0) * 0.15;
    float core = sdSphere(pc, 0.8 * pulse);
    
    // Subtract core for hollow effect
    d = smax(d, -core, 0.2);
    
    // ─── Detail Noise Layer ───
    float noiseDetail = fbm(origP * 3.0 + t * 0.5, 3) * 0.03;
    d += noiseDetail;
    
    return vec2(d, matId);
}

// ═══════════════════════════════════════════════════════════════
// RAYMARCHING & LIGHTING
// ═══════════════════════════════════════════════════════════════

vec3 calcNormal(vec3 p) {
    const float h = 0.0001;
    const vec2 k = vec2(1, -1);
    return normalize(
        k.xyy * map(p + k.xyy * h).x +
        k.yyx * map(p + k.yyx * h).x +
        k.yxy * map(p + k.yxy * h).x +
        k.xxx * map(p + k.xxx * h).x
    );
}

vec2 raymarch(vec3 ro, vec3 rd) {
    float t = 0.0;
    float matId = 0.0;
    
    for(int i = 0; i < 180; i++) {
        vec3 p = ro + rd * t;
        vec2 res = map(p);
        float d = res.x;
        matId = res.y;
        
        if(d < 0.0003 || t > 80.0) break;
        t += d * 0.5; // Smaller step for precision
    }
    
    return vec2(t, matId);
}

float calcAO(vec3 p, vec3 n) {
    float ao = 0.0;
    float scale = 1.0;
    
    for(int i = 0; i < 6; i++) {
        float hr = 0.01 + 0.06 * float(i);
        float dd = map(p + n * hr).x;
        ao += (hr - dd) * scale;
        scale *= 0.65;
    }
    
    return clamp(1.0 - 2.5 * ao, 0.0, 1.0);
}

float calcSoftShadow(vec3 ro, vec3 rd, float mint, float maxt, float k) {
    float res = 1.0;
    float t = mint;
    float ph = 1e10;
    
    for(int i = 0; i < 64; i++) {
        float h = map(ro + rd * t).x;
        
        if(h < 0.0005) return 0.0;
        
        float y = h * h / (2.0 * ph);
        float d = sqrt(h * h - y * y);
        res = min(res, k * d / max(0.0, t - y));
        ph = h;
        t += h * 0.5;
        
        if(t > maxt) break;
    }
    
    return clamp(res, 0.0, 1.0);
}

// Subsurface scattering approximation
float calcSSS(vec3 p, vec3 n, vec3 lightDir, float thickness) {
    float scatter = 0.0;
    
    for(int i = 0; i < 5; i++) {
        float hr = 0.01 + 0.1 * float(i);
        float dd = map(p - lightDir * hr).x;
        scatter += (hr - max(dd, 0.0));
    }
    
    return clamp(scatter * thickness, 0.0, 1.0);
}

// ═══════════════════════════════════════════════════════════════
// IRIDESCENCE & COLOR
// ═══════════════════════════════════════════════════════════════

// Thin-film interference for iridescence
vec3 iridescence(float cosTheta, float thickness) {
    float delta = thickness * cosTheta;
    
    vec3 color;
    color.r = sin(delta * 12.0) * 0.5 + 0.5;
    color.g = sin(delta * 12.0 + 2.094) * 0.5 + 0.5;
    color.b = sin(delta * 12.0 + 4.189) * 0.5 + 0.5;
    
    return color;
}

// Spectral color from wavelength approximation
vec3 spectralColor(float t) {
    vec3 c;
    if(t < 0.25) {
        c = mix(color1, color7, t * 4.0);
    } else if(t < 0.5) {
        c = mix(color7, color6, (t - 0.25) * 4.0);
    } else if(t < 0.75) {
        c = mix(color6, color3, (t - 0.5) * 4.0);
    } else {
        c = mix(color3, color4, (t - 0.75) * 4.0);
    }
    return c;
}

vec3 getMaterialColor(vec3 p, vec3 n, vec3 rd, float matId, float t) {
    float time = u_time * 0.25;
    vec3 col;
    
    // Fresnel
    float fresnel = pow(1.0 - abs(dot(n, -rd)), 4.0);
    
    // View-dependent iridescence
    float iriThickness = 1.5 + sin(time + length(p) * 2.0) * 0.5;
    vec3 iri = iridescence(dot(n, -rd), iriThickness);
    
    // Position-based color flow
    float flow = sin(p.x * 2.0 + p.y * 2.0 + p.z * 2.0 + time * 2.0) * 0.5 + 0.5;
    
    if(matId < 1.5) {
        // Central form - deep purples with iridescence
        col = mix(color1, color2, flow);
        col = mix(col, color3, fresnel * 0.5);
        col = mix(col, iri * color4, fresnel * 0.4);
    } else if(matId < 2.5) {
        // Trefoil knots - spectral shifting
        float spectral = fract(length(p) * 0.3 + time);
        col = spectralColor(spectral);
        col = mix(col, iri * color5, fresnel * 0.3);
    } else if(matId < 3.5) {
        // Gyroid - ethereal blue-violet
        col = mix(color6, color7, flow);
        col = mix(col, color5, abs(n.y) * 0.4);
        col = mix(col, iri * iridescentCyan, fresnel * 0.5);
    } else {
        // Möbius - golden iridescence
        col = mix(color2, color3, flow);
        vec3 goldIri = iri * iridescentGold;
        col = mix(col, goldIri, fresnel * 0.6);
    }
    
    return col;
}

// ═══════════════════════════════════════════════════════════════
// POST-PROCESSING
// ═══════════════════════════════════════════════════════════════

// Bloom approximation
vec3 bloom(vec3 col, float threshold, float intensity) {
    vec3 bright = max(col - threshold, 0.0);
    return col + bright * intensity;
}

// Chromatic aberration
vec3 chromaticAberration(vec2 uv, vec3 col, float amount) {
    vec2 dir = uv * amount;
    
    // Simplified - in full impl would re-raymarch
    // Here we simulate with color shift
    col.r *= 1.0 + length(dir) * 0.5;
    col.b *= 1.0 - length(dir) * 0.3;
    
    return col;
}

// Film grain
float grain(vec2 uv, float time, float amount) {
    return (hash21(uv + time) - 0.5) * amount;
}

// ═══════════════════════════════════════════════════════════════
// MAIN
// ═══════════════════════════════════════════════════════════════

void main() {
    vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / u_resolution.y;
    vec2 uvOrig = gl_FragCoord.xy / u_resolution.xy;
    
    // Camera with smooth orbital motion
    float camTime = u_time * 0.12;
    float camDist = 12.0 + sin(u_time * 0.1) * 2.0;
    
    vec3 ro = vec3(
        cos(camTime) * camDist,
        sin(camTime * 0.6) * 4.0 + 2.0,
        sin(camTime) * camDist
    );
    
    vec3 target = vec3(0.0, 0.0, 0.0);
    vec3 forward = normalize(target - ro);
    vec3 right = normalize(cross(vec3(0.0, 1.0, 0.0), forward));
    vec3 up = cross(forward, right);
    
    // Lens distortion for cinematic feel
    float distortion = length(uv) * 0.08;
    vec2 uvDist = uv * (1.0 + distortion * distortion);
    
    vec3 rd = normalize(forward + uvDist.x * right + uvDist.y * up);
    
    // ─── Raymarch ───
    vec2 res = raymarch(ro, rd);
    float t = res.x;
    float matId = res.y;
    
    vec3 col = vec3(0.0);
    
    if(t < 80.0) {
        vec3 p = ro + rd * t;
        vec3 n = calcNormal(p);
        
        // Ambient occlusion
        float ao = calcAO(p, n);
        
        // Multi-light setup
        vec3 lightDir1 = normalize(vec3(3.0, 5.0, 4.0));
        vec3 lightDir2 = normalize(vec3(-4.0, 2.0, -3.0));
        vec3 lightDir3 = normalize(vec3(0.0, -1.0, 0.0)); // Bottom fill
        
        vec3 lightCol1 = color5 * 1.2;
        vec3 lightCol2 = color6 * 0.6;
        vec3 lightCol3 = color3 * 0.3;
        
        // Shadows
        float shadow1 = calcSoftShadow(p + n * 0.01, lightDir1, 0.02, 8.0, 12.0);
        float shadow2 = calcSoftShadow(p + n * 0.01, lightDir2, 0.02, 8.0, 8.0);
        
        // Base material color
        col = getMaterialColor(p, n, rd, matId, t);
        
        // Diffuse lighting
        float diff1 = max(dot(n, lightDir1), 0.0);
        float diff2 = max(dot(n, lightDir2), 0.0);
        float diff3 = max(dot(n, -lightDir3), 0.0) * 0.5;
        
        // Specular (Blinn-Phong)
        vec3 h1 = normalize(lightDir1 - rd);
        vec3 h2 = normalize(lightDir2 - rd);
        float spec1 = pow(max(dot(n, h1), 0.0), 64.0);
        float spec2 = pow(max(dot(n, h2), 0.0), 32.0);
        
        // Subsurface scattering
        float sss = calcSSS(p, n, lightDir1, 0.4);
        vec3 sssColor = color3 * sss * 0.5;
        
        // Rim light
        float rim = pow(1.0 - abs(dot(n, -rd)), 4.0);
        vec3 rimColor = mix(color4, color6, rim) * rim;
        
        // Combine lighting
        vec3 lighting = vec3(0.0);
        lighting += lightCol1 * diff1 * shadow1;
        lighting += lightCol2 * diff2 * shadow2;
        lighting += lightCol3 * diff3;
        lighting += color5 * spec1 * shadow1 * 0.8;
        lighting += color6 * spec2 * shadow2 * 0.4;
        
        col *= lighting;
        col += sssColor;
        col += rimColor * 0.5;
        
        // Ambient
        vec3 ambient = mix(color9, color8, n.y * 0.5 + 0.5) * 0.15;
        col += ambient * ao;
        
        // Apply AO
        col *= 0.4 + 0.6 * ao;
        
        // Distance fog with color
        float fogAmount = 1.0 - exp(-t * 0.04);
        vec3 fogColor = mix(color9, color8, uvOrig.y);
        fogColor = mix(fogColor, color1, sin(u_time * 0.15) * 0.2 + 0.3);
        col = mix(col, fogColor, fogAmount * 0.7);
        
    } else {
        // Background - gradient with subtle patterns
        float bgGrad = uvOrig.y;
        col = mix(color9, color8, bgGrad);
        col = mix(col, color1, bgGrad * bgGrad * 0.4);
        
        // Subtle nebula effect
        float nebula = fbm(vec3(uv * 3.0, u_time * 0.05), 4);
        col = mix(col, color2 * 0.3, nebula * 0.3);
        
        // Stars
        float stars = pow(max(0.0, hash21(floor(uv * 500.0)) - 0.98) * 50.0, 2.0);
        col += color5 * stars * 0.5;
    }
    
    // ─── Post-processing ───
    
    // Bloom
    col = bloom(col, 0.7, 0.6);
    
    // Chromatic aberration
    col = chromaticAberration(uv, col, 0.003);
    
    // Vignette with color tint
    float vignette = 1.0 - pow(length(uv) * 0.7, 2.0);
    vignette = smoothstep(0.0, 1.0, vignette);
    col *= vignette;
    col = mix(col, color9, (1.0 - vignette) * 0.4);
    
    // Color grading
    col = mix(col, col * col * 2.5, 0.25); // Contrast
    col = pow(col, vec3(0.92)); // Saturation boost
    
    // Tone mapping (ACES approximation)
    col = col * (2.51 * col + 0.03) / (col * (2.43 * col + 0.59) + 0.14);
    
    // Gamma correction
    col = pow(col, vec3(0.4545));
    
    // Film grain
    col += grain(uvOrig, u_time, 0.025);
    
    // Final clamp
    col = clamp(col, 0.0, 1.0);
    
    fragColor = vec4(col, 1.0);
}