/*{ "DESCRIPTION": "PARKING LOT - a European 80s hatchback, alone in a marked bay, dancing. The car is one signed distance field whose PROPORTIONS are the parameter: uModel slides continuously through four sets of numbers - three-door supermini, five-door hatch, wedge coupe, long-roof estate - and every dimension in the body, the greenhouse, the rake of the screen and the hatch, the track and the wheelbase is a lerp between them, so the slider is a morph rather than a switch and the shapes between the presets are all valid cars. The dance is a transform on the RAY, not on the geometry: the sample point is pushed backward through a bounce, a roll, a yaw and a non-uniform scale before the car field is evaluated, and the returned distance is multiplied by the smallest scale factor so the field stays conservative and the march stays legal. The scale is volume-preserving - squash the height by k and the two horizontal axes go as one over root k - which is the whole of what makes squash-and-stretch read as a body with mass in it rather than as an object being resized. Every edge on the shell is a hard intersection of planes - no rounded boxes anywhere - because a smoothed corner is a modern renderer's tell and the whole point of the era being quoted is flat-shaded facets meeting at a line. The paint takes a colour straight off a picker and gets a two-lobe fake environment - sky above the horizon, tarmac below it, reflected by the normal and mixed by Fresnel - which is most of what makes a curved panel look like a curved panel, and it costs one dot product. The contact shadow is an analytic ellipse that squashes and slides with the car rather than a shadow ray, because the only occluder in the scene is the thing casting it. The PS2 look is the render and not a filter: the fragment coordinate is quantised before the ray is built, so the frame is genuinely traced at a couple of hundred lines, and the colour is posterised through an ordered 4x4 Bayer dither. Twenty-two controls: model, paint colour, gloss, tyre wall, orbit, elevation, distance, lens, dance amount, tempo, bounce, squash, sway, sun angle, brightness, sky, bay markings, tarmac wear, render resolution, colour depth, dither, and grain.", "CREDIT": "CC0. Original.", "CATEGORIES": ["generator", "raymarch", "retro", "ps2", "car", "vehicle", "procedural", "3d"], "INPUTS": [ { "NAME": "uModel", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 3.0 }, { "NAME": "uPaint", "TYPE": "color", "DEFAULT": [0.78, 0.14, 0.12, 1.0] }, { "NAME": "uGloss", "TYPE": "float", "DEFAULT": 0.75, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uTyre", "TYPE": "float", "DEFAULT": 0.35, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uOrbit", "TYPE": "float", "DEFAULT": 0.85, "MIN": -3.14,"MAX": 3.14 }, { "NAME": "uElev", "TYPE": "float", "DEFAULT": 1.25, "MIN": 0.25, "MAX": 6.0 }, { "NAME": "uDist", "TYPE": "float", "DEFAULT": 7.20, "MIN": 3.0, "MAX": 18.0 }, { "NAME": "uLens", "TYPE": "float", "DEFAULT": 1.60, "MIN": 0.7, "MAX": 3.5 }, { "NAME": "uDance", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uTempo", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 4.0 }, { "NAME": "uBounce", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uSquash", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uSway", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uSun", "TYPE": "float", "DEFAULT": 0.90, "MIN": -3.14,"MAX": 3.14 }, { "NAME": "uBright", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.5 }, { "NAME": "uSky", "TYPE": "float", "DEFAULT": 0.0, "MIN": 0.0, "MAX": 3.0 }, { "NAME": "uLines", "TYPE": "float", "DEFAULT": 0.85, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uWear", "TYPE": "float", "DEFAULT": 0.50, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uRes", "TYPE": "float", "DEFAULT": 320.0, "MIN": 96.0, "MAX": 1080.0 }, { "NAME": "uBits", "TYPE": "float", "DEFAULT": 5.0, "MIN": 2.0, "MAX": 8.0 }, { "NAME": "uDither", "TYPE": "float", "DEFAULT": 0.80, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uGrain", "TYPE": "float", "DEFAULT": 0.10, "MIN": 0.0, "MAX": 1.0 } ] }*/ // PARKING LOT - ISF / GLSL ES 1.00 (gl_FragColor, TIME, RENDERSIZE). // uModel: 0 = three-door supermini, 1 = five-door hatch, 2 = wedge coupe, // 3 = long-roof estate. It morphs, so anything between is a car too. #define FAR 60.0 float gT; vec3 pSky, pHaze, pTar, pLine; vec3 gSun, gSunC; // the dance, resolved once per frame float dBounce, dRoll, dYaw; vec3 dScale; // the body, resolved once per frame from uModel float bLen, bHgt, bHlf, bWid, gLen, gHgt, gZ, gY, rkF, rkR, wbase, wtrack, wrad, wwid; float hash11(float p){ p = fract(p*0.1031); p *= p + 33.33; p *= p + p; return fract(p); } float hash12(vec2 p){ vec3 q = fract(vec3(p.xyx)*0.1031); q += dot(q, q.yzx + 33.33); return fract((q.x + q.y)*q.z); } float vnoise(vec2 p){ vec2 i = floor(p), f = fract(p); f = f*f*(3.0 - 2.0*f); return mix(mix(hash12(i), hash12(i + vec2(1.0, 0.0)), f.x), mix(hash12(i + vec2(0.0, 1.0)), hash12(i + vec2(1.0, 1.0)), f.x), f.y); } float p4(float a, float b, float c, float d, float f){ float i = clamp(f, 0.0, 3.0); float n = floor(i), k = i - n; float lo = a; lo = mix(lo, b, step(0.5, n)); lo = mix(lo, c, step(1.5, n)); lo = mix(lo, d, step(2.5, n)); float hi = b; hi = mix(hi, c, step(0.5, n)); hi = mix(hi, d, step(1.5, n)); return mix(lo, hi, k); } vec3 v4(vec3 a, vec3 b, vec3 c, vec3 d, float f){ return vec3(p4(a.x, b.x, c.x, d.x, f), p4(a.y, b.y, c.y, d.y, f), p4(a.z, b.z, c.z, d.z, f)); } float sdBox(vec3 p, vec3 b){ vec3 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); } // a wheel: a hard-edged cylinder with its axis on x float sdWheel(vec3 p, float R, float w){ vec2 d = vec2(length(p.yz) - R, abs(p.x) - w); return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); } // --------------------------------------------------------------- // the car // --------------------------------------------------------------- // .x = distance, .y = material (2 shell, 4 wheel) vec2 carSDF(vec3 p){ // the shell: a lower body, a greenhouse, and two planes for the rake of // the screen and the tailgate. Four numbers and it is a hatchback. float body = sdBox(p - vec3(0.0, bHgt, 0.0), vec3(bWid, bHlf, bLen)); // the sill tuck, which is what stops it reading as a shoebox vec3 g = p - vec3(0.0, gY, gZ); float house = sdBox(g, vec3(bWid - 0.075, gHgt, gLen)); house = max(house, dot(p - vec3(0.0, gY - gHgt*0.75, gZ + gLen), normalize(vec3(0.0, rkF, 1.0)))); house = max(house, dot(p - vec3(0.0, gY - gHgt*0.75, gZ - gLen), normalize(vec3(0.0, rkR, -1.0)))); // The facets. Every one of these is a half-space intersected with the // shell, so the corners stay HARD - a bevel here would be a modern // renderer's tell - but the plan view goes octagonal, the flanks get a // shoulder and a skirt, the bonnet drops and the greenhouse leans in. // Eight dot products and it stops being a box with a box on it. vec3 s3 = vec3(abs(p.x), p.y, p.z); // the bonnet falls toward the nose, and the tail is cut back underneath // A bonnet drops about nine centimetres over a metre and a half. A quarter // of a radian of it turns a hatchback into a doorstop, which is what the // first pass did, so the slope is written as the real one: rise over run. body = max(body, dot(p - vec3(0.0, bHgt + bHlf - 0.015, bLen*0.30), normalize(vec3(0.0, 1.0, 0.070)))); body = max(body, dot(p - vec3(0.0, bHgt - bHlf + 0.06, -bLen), normalize(vec3(0.0, -1.0, -0.85)))); // the plan view is an octagon, not a rectangle body = max(body, dot(s3 - vec3(bWid*0.80, 0.0, bLen), normalize(vec3(1.0, 0.0, 1.0)))); body = max(body, dot(vec3(s3.x, p.y, -p.z) - vec3(bWid*0.82, 0.0, bLen), normalize(vec3(1.0, 0.0, 1.0)))); // A flank on a car of this era is very nearly VERTICAL - a couple of // degrees of tumblehome at the shoulder and a hint of tuck at the sill. // Wedging it in like a doorstop is what left the wheels standing proud of // the body, which is the single thing that made it read as a toy. body = max(body, dot(s3 - vec3(bWid, bHgt + bHlf, 0.0), normalize(vec3(1.0, 0.34, 0.0)))); body = max(body, dot(s3 - vec3(bWid - 0.012, bHgt - bHlf, 0.0), normalize(vec3(1.0, -0.30, 0.0)))); // tumblehome on the glasshouse, which is far stronger than on the body house = max(house, dot(s3 - vec3(bWid - 0.075, gY - gHgt, 0.0), normalize(vec3(1.0, 0.30, 0.0)))); float shell = min(body, house); // door mirrors shell = min(shell, sdBox(vec3(s3.x - (bWid + 0.04), p.y - (gY - gHgt*0.90), p.z - (gZ + gLen*0.80)), vec3(0.055, 0.038, 0.085))); // the arches, cut with the same cylinders the wheels stand in vec3 aw = vec3(abs(p.x), p.y, abs(p.z)); float arch = sdWheel(vec3(aw.x - wtrack - 0.34, p.y - wrad*0.98, aw.z - wbase), wrad + 0.075, 0.58); shell = max(shell, -arch); vec2 r = vec2(shell, 2.0); float wh = sdWheel(vec3(aw.x - wtrack, p.y - wrad, aw.z - wbase), wrad, wwid); if (wh < r.x) r = vec2(wh, 4.0); return r; } // The dance is a transform on the RAY. The sample is pushed backward through // the bounce, the roll, the yaw and the scale, then the returned distance is // multiplied by the SMALLEST scale factor - which is what keeps a // non-uniformly scaled field conservative and the sphere trace legal. vec2 carWorld(vec3 p){ vec3 q = p - vec3(0.0, dBounce, 0.0); float cy = cos(-dYaw), sy = sin(-dYaw); q = vec3(cy*q.x + sy*q.z, q.y, -sy*q.x + cy*q.z); float cz = cos(-dRoll), sz = sin(-dRoll); q = vec3(cz*q.x - sz*q.y, sz*q.x + cz*q.y, q.z); q /= dScale; vec2 r = carSDF(q); r.x *= min(dScale.x, min(dScale.y, dScale.z)); return r; } vec2 map(vec3 p){ vec2 r = vec2(p.y, 1.0); vec2 c = carWorld(p); if (c.x < r.x) r = c; return r; } vec3 calcN(vec3 p){ vec2 e = vec2(1.0, -1.0)*0.0015; return normalize(e.xyy*map(p + e.xyy).x + e.yyx*map(p + e.yyx).x + e.yxy*map(p + e.yxy).x + e.xxx*map(p + e.xxx).x); } // The contact shadow, as an ellipse rather than a ray. There is exactly one // occluder in this scene and its footprint is known in closed form, so a // shadow march would be sixteen map() calls to reproduce something two lines // of algebra already know - including the way it spreads and lightens as the // car leaves the ground. float contact(vec2 xz){ float lift = max(dBounce, 0.0); float sp = 1.0 + lift*1.5; vec2 q = xz; float cy = cos(-dYaw), sy = sin(-dYaw); q = vec2(cy*q.x + sy*q.y, -sy*q.x + cy*q.y); q /= vec2(bWid*1.25*dScale.x*sp, bLen*1.05*dScale.z*sp); float d = length(q); return 1.0 - (0.72/(1.0 + lift*2.2))*smoothstep(1.15, 0.25, d); } // two lobes and a Fresnel. Most of what makes a curved panel read as metal is // that it shows the sky on top and the tarmac underneath, and that costs a // dot product. vec3 env(vec3 r){ return mix(pTar*0.55, mix(pHaze, pSky, clamp(r.y*1.6, 0.0, 1.0)), smoothstep(-0.12, 0.10, r.y)); } vec3 shade(vec3 p, vec3 rd, vec3 n, float mat){ vec3 alb = vec3(0.5); float gloss = 0.0, metal = 0.0; vec3 emis = vec3(0.0); if (mat < 1.5){ // ---- the lot ---- float agg = vnoise(p.xz*38.0)*0.6 + vnoise(p.xz*130.0)*0.4; alb = pTar*(0.82 + 0.34*agg); alb *= mix(1.0, 0.72 + 0.55*vnoise(p.xz*0.9 + 3.0), uWear); // bay markings: 2.5 m centres, 5 m deep float bx = abs(mod(p.x + 1.25, 2.50) - 1.25); float bz = abs(p.z); float lw = 0.055; float line = (1.0 - smoothstep(lw, lw + 0.02, bx))*step(bz, 2.55); line = max(line, (1.0 - smoothstep(lw, lw + 0.02, abs(bz - 2.55)))*step(abs(p.x + 1.25), 3.8)); line *= uLines*(0.45 + 0.55*vnoise(p.xz*7.0)); // worn paint alb = mix(alb, pLine, line); gloss = 0.10 + 0.25*line; } else if (mat < 3.5){ // ---- the shell ---- // Undo the dance to ask the body where on itself we are, because the // trim, the glass and the lamps are fixed to the CAR and not to the // world it is bouncing around in. vec3 q = p - vec3(0.0, dBounce, 0.0); float cy = cos(-dYaw), sy = sin(-dYaw); q = vec3(cy*q.x + sy*q.z, q.y, -sy*q.x + cy*q.z); float cz = cos(-dRoll), sz = sin(-dRoll); q = vec3(cz*q.x - sz*q.y, sz*q.x + cz*q.y, q.z)/dScale; alb = uPaint.rgb; gloss = 0.55 + 0.45*uGloss; metal = 0.65*uGloss; // glass, wherever the greenhouse is above the beltline float belt = gY - gHgt + 0.015; // Glass is EVERYTHING between the beltline and the roof panel. The // windscreen and the tailgate are raked cuts through the same box the // side glass comes from, so keying all three off HEIGHT gets them for // free; fading glass out from partway up the cabin quietly painted the // screens body colour, because they are the two parts that reach the // roof. float glass = step(belt, q.y)*step(abs(q.z - gZ), gLen + 0.45); glass *= 1.0 - step(gY + gHgt - 0.030, q.y); // the B and C pillars, which stand on the FLANK - a pillar mask keyed // to z alone also cuts a stripe out of the windscreen float pill = step(0.90, abs(sin((q.z - gZ)*3.4)))*step(bWid - 0.17, abs(q.x)); glass *= 1.0 - pill; alb = mix(alb, vec3(0.055, 0.070, 0.085), glass); gloss = mix(gloss, 1.0, glass); metal = mix(metal, 0.10, glass); // black plastic bumpers, sills and the grille band float bump = step(bLen - 0.30, abs(q.z))*step(q.y, bHgt - bHlf*0.20); bump = max(bump, step(q.y, bHgt - bHlf*0.72)*step(abs(q.z), bLen - 0.18)); alb = mix(alb, vec3(0.055, 0.055, 0.060), bump); gloss = mix(gloss, 0.25, bump); metal = mix(metal, 0.0, bump); // lamps: two square units each end, an 80s hatchback's whole face float lampY = step(abs(q.y - (bHgt + bHlf*0.42)), 0.11); float lampX = step(0.30, abs(q.x))*step(abs(q.x), bWid + 0.02); float lampF = step(bLen - 0.26, q.z)*lampY*lampX; float lampR = step(bLen - 0.26, -q.z)*lampY*lampX; alb = mix(alb, vec3(0.90, 0.88, 0.80), lampF); alb = mix(alb, vec3(0.62, 0.07, 0.05), lampR); emis += vec3(0.90, 0.86, 0.72)*lampF*0.18 + vec3(0.80, 0.10, 0.07)*lampR*0.22; gloss = mix(gloss, 1.0, max(lampF, lampR)); } else { // ---- wheels ---- vec3 q = p - vec3(0.0, dBounce, 0.0); float cy = cos(-dYaw), sy = sin(-dYaw); q = vec3(cy*q.x + sy*q.z, q.y, -sy*q.x + cy*q.z); q = q/dScale; float rr = length(vec2(q.y - wrad, abs(q.z) - wbase)); float hub = smoothstep(wrad*0.66, wrad*0.58, rr); alb = mix(vec3(0.035, 0.035, 0.040), mix(vec3(0.42, 0.43, 0.45), vec3(0.62, 0.63, 0.66), uTyre), hub); // five holes in a steel wheel, which is what an 80s base model had float a = atan(q.y - wrad, abs(q.z) - wbase); float spoke = smoothstep(0.55, 0.85, abs(sin(a*2.5)))*hub*step(rr, wrad*0.50); alb = mix(alb, vec3(0.10, 0.10, 0.11), spoke); gloss = 0.15 + 0.55*hub; metal = 0.5*hub; } // ---- light: one key, one sky, no bounce. A fixed-function pipeline had // exactly this and nothing more. float ndl = max(dot(n, gSun), 0.0); float sh = (mat < 1.5) ? contact(p.xz) : 1.0; vec3 lit = gSunC*ndl*sh*(1.1 + 1.2*uBright); vec3 amb = mix(pHaze, pSky, clamp(n.y*0.5 + 0.5, 0.0, 1.0))*(0.34 + 0.30*uBright); vec3 col = alb*(lit + amb) + emis; if (gloss > 0.02){ vec3 rf = reflect(rd, n); float fres = 0.04 + 0.96*pow(1.0 - max(dot(-rd, n), 0.0), 5.0); col = mix(col, env(rf)*mix(vec3(1.0), alb, metal), fres*gloss*(0.35 + 0.65*metal + 0.25)); vec3 h = normalize(gSun - rd); col += gSunC*pow(max(dot(n, h), 0.0), mix(18.0, 220.0, gloss))*gloss*sh*(0.5 + 0.6*uBright); } return col; } // --------------------------------------------------------------- void main(){ vec2 R = RENDERSIZE.xy; // The pixelation is the render: the coordinate is quantised BEFORE the ray // is built, so every chunky pixel is a real sample of the geometry. float sc = max(R.y/max(uRes, 16.0), 1.0); vec2 fc = (floor(gl_FragCoord.xy/sc) + 0.5)*sc; float T = TIME; gT = T; pSky = v4(vec3(0.42,0.55,0.74), vec3(0.62,0.64,0.66), vec3(0.30,0.34,0.50), vec3(0.72,0.60,0.44), uSky); pHaze = v4(vec3(0.74,0.80,0.88), vec3(0.78,0.78,0.78), vec3(0.52,0.56,0.66), vec3(0.90,0.76,0.58), uSky); pTar = v4(vec3(0.24,0.245,0.26), vec3(0.26,0.26,0.265),vec3(0.20,0.21,0.24), vec3(0.28,0.26,0.24), uSky); pLine = vec3(0.80, 0.79, 0.74); gSunC = v4(vec3(1.00,0.95,0.84), vec3(0.88,0.89,0.90), vec3(0.70,0.78,0.95), vec3(1.00,0.78,0.52), uSky); gSun = normalize(vec3(sin(uSun)*0.72, 0.62, cos(uSun)*0.72)); // ---- the body, morphed ---- // Measured cars, in metres, halved wherever the field wants a half-extent. // 0: 3.71 x 1.57 x 1.37 supermini 1: 3.99 x 1.67 x 1.42 five-door hatch // 2: 4.05 x 1.65 x 1.31 wedge coupe 3: 4.30 x 1.68 x 1.45 long-roof estate // // A silhouette is made of four numbers - sill, beltline, roof, wheelbase - // and the mistake worth naming is deriving the body box from a centre and // a half-height guessed independently of them. Take the belt and the sill // as the primitives and let the box fall out, and the glass, the bumpers // and the lamps all land where they belong for free. bLen = p4(1.853, 1.993, 2.025, 2.150, uModel); bWid = p4(0.786, 0.833, 0.825, 0.840, uModel); float belt = p4(0.920, 0.950, 0.900, 0.960, uModel); float roof = p4(1.372, 1.415, 1.310, 1.450, uModel); float sill = p4(0.330, 0.340, 0.325, 0.345, uModel); bHgt = 0.5*(sill + belt); bHlf = 0.5*(belt - sill); gY = 0.5*(belt + roof); gHgt = 0.5*(roof - belt); gLen = p4(0.820, 0.875, 0.800, 1.150, uModel); gZ = p4(-0.380,-0.425,-0.350,-0.700, uModel); rkF = p4(1.55, 1.73, 1.15, 1.73, uModel); rkR = p4(1.30, 1.19, 0.75, 4.00, uModel); wbase = p4(1.210, 1.238, 1.200, 1.240, uModel); wtrack = p4(0.695, 0.713, 0.705, 0.710, uModel); wrad = p4(0.270, 0.290, 0.290, 0.300, uModel); wwid = p4(0.085, 0.093, 0.096, 0.096, uModel); // ---- the dance ---- // Volume preserving: squash the height by k and the horizontals go as one // over root k. That single relationship is the whole of why squash and // stretch reads as a body with mass instead of an object being resized. float ph = T*uTempo*3.2; float beat = sin(ph); float k = 1.0 + uDance*uSquash*0.20*beat; k = max(k, 0.35); dScale = vec3(inversesqrt(k), k, inversesqrt(k)); dBounce = uDance*uBounce*0.20*max(sin(ph - 1.2), 0.0); dRoll = uDance*uSway*0.13*sin(ph*0.5 + 0.6); dYaw = uDance*uSway*0.30*sin(ph*0.25); // ---- the camera, which does not move ---- vec3 tgt = vec3(0.0, 0.80, 0.0); vec3 ro = tgt + vec3(sin(uOrbit)*uDist, uElev, cos(uOrbit)*uDist); vec3 fw = normalize(tgt - ro); vec3 rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0))); vec3 up = cross(rt, fw); vec2 uv = (fc - 0.5*R)/R.y; vec3 rd = normalize(uv.x*rt + uv.y*up + uLens*fw); float tt = 0.02, tHit = FAR, mat = 0.0; for (int i = 0; i < 110; i++){ vec2 m = map(ro + rd*tt); if (m.x < 0.0012*tt + 0.0006){ tHit = tt; mat = m.y; break; } tt += m.x*0.88; if (tt > FAR) break; } vec3 col; if (mat > 0.5){ vec3 p = ro + rd*tHit; col = shade(p, rd, calcN(p), mat); col = mix(col, pHaze, smoothstep(18.0, 52.0, tHit)); } else { col = mix(pHaze, pSky, clamp(rd.y*2.2, 0.0, 1.0)); } // ---- the framebuffer ---- col = col/(1.0 + col*0.68); vec2 sp = fc/R; col *= 1.0 - 0.30*dot(sp - 0.5, sp - 0.5); vec2 bp = mod(floor(gl_FragCoord.xy/max(sc*0.5, 1.0)), 4.0); float bayer = mod(bp.x*5.0 + bp.y*3.0 + bp.x*bp.y*2.0, 16.0)/16.0; float levels = pow(2.0, floor(uBits + 0.5)) - 1.0; col += (bayer - 0.5)*uDither/levels; col = floor(clamp(col, 0.0, 1.0)*levels + 0.5)/levels; col += (hash12(gl_FragCoord.xy + floor(T*24.0)*vec2(13.7, 91.3)) - 0.5)*uGrain*0.06; gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0); }
PARKING LOT - a European 80s hatchback, alone in a marked bay, dancing. The car is one signed distance field whose PROPORTIONS are the parameter: uModel slides continuously through four sets of numbers - three-door supermini, five-door hatch, wedge coupe, long-roof estate - and every dimension in the body, the greenhouse, the rake of the screen and the hatch, the track and the wheelbase is a lerp between them, so the slider is a morph rather than a switch and the shapes between the presets are all valid cars. The dance is a transform on the RAY, not on the geometry: the sample point is pushed backward through a bounce, a roll, a yaw and a non-uniform scale before the car field is evaluated, and the returned distance is multiplied by the smallest scale factor so the field stays conservative and the march stays legal. The scale is volume-preserving - squash the height by k and the two horizontal axes go as one over root k - which is the whole of what makes squash-and-stretch read as a body with mass in it rather than as an object being resized. Every edge on the shell is a hard intersection of planes - no rounded boxes anywhere - because a smoothed corner is a modern renderer's tell and the whole point of the era being quoted is flat-shaded facets meeting at a line. The paint takes a colour straight off a picker and gets a two-lobe fake environment - sky above the horizon, tarmac below it, reflected by the normal and mixed by Fresnel - which is most of what makes a curved panel look like a curved panel, and it costs one dot product. The contact shadow is an analytic ellipse that squashes and slides with the car rather than a shadow ray, because the only occluder in the scene is the thing casting it. The PS2 look is the render and not a filter: the fragment coordinate is quantised before the ray is built, so the frame is genuinely traced at a couple of hundred lines, and the colour is posterised through an ordered 4x4 Bayer dither. Twenty-two controls: model, paint colour, gloss, tyre wall, orbit, elevation, distance, lens, dance amount, tempo, bounce, squash, sway, sun angle, brightness, sky, bay markings, tarmac wear, render resolution, colour depth, dither, and grain.
export clip · render a video in your browser
Renders to video in your browser (no upload), stepping the real renderer frame by frame so multipass + feedback come out right. motion auto-animates the shader's own knobs — each export rolls a fresh take (🎲 re-roll, 🔒 to pin one you like). GIF is slower with a tighter length cap; MP4/WebM are faster. Some devices record in real time (noted below if so) — keep the tab visible.
embed · drop this shader into any page
Renders client-side in the reader's browser (CC0 — no attribution required, but the backlink is appreciated).