Project_2026-02-13_16-02-06

GLSL shader by scry · created 2026-02-14 · 10s loop · 2 passes

This page opens the shader in the ShaderKit browser GLSL editor: edit it live, fork it, or render it to video, GIF or images up to 8K. Also available as a full-screen view and an embeddable player.

Shader source (GLSL)

Common

#define pi acos(-1.)
#define deg pi/180.  //1 degree
#define time iTime*2.*pi/10. //sin(time) loops 10 seconds
#define R iResolution.xy //shorthand
#define ar R.x/R.y //aspect ratio
#define M iMouse //shorthand
#define xm (M.xy/R) //normalized mouse
#define nm ((xm.xy-0.5)*vec2(ar,1.)+0.5) //aspect ratio correction
vec3 cs = vec3(1.,2.,3.);
mat2 r2d(float a) {
    return mat2(cos(a),sin(a),-sin(a),cos(a));
}

vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
    return a + b * cos(6.28318 * (c * t + d));
}

vec3 paletteClassic(float t) {
    return palette(t, vec3(0.5), vec3(0.5), vec3(1.0), vec3(0.0, 0.33, 0.67));
}

Buffer A (iChannel0)

// Mandelbox distance estimator
// Parameters
const float SCALE = -1.5;
const float FIXED_RADIUS2 = 1.0;
const float MIN_RADIUS2 = 0.25;
const int MB_ITERS = 12;

void sphereFold(inout vec3 z, inout float dz) {
    float r2 = dot(z, z);
    if (r2 < MIN_RADIUS2) {
        float temp = FIXED_RADIUS2 / MIN_RADIUS2;
        z  *= temp;
        dz *= temp;
    } else if (r2 < FIXED_RADIUS2) {
        // Clamp r2 away from zero to prevent division explosion
        float temp = FIXED_RADIUS2 / max(r2, 1e-6);
        z  *= temp;
        dz *= temp;
    }
}

void boxFold(inout vec3 z) {
    z = clamp(z, -1.0, 1.0) * 2.0 - z;
}

vec2 mandelbox(vec3 p) {
    float s = 0.2;
    p = fract(p*s)-0.5;
    p /= s;
    vec3 z = p;
    float dz = 1.0;
    float trap = 1e10;

    for (int i = 0; i < MB_ITERS; i++) {
        boxFold(z);
        sphereFold(z, dz);
        z = SCALE * z + p;
        dz = dz * abs(SCALE) + 1.0;
        trap = min(trap, dot(z, z));
    }

    float r = length(z);
    float d = r / abs(dz);
    // Clamp DE to a small positive floor to prevent zero/negative steps
    return vec2(max(d, 1e-7), trap);
}

vec4 map(vec3 p) {
    //p = fract(p)-0.5;
    vec2 mb = mandelbox(p);
    return vec4(p, mb.x);
}

vec2 RM(vec3 ro, vec3 rd) {
    float dO = 0.0;
    float trap = 0.0;
    for (int i = 0; i < 120; i++) {
        vec3 pos = ro + rd * dO;
        vec2 mb = mandelbox(pos);
        float dS = mb.x;
        trap = mb.y;
        if (dO > 30.0) break;
        // Scale hit threshold with accumulated distance
        float hitThresh = max(0.0002 * dO, 5e-6);
        if (dS < hitThresh) { break; }
        // Clamp step to avoid overstepping; use conservative factor
        dO += dS * 0.7;
    }
    return vec2(dO, trap);
}

