A real-time raymarched SDF blob engine with PBR shading, softbox lighting, audio reactivity, and a full modifier stack — running entirely in the browser at 60fps.
// Smooth-minimum blending for organic merging
float smin(float a, float b, float k) {
float h = clamp(.5 + .5 * (b - a) / k, 0., 1.);
return mix(b, a, h) - k * h * (1. - h);
}
float map(vec3 p) {
vec3 mp = applyModifiers(p);
float d = 100.;
for(int i = 0; i < 10; i++) {
if(i >= u_numBlobs) break;
float r = (i == 0) ? u_coreR : u_dropR;
float db = length(mp - applyModifiers(u_blobs[i])) - r;
if(i == 0) d = db;
else d = smin(d, db, u_merge);
}
return d;
}
// GGX microfacet specular distribution
float ggx(vec3 n, vec3 h, float r) {
float a2 = r*r*r*r;
float NdH = max(dot(n, h), 0.);
float d = NdH*NdH * (a2 - 1.) + 1.;
return a2 / (3.14159 * d*d + .0001);
}
// Schlick fresnel — metals use albedo as F0
vec3 F0 = mix(vec3(.04), albedo, u_metal);
vec3 schlickF = F0 + (1.-F0) * pow(1.-NdV, 5.);
// Three-light softbox studio
vec3 diff = albedo * (1.-u_metal) * (
NdL1 * u_keyL * .5 + // warm key
NdL2 * u_fillL * .3 + // cool fill
NdL3 * u_rimL * .25 + // rim strip
.15 // ambient floor
);
// Three-band FFT → visual parameter routing matrix
let audioRoutes = [
{ band: 'bass', prop: 'size', amount: 0.5 },
{ band: 'bass', prop: 'merge', amount: 0.3 },
{ band: 'mid', prop: 'drift', amount: 0.5 },
{ band: 'high', prop: 'rough', amount: 0.2 },
{ band: 'bass', prop: 'fresnel', amount: 0.4 },
];
// Accumulate modulations and clamp to safe ranges
function applyAudioToUniforms() {
const mods = {};
audioRoutes.forEach(r => {
const bv = getBandValue(r.band);
const cfg = routeProps[r.prop];
mods[r.prop] = (mods[r.prop] || 0)
+ bv * r.amount * cfg.scale;
});
}
vec3 applyModifiers(vec3 p) {
vec3 q = p;
// Twist — rotate around Y axis by height
if(u_modTwist != 0.) {
float tw = q.y * u_modTwist * 2.;
q = vec3(q.x*cos(tw)-q.z*sin(tw), q.y,
q.x*sin(tw)+q.z*cos(tw));
}
// Squash — flatten Y, expand XZ
if(u_modSquash != 0.) {
q.y *= 1. - u_modSquash * .6;
q.x *= 1. + u_modSquash * .3;
q.z *= 1. + u_modSquash * .3;
}
// Melt — droop by distance from center
if(u_modMelt != 0.) {
float dist = length(q.xz);
q.y -= dist*dist * u_modMelt * .8;
}
return q;
}