fork a shader
Write or paste an ISF shader (GLSL ES with a /*{ ... }*/ header). It compiles + renders in yer browser. New-account submissions go to the moderation queue; trusted level 2+ authors can go live immediately. Everything is CC0. Got a file first? preview it locally →
A model marks this as witnessed_generation (honest AI provenance). Leave blank if you hand-authored it.
/*{ "DESCRIPTION": "AISLE 4 - an endless PS2-era liquor store. Gondolas, shelves and bottles are all domain repetition, so the store is unbounded and costs one bay: the plan folds x by the aisle pitch and z by the bay length, the shelf level folds y, and the bottles fold again inside the shelf, which is four nested folds and about forty ALU for a shop with no far wall. The bottles are the only thing that must NOT fold cleanly - a rack of identical cylinders reads as wallpaper - so each one draws its height, radius, cap and colour from a hash of its four fold indices, and the SDF is written as a stack of clipped cylinders whose radii come from that hash rather than as one repeated primitive. The PS2 look is not a filter over a modern render, it is the render: the ray is built from a QUANTISED fragment coordinate, so the whole frame is genuinely traced at 320-ish lines and the chunky pixels are real geometry sampling rather than a blur; lighting is a single lambert term plus a fixed ambient with no shadows and no bounce, which is what a fixed-function TnL pipeline actually gave you; the colour is posterised to five bits and pushed through an ordered 4x4 Bayer dither, because the console dithered its way out of a 16-bit framebuffer and that dither pattern is more of the memory than the polygon count is. Fluorescent troffers run down the ceiling over each aisle on their own lattice, gathered nine at a time as RECTANGLES rather than points so the pools they throw are strips and not spots, with one tube per fixture failing on a hash and a mains-frequency buzz on the rest. Twenty-four controls: walk, lens, yaw and pitch, sway, aisle pitch, aisle width, bay length, shelf height, shelf count, bottle density, light spacing, brightness, colour temperature, dead tubes, flicker, four palettes, render resolution, colour depth, dither, fog, vignette and grain.", "CREDIT": "CC0. Original. Value noise constants after Dave Hoskins.", "CATEGORIES": ["generator", "raymarch", "liminal", "retro", "ps2", "interior", "procedural", "3d"], "INPUTS": [ { "NAME": "uSpeed", "TYPE": "float", "DEFAULT": 1.60, "MIN": 0.0, "MAX": 6.0 }, { "NAME": "uLens", "TYPE": "float", "DEFAULT": 1.20, "MIN": 0.7, "MAX": 3.0 }, { "NAME": "uYaw", "TYPE": "float", "DEFAULT": 0.0, "MIN": -1.57, "MAX": 1.57 }, { "NAME": "uPitch", "TYPE": "float", "DEFAULT": -0.03, "MIN": -0.5, "MAX": 0.5 }, { "NAME": "uBob", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uPitchX", "TYPE": "float", "DEFAULT": 3.60, "MIN": 2.2, "MAX": 7.0 }, { "NAME": "uAisle", "TYPE": "float", "DEFAULT": 1.05, "MIN": 0.55, "MAX": 2.4 }, { "NAME": "uBay", "TYPE": "float", "DEFAULT": 6.50, "MIN": 3.0, "MAX": 16.0 }, { "NAME": "uShelfH", "TYPE": "float", "DEFAULT": 1.95, "MIN": 1.0, "MAX": 2.6 }, { "NAME": "uShelves", "TYPE": "float", "DEFAULT": 5.0, "MIN": 2.0, "MAX": 8.0 }, { "NAME": "uBottles", "TYPE": "float", "DEFAULT": 0.86, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uCeil", "TYPE": "float", "DEFAULT": 3.05, "MIN": 2.3, "MAX": 5.0 }, { "NAME": "uLightSp", "TYPE": "float", "DEFAULT": 3.66, "MIN": 1.8, "MAX": 9.0 }, { "NAME": "uBright", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 3.0 }, { "NAME": "uTemp", "TYPE": "float", "DEFAULT": 4300.0,"MIN": 2400.0,"MAX": 7500.0 }, { "NAME": "uDead", "TYPE": "float", "DEFAULT": 0.10, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uFlicker", "TYPE": "float", "DEFAULT": 0.35, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uPalette", "TYPE": "float", "DEFAULT": 0.0, "MIN": 0.0, "MAX": 3.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": "uFog", "TYPE": "float", "DEFAULT": 0.030, "MIN": 0.0, "MAX": 0.12 }, { "NAME": "uVignette", "TYPE": "float", "DEFAULT": 0.30, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uGrain", "TYPE": "float", "DEFAULT": 0.12, "MIN": 0.0, "MAX": 1.0 } ] }*/ // AISLE 4 - ISF / GLSL ES 1.00 (gl_FragColor, TIME, RENDERSIZE). #define PI 3.14159265359 #define FAR 60.0 float gPX, gT, gNS; float gSpineW, gShelfW, gStep; vec3 gLite, pFloorA, pFloorB, pShelf, pCeil, pWall; 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); } vec4 hash42(vec2 p){ vec4 q = fract(vec4(p.xyxy)*vec4(0.1031, 0.1030, 0.0973, 0.1099)); q += dot(q, q.wzxy + 33.33); return fract((q.xxyz + q.yzzw)*q.zywx); } vec4 hash43(vec3 p){ vec4 q = fract(vec4(p.xyzx)*vec4(0.1031, 0.1030, 0.0973, 0.1099)); q += dot(q, q.wzxy + 33.33); return fract((q.xxyz + q.yzzw)*q.zywx); } 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); } vec3 kelvin(float k){ k = clamp(k, 1000.0, 15000.0)*0.01; float r, g, b; if (k <= 66.0){ r = 1.0; g = clamp(0.39008158*log(max(k, 1.0)) - 0.63184144, 0.0, 1.0); } else { r = clamp(1.29293619*pow(max(k - 60.0, 0.001), -0.1332047), 0.0, 1.0); g = clamp(1.12989086*pow(max(k - 60.0, 0.001), -0.0755148), 0.0, 1.0); } if (k >= 66.0) b = 1.0; else if (k <= 19.0) b = 0.0; else b = clamp(0.54320679*log(max(k - 10.0, 1.0)) - 1.19625408, 0.0, 1.0); vec3 c = vec3(r, g, b)/max(max(r, g), max(b, 1e-3)); return c*(0.94/max(dot(c, vec3(0.299, 0.587, 0.114)), 1e-3)); } vec3 pick4(vec3 a, vec3 b, vec3 c, vec3 d, float f){ float i = clamp(f, 0.0, 3.0); float n = floor(i), k = i - n; vec3 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)); vec3 hi = b; hi = mix(hi, c, step(0.5, n)); hi = mix(hi, d, step(1.5, n)); return mix(lo, hi, k); } 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 cylinder standing on y = 0, capped at h float sdCyl(vec3 p, float r, float h){ vec2 d = vec2(length(p.xz) - r, abs(p.y - 0.5*h) - 0.5*h); return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); } // --------------------------------------------------------------- // the fittings // --------------------------------------------------------------- // Troffers run down the ceiling over each aisle, on their own lattice in z. // Gathered as RECTANGLES rather than points: a four-foot tube throws a STRIP // down the floor, and a point light in its place gives circular pools that // nothing in a shop has ever looked like. vec2 lightCentre(vec2 lid){ return vec2(lid.x*uPitchX, (lid.y + 0.5)*uLightSp); } #define TUBE_E vec2(0.16, 0.60) float tubeOn(vec2 lid){ vec4 h = hash42(lid + 5.31); float on = step(uDead, h.x); // the mains buzz, and the one at the end of the row that never quite struck float buzz = 1.0 - uFlicker*0.06*sin(gT*100.0 + h.y*40.0); float bad = step(h.z, 0.10 + 0.25*uFlicker); float strobe = mix(1.0, step(0.45, fract(gT*(1.7 + 5.0*h.w) + h.y)), bad*uFlicker); return on*buzz*strobe; } // --------------------------------------------------------------- // the store // --------------------------------------------------------------- // Four nested folds: x by the aisle pitch, z by the bay, y by the shelf, and // the bottles again inside the shelf. The whole shop is one bay of arithmetic. // // .x = distance, .y = material (1 floor, 2 ceiling, 3 gondola, 4 bottle, // 5 troffer, 6 kick rail) vec2 map(vec3 p){ vec2 r = vec2(p.y, 1.0); float dc = uCeil - p.y; if (dc < r.x) r = vec2(dc, 2.0); float P = uPitchX; float gx = mod(p.x, P) - 0.5*P; // gondolas at (i+0.5)*P, aisles at i*P float bz = mod(p.z, uBay) - 0.5*uBay; float hlen = 0.5*uBay - 0.28; // the gap between runs // the spine, the base deck and the top canopy float d = sdBox(vec3(gx, p.y - uShelfH*0.5, bz), vec3(gSpineW, uShelfH*0.5, hlen)); d = min(d, sdBox(vec3(gx, p.y - 0.16, bz), vec3(gShelfW, 0.16, hlen))); d = min(d, sdBox(vec3(gx, p.y - uShelfH, bz), vec3(gShelfW*0.92, 0.035, hlen))); if (d < r.x) r = vec2(d, 3.0); // the shelf plates, folded in y float y0 = 0.36; float ky = clamp(floor((p.y - y0)/gStep + 0.5), 0.0, gNS - 1.0); float py = y0 + ky*gStep; float dp = sdBox(vec3(gx, p.y - py, bz), vec3(gShelfW, 0.020, hlen)); if (dp < r.x) r = vec2(dp, 3.0); // ---- the bottles ---- // The one thing that must NOT fold cleanly. A rack of identical cylinders // reads as wallpaper, so every bottle pulls its height, its shoulder, its // cap and its colour out of a hash of the four indices that found it, and // the body is a stack of clipped cylinders rather than one primitive. if (uBottles > 0.02 && abs(gx) < gShelfW + 0.05){ float bw = 0.092; float ax = abs(gx) - (gSpineW + 0.055); float ix = floor(ax/bw); float jz = floor((bz + hlen)/bw); float lx = ax - (ix + 0.5)*bw; float lz = (bz + hlen) - (jz + 0.5)*bw; vec4 h = hash43(vec3(ix*sign(gx + 1e-4), jz, ky) + floor(p.x/P + 0.5)*11.0); float here = step(1.0 - uBottles, h.x)*step(0.0, ax)*step(ax, gShelfW - gSpineW - 0.10); if (here > 0.5){ float bh = 0.20 + 0.15*h.y; float rr = 0.028 + 0.011*h.z; vec3 q = vec3(lx, p.y - (py + 0.020), lz); float db = sdCyl(q, rr, bh*0.66); // the body db = min(db, sdCyl(q - vec3(0.0, bh*0.66, 0.0), rr*0.42, bh*0.34)); // the neck db = min(db, sdCyl(q - vec3(0.0, bh*0.97, 0.0), rr*0.50, bh*0.06)); // the cap if (db < r.x) r = vec2(db, 4.0); } } // ---- the troffers ---- vec2 lid = vec2(floor(p.x/P + 0.5), floor(p.z/uLightSp)); vec2 lc = lightCentre(lid); float dt = sdBox(vec3(p.x - lc.x, p.y - (uCeil - 0.035), p.z - lc.y), vec3(TUBE_E.x, 0.035, TUBE_E.y)); if (dt < r.x) r = vec2(dt, 5.0); return r; } vec3 calcN(vec3 p){ vec2 e = vec2(1.0, -1.0)*0.0012; 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); } // Nine nearest tubes, as rectangles. Clamping the shading point into the // fixture's own extent before taking the direction is what turns a point into // a strip, and it is two clamps. vec3 lighting(vec3 p, vec3 n){ float P = uPitchX; vec2 lb = vec2(floor(p.x/P + 0.5), floor(p.z/uLightSp)); vec3 sum = vec3(0.0); for (int j = 0; j < 3; j++){ for (int i = 0; i < 3; i++){ vec2 lid = lb + vec2(float(i) - 1.0, float(j) - 1.0); vec2 lc = lightCentre(lid); vec3 pl = vec3(clamp(p.x, lc.x - TUBE_E.x, lc.x + TUBE_E.x), uCeil - 0.06, clamp(p.z, lc.y - TUBE_E.y, lc.y + TUBE_E.y)); vec3 L = pl - p; float dl = max(length(L), 0.10); L /= dl; // a fixture has to be worth nothing by the time the window drops it vec2 ho = max(abs(p.xz - lc) - TUBE_E, 0.0); float w = tubeOn(lid)*max(dot(n, L), 0.0) * smoothstep(1.55*uLightSp, 1.0*uLightSp, length(ho))/(1.0 + 0.42*dl*dl); sum += gLite*w; } } return sum; } // --------------------------------------------------------------- vec3 shade(vec3 p, vec3 rd, vec3 n, float mat){ vec3 alb = vec3(0.6); float spec = 0.0; vec3 emis = vec3(0.0); if (mat < 1.5){ // vinyl composition tile, one foot square, with the odd scuffed one vec2 g = p.xz/0.305; vec2 id = floor(g), f = fract(g); vec4 h = hash42(id + 2.7); alb = mix(pFloorA, pFloorB, step(0.5, h.x)); alb *= 0.90 + 0.18*h.y; alb *= 1.0 + 0.10*(vnoise(p.xz*70.0) - 0.5); float joint = min(min(f.x, 1.0 - f.x), min(f.y, 1.0 - f.y)); alb *= mix(0.72, 1.0, smoothstep(0.0, 0.035, joint)); spec = 0.55; // waxed } else if (mat < 2.5){ // 2x2 acoustic tile vec2 g = p.xz/0.61; vec2 f = fract(g); float joint = min(min(f.x, 1.0 - f.x), min(f.y, 1.0 - f.y)); alb = pCeil*(0.88 + 0.20*vnoise(p.xz*40.0)); alb *= mix(0.62, 1.0, smoothstep(0.0, 0.03, joint)); } else if (mat < 3.5){ alb = pShelf*(0.88 + 0.16*hash11(floor(p.y/gStep)*3.7 + floor(p.x/uPitchX)*9.1)); alb *= 0.94 + 0.12*vnoise(vec2(p.x + p.z, p.y)*55.0); spec = 0.30; } else if (mat < 4.5){ // Glass, and the label. Every bottle has to be a different bottle or // the whole rack collapses back into a texture. float P = uPitchX; float gx = mod(p.x, P) - 0.5*P; float bz = mod(p.z, uBay) - 0.5*uBay; float hlen = 0.5*uBay - 0.28; float ky = clamp(floor((p.y - 0.36)/gStep + 0.5), 0.0, gNS - 1.0); float ax = abs(gx) - (gSpineW + 0.055); float ix = floor(ax/0.092), jz = floor((bz + hlen)/0.092); vec4 h = hash43(vec3(ix*sign(gx + 1e-4), jz, ky) + floor(p.x/P + 0.5)*11.0); vec3 glass = pick4(vec3(0.42, 0.25, 0.10), vec3(0.16, 0.30, 0.14), vec3(0.55, 0.42, 0.16), vec3(0.30, 0.14, 0.16), h.y*3.0); float ly = p.y - (0.36 + ky*gStep); float label = smoothstep(0.055, 0.070, ly)*smoothstep(0.185, 0.170, ly); vec3 lab = pick4(vec3(0.82, 0.78, 0.62), vec3(0.75, 0.15, 0.12), vec3(0.10, 0.16, 0.42), vec3(0.86, 0.72, 0.20), h.z*3.0); alb = mix(glass, lab, label*step(0.25, h.w)); spec = mix(0.85, 0.35, label); } else if (mat < 5.5){ vec2 lid = vec2(floor(p.x/uPitchX + 0.5), floor(p.z/uLightSp)); float on = tubeOn(lid); alb = vec3(0.60); emis = gLite*on*(2.2 + 7.0*uBright)*clamp(-n.y*1.4 + 0.30, 0.0, 1.0); } vec3 lit = lighting(p, n); // No shadows and no bounce, because a fixed-function pipeline had neither: // one lambert term and a flat ambient is the entire lighting model, and // getting the ambient level right is what stops it reading as "broken" // rather than as "2001". vec3 amb = mix(pWall, gLite, 0.35)*(0.085 + 0.075*uBright)*(0.55 + 0.45*max(n.y, 0.0)); vec3 col = alb*(lit*(1.7 + 1.9*uBright) + amb) + emis; if (spec > 0.02){ vec3 h2 = normalize(vec3(0.0, 1.0, 0.0) - rd); col += gLite*pow(max(dot(n, h2), 0.0), mix(14.0, 70.0, spec)) * spec*length(lit)*0.55; } return col; } // --------------------------------------------------------------- void main(){ vec2 R = RENDERSIZE.xy; // THE PIXELATION IS THE RENDER, not a filter over it. The fragment // coordinate is quantised BEFORE the ray is built, so the frame is // genuinely traced at a couple of hundred lines and every chunky pixel is // one real sample of the geometry - which is why the edges crawl the way // they did rather than looking like a blurred screenshot. 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; gNS = max(floor(uShelves + 0.5), 1.0); gSpineW = 0.10; gShelfW = max(uAisle*0.62, 0.30); gStep = max((uShelfH - 0.42)/gNS, 0.14); gLite = kelvin(uTemp); pFloorA = pick4(vec3(0.62,0.60,0.55), vec3(0.55,0.57,0.60), vec3(0.60,0.56,0.48), vec3(0.50,0.52,0.50), uPalette); pFloorB = pick4(vec3(0.50,0.47,0.42), vec3(0.42,0.45,0.50), vec3(0.47,0.42,0.35), vec3(0.38,0.42,0.40), uPalette); pShelf = pick4(vec3(0.52,0.53,0.55), vec3(0.30,0.34,0.40), vec3(0.56,0.50,0.42), vec3(0.34,0.40,0.36), uPalette); pCeil = pick4(vec3(0.78,0.77,0.72), vec3(0.72,0.75,0.78), vec3(0.80,0.76,0.66), vec3(0.70,0.74,0.70), uPalette); pWall = pick4(vec3(0.55,0.54,0.50), vec3(0.42,0.48,0.54), vec3(0.58,0.52,0.42), vec3(0.44,0.50,0.46), uPalette); // ---- the walk ---- float t = T*uSpeed; vec3 ro = vec3(uBob*0.030*sin(t*2.2), 1.58 + uBob*0.028*sin(t*4.4), t); float yaw = uYaw + uBob*0.024*sin(t*0.83 + 1.0); float pit = uPitch + uBob*0.014*sin(t*1.6); vec3 fw = normalize(vec3(sin(yaw), pit, cos(yaw))); vec3 rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0))); vec3 up = cross(rt, fw); float roll = uBob*0.010*sin(t*2.0 + 0.4); vec3 r2 = rt*cos(roll) + up*sin(roll); up = up*cos(roll) - rt*sin(roll); rt = r2; vec2 uv = (fc - 0.5*R)/R.y; vec3 rd = normalize(uv.x*rt + uv.y*up + uLens*fw); gPX = sc/(uLens*R.y); float tt = 0.0, tHit = FAR, mat = 0.0; for (int i = 0; i < 128; i++){ vec2 m = map(ro + rd*tt); if (m.x < 0.0016*tt + 0.0008){ tHit = tt; mat = m.y; break; } tt += m.x*0.90; if (tt > FAR) break; } vec3 fogc = mix(pWall, gLite, 0.30)*(0.10 + 0.16*uBright); vec3 col = fogc; if (mat > 0.5){ vec3 p = ro + rd*tHit; vec3 n = calcN(p); col = shade(p, rd, n, mat); float f = 1.0 - exp(-uFog*tHit); col = mix(col, fogc, max(f, smoothstep(FAR*0.55, FAR*0.92, tHit))); } // ---- the framebuffer ---- col = col/(1.0 + col*0.72); vec2 sp = fc/R; col *= 1.0 - uVignette*0.9*dot(sp - 0.5, sp - 0.5); // A 4x4 Bayer matrix and then a hard quantise. The console dithered its // way out of a 16-bit framebuffer, and that cross-hatch in the gradients is // more of the memory of it than the polygon budget ever was - so the dither // has to happen BEFORE the quantise, and the quantise has to be a floor. 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; float nz = hash12(gl_FragCoord.xy + floor(T*24.0)*vec2(13.7, 91.3)); col += (nz - 0.5)*uGrain*0.06; gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0); }
forking #1azsp0fq — lineage will be recorded.
renders in YOUR browser. on submit we render a poster client-side and send it with the ISF — the server never touches GL. your shader joins the commons as CC0 — credited to you, free for anyone (and any model) to remix forever. that's the whole point. it's permanent: once it's public, CC0 has no take-backs. by submitting you attest you have the right to release it as CC0.