0.00
60.0 fps

Fractal Cube Gone Wild

Pretty

Log in to post a comment.

#version 300 es
precision highp float;

uniform float iTime;
uniform vec2  iResolution;
uniform int uLevels;

out vec4 fragColor;

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

// -------------------------------------------------------------
// SDFs
// -------------------------------------------------------------
float sdCrispBox(vec3 p, vec3 b){
    vec3 q = abs(p) - b;
    return max(max(q.x,q.y), q.z);
}

float sdRoundBox(vec3 p, vec3 b, float r){
    vec3 q = abs(p) - b;
    return length(max(q, 0.0)) - r;
}

// -------------------------------------------------------------
// Smooth min (melting)
// -------------------------------------------------------------
float smin(float a,float b,float k){
    float h = clamp(0.5 + 0.5*(b-a)/k, 0., 1.);
    return mix(b,a,h) - k*h*(1.-h);
}

float contactMelt(float da,float db,float baseR,float meltAmp){
    float overlap = da + db;
    float r = baseR * meltAmp;
    float blend = smoothstep(r,0.0,overlap);
    float k = r * blend;
    return smin(da, db, k);
}

vec2 opCM(vec2 a, vec2 b, float baseR, float meltAmp){
    float d = contactMelt(a.x, b.x, baseR, meltAmp);
    return (d == a.x) ? vec2(d,a.y) : vec2(d,b.y);
}

// -------------------------------------------------------------
// Helpers
// -------------------------------------------------------------
float hash(vec3 p){
    p = fract(p*0.3183099 + vec3(0.1,0.2,0.3));
    p += dot(p,p.yzx+19.19);
    return fract(p.x*p.y*p.z);
}

float meltPhase(){
    float w = 0.5 + 0.5 * sin(iTime * 1.1);
    w = pow(w, 3.0);
    return clamp(w, 0.0, 1.0);
}

// kaleidoscopic-ish fold
vec3 kaleido(vec3 p){
    p.xy = abs(p.xy);
    p.xz = abs(p.xz);
    float r = length(p.xy);
    float k = 0.4 + 0.6 * sin(iTime*0.7 + r*2.5);
    p.xy *= mix(1.0, 0.5 + 0.5*sin(r*3.0), 0.3 * k);
    return p;
}

// -------------------------------------------------------------
// Scene with variable-sized, sparse "macro appendages"
// -------------------------------------------------------------
vec2 scene(vec3 p){
    float t  = iTime;
    float mp = meltPhase();

    // global kaleido / swirl
    p = kaleido(p);
    float swirl = 0.4 * sin(t*0.3);
    mat3 G = rotY(swirl) * rotZ(swirl*0.7);
    p = G * p;

    vec2 res = vec2(1e9, 0.0);

    const int MAX_L = 10;
    float size  = 0.6;
    float scale = 0.45;

    // global melt intensity
    float meltAmpGlobal = 0.8 + 6.0 * pow(mp, 2.5);

    for(int level=0; level<MAX_L; level++)
    {
        float lf = float(level);

        // breathing per-level size
        float grow = 0.2 + 0.8 * abs(sin(iTime*0.7 + lf*1.37));

        // spiral / limpet twist per level
        float ay = sin(lf*0.9 + t*0.5) * (0.35 + 0.25*mp);
        float ax = sin(lf*1.2 + t*0.3) * (0.25 + 0.25*mp);
        float az = sin(lf*0.7 + t*0.7) * (0.20 + 0.20*mp);

        mat3 twist = rotY(ay)*rotX(ax)*rotZ(az);
        vec3 lp = twist * p;

        // parent cube: crisp core with slight rounding in melt phase
        float parentCrisp = sdCrispBox(lp, vec3(size*grow));
        float parentRound = sdRoundBox(lp, vec3(size*grow), size*grow*0.2*mp);
        float parent = mix(parentCrisp, parentRound, mp);

        float baseR = mix(0.05, 0.28, lf/float(MAX_L-1));
        res = opCM(res, vec2(parent, lf), baseR, meltAmpGlobal);

        // --- child appendages (some huge, some tiny, all offset) ----
        float baseChild = size * scale * grow;

        // canonical face normals
        vec3 faceN[6] = vec3[6](
            vec3( 1.,0.,0.), vec3(-1.,0.,0.),
            vec3( 0.,1.,0.), vec3( 0.,-1.,0.),
            vec3( 0.,0.,1.), vec3( 0.,0.,-1.)
        );

        // we need the final offsets for folding
        vec3 centers[6];

        for(int k=0; k<6; k++){
            vec3 n   = faceN[k];

            // hash-driven variation per level & direction
            float h  = hash(vec3(lf, float(k), 7.123));
            float macroFlag  = step(0.78, h);             // some directions become big appendages
            float macroScale = mix(1.0, 2.8, macroFlag);  // how much bigger

            // subtle continuous variation too
            float sizeJitter = 0.65 + 0.6 * h;
            float distJitter = 0.9  + 0.4 * sin(h*6.2831);

            float cSize  = baseChild * sizeJitter * macroScale;
            float cShift = (size*grow + cSize) * distJitter * mix(1.0, 2.0, macroFlag);

            vec3 offset = n * cShift;
            centers[k] = offset;

            // position + extra wobble in melt mode
            vec3 q = lp - offset;
            q += 0.18 * mp * sin(q*4.0 + (lf+float(k))*1.9 + t*3.2);

            // rounder child boxes for more organic blobs
            float cb = sdRoundBox(q, vec3(cSize), cSize * (0.15 + 0.25*mp));

            // more smoothing for macros so they look like big blobby arms
            float childR = baseR * (1.0 + 1.8*macroFlag);
            res = opCM(res, vec2(cb, lf), childR, meltAmpGlobal);
        }

        // fold to nearest child center for next recursion: this is what
        // makes the structure stay fractal while having irregular appendages
        float best = 1e9;
        vec3  bestP = lp;
        for(int k=0;k<6;k++){
            vec3 pp = lp - centers[k];
            float m = dot(pp,pp);
            if(m < best){ best = m; bestP = pp; }
        }

        p = bestP;
        size *= scale;
    }

    return res;
}

