Back to shaders

Shader test bench

20251111

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;

// Raymarching constants
#define MAX_STEPS 100
#define MAX_DIST 100.0
#define SURF_DIST 0.001

// Mandelbulb parameters
#define ITERATIONS 15

// Rotation matrix around Y axis
mat3 rotateY(float angle) {
    float c = cos(angle);
    float s = sin(angle);
    return mat3(
        c, 0.0, s,
        0.0, 1.0, 0.0,
        -s, 0.0, c
    );
}

// Rotation matrix around X axis
mat3 rotateX(float angle) {
    float c = cos(angle);
    float s = sin(angle);
    return mat3(
        1.0, 0.0, 0.0,
        0.0, c, -s,
        0.0, s, c
    );
}

// Rotation matrix around Z axis
mat3 rotateZ(float angle) {
    float c = cos(angle);
    float s = sin(angle);
    return mat3(
        c, -s, 0.0,
        s, c, 0.0,
        0.0, 0.0, 1.0
    );
}

// Mandelbulb distance estimation with animated power
float mandelbulbDE(vec3 pos, float power) {
    vec3 z = pos;
    float dr = 1.0;
    float r = 0.0;

    for(int i = 0; i < ITERATIONS; i++) {
        r = length(z);

        if(r > 2.0) break;

        // Convert to spherical coordinates
        float theta = acos(z.z / r);
        float phi = atan(z.y, z.x);
        dr = pow(r, power - 1.0) * power * dr + 1.0;

        // Scale and rotate the point
        float zr = pow(r, power);
        theta = theta * power;
        phi = phi * power;

        // Convert back to cartesian coordinates
        z = zr * vec3(
            sin(theta) * cos(phi),
            sin(theta) * sin(phi),
            cos(theta)
        );
        z += pos;
    }

    return 0.5 * log(r) * r / dr;
}

// Global time variable for animations
float g_time = 0.0;

// Scene distance function with transformations
float getDist(vec3 p) {
    vec3 pos = p;

    // Pulsing scale animation
    float pulse = 1.0 + sin(g_time * 0.4) * 0.1;
    pos /= pulse;

    // Multi-axis rotation of the fractal
    float rotSpeed = g_time * 0.1;
    pos = rotateY(rotSpeed) * pos;
    pos = rotateX(rotSpeed * 0.7) * pos;
    pos = rotateZ(rotSpeed * 0.5) * pos;

    // Kaleidoscopic folding for extra symmetry
    float foldTime = sin(g_time * 0.2) * 0.3;
    pos.xy = abs(pos.xy);
    if(pos.x < pos.y) pos.xy = pos.yx;

    // Twist effect
    float twist = length(pos.xy) * (0.5 + sin(g_time * 0.3) * 0.3);
    pos.xy = rotateZ(twist)[0].xy * pos.x + rotateZ(twist)[1].xy * pos.y;

    // Animated power morphing (cycles between 5 and 11)
    float power = 8.0 + sin(g_time * 0.25) * 3.0;

    float d = mandelbulbDE(pos, power);

    return d * pulse; // Scale distance back
}

// Calculate normal using tetrahedron technique
vec3 getNormal(vec3 p) {
    float d = getDist(p);
    vec2 e = vec2(0.001, 0.0);

    vec3 n = d - vec3(
        getDist(p - e.xyy),
        getDist(p - e.yxy),
        getDist(p - e.yyx)
    );

    return normalize(n);
}

// Raymarching function
float rayMarch(vec3 ro, vec3 rd) {
    float dO = 0.0;

    for(int i = 0; i < MAX_STEPS; i++) {
        vec3 p = ro + rd * dO;
        float dS = getDist(p);
        dO += dS;

        if(dO > MAX_DIST || abs(dS) < SURF_DIST) break;
    }

    return dO;
}

// Ambient occlusion for depth perception
float calculateAO(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) / 4.0;
        float d = getDist(p + h * n);
        occ += (h - d) * sca;
        sca *= 0.95;
    }
    return clamp(1.0 - 1.5 * occ, 0.0, 1.0);
}

// Advanced lighting calculation
float calculateLighting(vec3 p, vec3 n) {
    // Animated light positions
    vec3 light1 = normalize(vec3(
        sin(u_time * 0.5) * 2.0,
        cos(u_time * 0.3) * 2.0 + 1.0,
        cos(u_time * 0.5) * 2.0 + 1.0
    ));

    vec3 light2 = normalize(vec3(-1.0, -0.5, 1.0));

    // Diffuse lighting from multiple sources
    float diff1 = max(dot(n, light1), 0.0);
    float diff2 = max(dot(n, light2), 0.0) * 0.5;

    // Rim lighting for dramatic edges
    vec3 viewDir = normalize(-p);
    float rim = pow(1.0 - abs(dot(n, viewDir)), 2.5);

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

    // Combine all lighting
    float ambient = 0.15;
    float diffuse = diff1 + diff2;

    float brightness = ambient + diffuse * ao + rim * 0.6;

    return clamp(brightness, 0.0, 1.0);
}

void main() {
    // Normalized pixel coordinates (centered)
    vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / u_resolution.y;

    // Set global time for animations
    g_time = u_time;

    // Dynamic camera animation
    float time = u_time * 0.15;

    // Camera position with smooth circular motion and height variation
    float radius = 2.5 + sin(time * 0.3) * 0.5; // Breathing distance
    float angle = time;
    float height = sin(time * 0.4) * 0.5; // Smooth vertical movement

    vec3 ro = vec3(
        radius * cos(angle),
        height,
        radius * sin(angle)
    );

    // Look at center with slight offset
    vec3 target = vec3(0.0, sin(time * 0.5) * 0.2, 0.0);
    vec3 forward = normalize(target - ro);

    // Camera up vector
    vec3 up = vec3(0.0, 1.0, 0.0);
    vec3 right = normalize(cross(forward, up));
    up = cross(right, forward);

    // Ray direction with proper camera matrix
    vec3 rd = normalize(forward + uv.x * right + uv.y * up);

    // Raymarch
    float d = rayMarch(ro, rd);

    vec3 color = vec3(0.0); // Start with black

    if(d < MAX_DIST) {
        // Hit the fractal
        vec3 p = ro + rd * d;
        vec3 n = getNormal(p);

        float brightness = calculateLighting(p, n);

        // Add depth-based darkening for atmosphere
        float depth = d / MAX_DIST;
        brightness *= (1.0 - depth * 0.3);

        // Enhanced contrast for dramatic black and white
        brightness = pow(brightness, 0.8);

        color = vec3(brightness);
    } else {
        // Add subtle glow in the background
        float glow = 0.02 / (d * 0.1 + 0.1);
        color = vec3(glow);
    }

    // Vignette effect for focus
    float vignette = 1.0 - length(uv) * 0.3;
    color *= vignette;

    // Final contrast boost
    color = smoothstep(0.0, 1.0, color);

    fragColor = vec4(color, 1.0);
}