vec3 getNormal(vec3 p) {
    // Scale epsilon with distance to keep normals stable at all distances
    // Use the DE value itself to pick a good epsilon
    float d = mandelbox(p).x;
    float eps = max(5e-5, clamp(d * 0.5, 1e-5, 0.002));
    vec2 e = vec2(eps, 0.0);
    vec3 n = vec3(
        mandelbox(p + e.xyy).x - mandelbox(p - e.xyy).x,
        mandelbox(p + e.yxy).x - mandelbox(p - e.yxy).x, 
        mandelbox(p + e.yyx).x - mandelbox(p - e.yyx).x
    );
    return normalize(n + 1e-10); // prevent zero-length normal
}

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 = mandelbox(p + h * n).x;
        occ += (h - d) * sca;
        sca *= 0.7;
    }
    return clamp(1.0 - 3.0 * occ, 0.0, 1.0);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord.xy / iResolution.xy;
    vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
    p /= 4.-log(length(uv-0.5)+2.)*4.;
    // Smooth continuous orbit around a single Mandelbox cell center
    // The fract(p*0.2)-0.5 repetition means cell center is at origin,
    // cell extends roughly -2.5 to 2.5. We orbit just inside that.
    
    float t = iTime * 0.2;
    
    // Lissajous-style orbit: three incommensurate frequencies
    // so the path never exactly repeats and stays smooth forever
    float orbitR = 0.8;  // radius - close to surface but not inside
    float bobR   = 0.6;  // vertical bob amplitude
    
    vec3 ro = vec3(
        orbitR * cos(t * 0.7) + 0.4 * sin(t * 0.31),
        bobR * sin(t * 0.53) + 0.3 * cos(t * 0.79),
        orbitR * sin(t * 0.7) + 0.4 * cos(t * 0.43)
    );
    
    // Look-at: slightly ahead on the same path (derivative-ish)
    float dt = 0.15;
    float t2 = t + dt;
    vec3 roAhead = vec3(
        orbitR * cos(t2 * 0.7) + 0.3 * sin(t2 * 0.31),
        bobR * sin(t2 * 0.53) + 0.2 * cos(t2 * 0.79),
        orbitR * sin(t2 * 0.7) + 0.3 * cos(t2 * 0.43)
    );
    
    vec3 ta = roAhead;

    // Mouse control
    if (M.z > 0.0) {
        float mx = M.x / R.x * 2.0 * pi;
        float my = (M.y / R.y - 0.5) * pi;
        float camDist = 2.0;
        vec3 ctr = ro;
        ro = ctr + vec3(camDist * cos(mx) * cos(my), camDist * sin(my), camDist * sin(mx) * cos(my));
        ta = ctr;
    }

    vec3 ww = normalize(ta - ro);
    vec3 upVec = vec3(0.0, 1.0, 0.0);
    if (abs(dot(ww, upVec)) > 0.999) upVec = vec3(1.0, 0.0, 0.0);
    vec3 uu = normalize(cross(ww, upVec));
    vec3 vv = normalize(cross(uu, ww));
    
    // Gentle banking roll from lateral acceleration
    float rollAngle = 0.15 * sin(t * 0.7) * cos(t * 0.53);
    vec2 pr = p * r2d(rollAngle);
    vec3 rd = normalize(pr.x * uu + pr.y * vv + 1.5 * ww);

    // Raymarch
    vec2 res = RM(ro, rd);
    float dist = res.x;
    float trap = res.y;

    vec3 col = vec3(0.02, 0.02, 0.04);

    if (dist < 30.0) {
        vec3 hp = ro + rd * dist;
        // Push hit point slightly along ray to ensure we're on the surface, not inside
        float surfDE = mandelbox(hp).x;
        hp += rd * surfDE * 0.5;
        vec3 n = getNormal(hp);
        
        float offsetDist = max(0.005, 0.002 * dist);
        float ao = calcAO(hp + n * offsetDist, n);

        // Two-light setup for more natural shading
        vec3 l1 = normalize(vec3(0.8, 0.6, -0.5));
        vec3 l2 = normalize(vec3(-0.4, 0.3, 0.7));
        float diff1 = max(dot(n, l1), 0.0);
        float diff2 = max(dot(n, l2), 0.0);
        float diff = diff1 * 0.8 + diff2 * 0.3;

        // Fresnel-attenuated specular to reduce artifacts at grazing angles
        float fresnel = 1.0 - abs(dot(n, -rd));
        fresnel = fresnel * fresnel * fresnel;
        vec3 h = normalize(l1 + (-rd));
        float spec = pow(max(dot(n, h), 0.0), 32.0);

        // Color from orbit trap
        float trapT = sqrt(trap) * 0.4;
        vec3 matCol = palette(trapT,
            vec3(0.4, 0.35, 0.3),
            vec3(0.3, 0.25, 0.25),
            vec3(1.0, 1.0, 1.0),
            vec3(0.0, 0.1, 0.2));

        col = matCol * (0.2 + 0.8 * diff) * ao;
        col += vec3(0.6) * spec * 0.15 * ao * (1.0 - fresnel * 0.8);

        float fog = exp(-0.03 * dist * dist);
        col = mix(vec3(0.02, 0.02, 0.04), col, fog);
    }

    // Tonemap and gamma
    col = col / (1.0 + col);
    col = pow(col, vec3(0.4545));

    fragColor = vec4(col, 1.0);
}

Image

Not used

More shaders by scry

Browse all public shaders · All shaders by scry · ShaderKit home

Vibe Mode BETA
💰 ~0 credits
Uniforms
FPS: 0
Time: 0
Resolution: 0 x 0

Account

FPS: -- Time: -- --×-- 1x