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);
}