// -------------------------------------------------------------
// Raymarch + shading
// -------------------------------------------------------------
vec2 raymarch(vec3 ro, vec3 rd){
    float t = 0.0;
    for(int i=0;i<220;i++){
        vec3 pos = ro + rd*t;
        vec2 d = scene(pos);
        if(d.x < 0.001) return vec2(t,d.y);
        t += d.x;
        if(t > 60.0) break;
    }
    return vec2(-1.0);
}

vec3 getNormal(vec3 p){
    float h=0.001;
    const vec3 e=vec3(1,-1,0);
    return normalize(
        e.xyy*scene(p+e.xyy*h).x +
        e.yyx*scene(p+e.yyx*h).x +
        e.yxy*scene(p+e.yxy*h).x +
        e.xxx*scene(p+e.xxx*h).x
    );
}

// rainbow-ish palette
vec3 palette(float t){
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 0.7, 0.4);
    vec3 d = vec3(0.0, 0.33, 0.67);
    return a + b * cos(6.28318 * (c * t + d));
}

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

    // camera orbit
    float camT = iTime * 0.22;
    float camR = 4.0;
    vec3 ro = vec3(
        camR * sin(camT),
        1.2 + 0.6*sin(camT*1.7),
        camR * cos(camT)
    );
    vec3 ta = vec3(0.0, 0.0, 0.0);

    vec3 ww = normalize(ta-ro);
    vec3 uu = normalize(cross(vec3(0,1,0), ww));
    vec3 vv = cross(ww, uu);
    vec3 rd = normalize(uv.x*uu + uv.y*vv + ww);

    vec2 hit = raymarch(ro, rd);
    if(hit.x < 0.0){
        float g = 0.25 + 0.4*pow(max(rd.y,0.0),2.0);
        vec3 bg = mix(vec3(0.02,0.03,0.05), vec3(0.15,0.18,0.22), g);
        fragColor = vec4(bg,1.0);
        return;
    }

    vec3 pos = ro + rd*hit.x;
    vec3 nor = getNormal(pos);

    vec3 lightDir = normalize(vec3(0.7,1.0,0.4));
    float diff = max(dot(nor, lightDir),0.0);
    float mp = meltPhase();

    float lvl   = hit.y;
    float nDot  = dot(nor, normalize(vec3(0.3,0.6,0.7)));
    float cHash = hash(pos*1.7);

    float colorT = lvl*0.2 + iTime*0.12 + nDot*0.4 + cHash*0.3;
    vec3 baseCol = palette(colorT);

    float stripes = 0.5 + 0.5*sin(dot(pos, vec3(0.8,1.3,0.9))*1.3 + iTime*1.5);
    baseCol *= mix(0.8, 1.4, stripes);

    vec3 meltGlow = mix(vec3(0.0), vec3(1.0,0.7,0.9), pow(mp, 2.0));
    baseCol += meltGlow * 0.7;

    vec3 col = baseCol * (0.2 + 0.8*diff);

    float fog = exp(-0.08 * hit.x);
    vec3 bg = vec3(0.02,0.03,0.05);
    col = mix(bg, col, fog);

    float v = 1.0 - dot(uv,uv);
    col *= clamp(v*1.3, 0.0, 1.0);

    fragColor = vec4(col,1.0);
}