Back to shaders

Shader test bench

20250418

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 u_resolution;
#define fragColor gl_FragColor

#define MAX_STEPS   120
#define MAX_DIST    100.0
#define SURF_DIST   0.001

float opSmoothUnion(float d1, float d2, float k) {
    float h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0);
    return mix(d2, d1, h) - k * h * (1.0 - h);
}

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

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

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

float sdOctahedron(vec3 p, float s) {
    p = abs(p);
    return (p.x + p.y + p.z - s) * 0.57735026;
}

float mapScene(vec3 p) {
    float warpAmp = 0.1 + 0.05 * sin(u_time * 0.7);
    float pulse = 0.2 * sin(u_time * 1.2) * (0.5 + 0.5 * sin(u_time * 0.4));

    vec3 wp = p + warpAmp * vec3(
        sin(p.y * 2.0 + u_time * 1.2) * 0.8,
        sin(p.z * 2.5 + u_time * 1.5) * 0.6,
        sin(p.x * 3.0 + u_time * 1.1) * 0.7
    );

    float angle = u_time * 0.7 + sin(u_time * 0.3) * 0.3;
    mat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
    vec3 rp = wp;
    rp.yz = rot * wp.yz;
    float d1 = sdTorus(rp, vec2(1.0 + pulse, 0.3 + 0.05 * cos(u_time * 2.5)));

    float d2 = sdBox(wp - vec3(0.0, 2.0, 0.0), vec3(0.5));

    float d3 = sdSphere(
        wp - vec3(0.0, -2.0 + 0.2 * sin(u_time * 0.8), 0.0),
        0.7 + 0.1 * sin(u_time * 1.7)
    );

    float u12 = opSmoothUnion(d1, d2, 0.4);
    float u123 = opSmoothUnion(u12, d3, 0.3);

    float angle2 = u_time * 0.7;
    mat2 rot2 = mat2(cos(angle2), -sin(angle2), sin(angle2), cos(angle2));
    vec3 rp2 = wp;
    rp2.xz = rot2 * wp.xz;
    float d4 = sdTorus(rp2, vec2(1.2 + 0.2 * sin(u_time * 1.5), 0.2 + 0.05 * cos(u_time * 1.8)));

    float d5 = 1e5;
    for (int i = 0; i < 6; i++) {
        float a = u_time * 0.8 + float(i) / 6.0 * 6.2831853;
        vec3 sp = vec3(0.0, cos(a) * 2.5, sin(a) * 2.5);
        d5 = min(d5, sdSphere(wp - sp, 0.08));
    }

    float baseBlend = opSmoothUnion(u123, d4, 0.25);

    float angle3 = u_time * 1.3;
    mat2 rot3 = mat2(cos(angle3), -sin(angle3), sin(angle3), cos(angle3));
    vec3 bp = wp;
    bp.xy = rot3 * wp.xy;
    float cubeSize = 0.3 + 0.1 * sin(u_time * 2.2);
    float d6 = sdBox(bp, vec3(cubeSize));
    float withCube = opSmoothUnion(baseBlend, d6, 0.2);

    float angle4 = u_time * 0.6;
    mat2 rot4 = mat2(cos(angle4), -sin(angle4), sin(angle4), cos(angle4));
    vec3 rp7 = wp;
    rp7.yz = rot4 * wp.yz;
    float d7 = sdOctahedron(rp7, 0.8 + 0.2 * sin(u_time * 1.3));

    float blendAll = opSmoothUnion(withCube, d5, 0.15);
    return opSmoothUnion(blendAll, d7, 0.2);
}

vec3 getNormal(vec3 p) {
    vec2 e = vec2(0.001, 0.0);
    return normalize(vec3(
        mapScene(p + e.xyy) - mapScene(p - e.xyy),
        mapScene(p + e.yxy) - mapScene(p - e.yxy),
        mapScene(p + e.yyx) - mapScene(p - e.yyx)
    ));
}

float softShadow(vec3 ro, vec3 rd) {
    float res = 1.0;
    float t = 0.02;
    for (int i = 0; i < 30; i++) {
        float h = mapScene(ro + rd * t);
        if (h < 0.001) return 0.0;
        res = min(res, 10.0 * h / t);
        t += clamp(h, 0.02, 0.1);
        if (t > MAX_DIST) break;
    }
    return res;
}

float rayMarch(vec3 ro, vec3 rd, out vec3 pos) {
    float dO = 0.0;
    for (int i = 0; i < MAX_STEPS; i++) {
        vec3 p = ro + dO * rd;
        float ds = mapScene(p);
        if (ds < SURF_DIST || dO > MAX_DIST) break;
        dO += ds;
    }
    pos = ro + dO * rd;
    return dO;
}

void main() {
    vec2 res = vec2(1080.0, 1920.0);
    vec2 uv = (gl_FragCoord.xy - 0.5 * res) / res.y;

    float roll = 0.02 * sin(u_time * 0.3);
    uv = mat2(cos(roll), -sin(roll), sin(roll), cos(roll)) * uv;

    vec3 ro = vec3(0.0, 0.3, 18.0);
    vec3 ta = vec3(-0.8, 1.5, 0.0);

    vec3 cw = normalize(ta - ro);
    vec3 cu = normalize(cross(vec3(0.0, 1.0, 0.0), cw));
    vec3 cv = cross(cw, cu);

    vec3 rd = normalize(uv.x * cu + uv.y * cv + 2.0 * cw);

    vec3 p;
    float dist = rayMarch(ro, rd, p);

    float focusDist = 15.0;
    float blurAmount = clamp(abs(dist - focusDist) * 0.1, 0.0, 1.0);

    vec3 col = vec3(0.0);

    if (dist < MAX_DIST) {
        vec3 n = getNormal(p);
        vec3 viewDir = normalize(ro - p);
        vec3 lightPos = vec3(6.0 * cos(u_time * 1.2), 4.5 + sin(u_time * 0.4) * 1.5, 6.0 * sin(u_time * 1.1));
        vec3 L = normalize(lightPos - p);

        float diff = max(dot(n, L), 0.0);
        float shadow = softShadow(p + n * SURF_DIST * 2.0, L);
        float rim = pow(1.0 - max(dot(n, viewDir), 0.0), 3.0);
        float amb = 0.2;

        float lum = amb + diff * shadow + rim * 0.3;
        float fogFactor = clamp(1.0 - exp(-dist * 0.01), 0.0, 1.0);
        lum = mix(lum, 1.0, fogFactor);

        lum += (fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453 + u_time) - 0.5) * 0.02;

        vec3 halfway = normalize(L + viewDir);
        float spec = pow(max(dot(n, halfway), 0.0), 32.0);
        lum += spec * 0.5;

        float glow = pow(clamp(1.0 - dist * 0.05, 0.0, 1.0), 2.0);
        lum += glow * 0.1;

        float vig = smoothstep(1.0, 0.4, length(uv));
        lum *= vig;

        col = vec3(lum);
    }

    // ✨ Sparkles
    float sparkle = step(0.995, fract(sin(dot(p.xy + u_time, vec2(12.9898, 78.233))) * 43758.5453));
    col += vec3(sparkle) * 1.2;

    fragColor = vec4(mix(col, col, blurAmount), 1.0);
}