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":"Recursive portal between a tropical lagoon and a rain-slicked neon city. Seamless 16s loop.", "CREDIT":"Craig", "ISFVSN":"2", "CATEGORIES":["Generator","3D","Raymarching"], "INPUTS":[ {"NAME":"speed","LABEL":"Flight Speed","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":3.0}, {"NAME":"phaseOffset","LABEL":"Phase Offset","TYPE":"float","DEFAULT":0.0,"MIN":0.0,"MAX":1.0}, {"NAME":"detail","LABEL":"Scene Detail","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":1.0}, {"NAME":"quality","LABEL":"March Quality","TYPE":"float","DEFAULT":1.0,"MIN":0.35,"MAX":1.0}, {"NAME":"neonHue","LABEL":"Portal Hue","TYPE":"float","DEFAULT":0.99,"MIN":0.0,"MAX":1.0}, {"NAME":"neonSat","LABEL":"Portal Sat","TYPE":"float","DEFAULT":0.94,"MIN":0.0,"MAX":1.0}, {"NAME":"neonGain","LABEL":"Portal Gain","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":3.0}, {"NAME":"portalFlash","LABEL":"Transit Flash","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":3.0}, {"NAME":"fogTropic","LABEL":"Haze Lagoon","TYPE":"float","DEFAULT":0.0058,"MIN":0.0,"MAX":0.03}, {"NAME":"fogCity","LABEL":"Haze City","TYPE":"float","DEFAULT":0.0092,"MIN":0.0,"MAX":0.03}, {"NAME":"volAmt","LABEL":"Volumetrics","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":2.5}, {"NAME":"rainAmt","LABEL":"City Rain","TYPE":"float","DEFAULT":0.7,"MIN":0.0,"MAX":2.0}, {"NAME":"wetness","LABEL":"Street Wetness","TYPE":"float","DEFAULT":0.75,"MIN":0.0,"MAX":1.0}, {"NAME":"moteAmt","LABEL":"Lagoon Motes","TYPE":"float","DEFAULT":0.35,"MIN":0.0,"MAX":2.0}, {"NAME":"partAmt","LABEL":"Fly-By Particles","TYPE":"float","DEFAULT":0.9,"MIN":0.0,"MAX":3.0}, {"NAME":"partSize","LABEL":"Particle Size","TYPE":"float","DEFAULT":1.0,"MIN":0.2,"MAX":3.0}, {"NAME":"partDens","LABEL":"Particle Density","TYPE":"float","DEFAULT":0.4,"MIN":0.0,"MAX":1.0}, {"NAME":"partStreak","LABEL":"Particle Streak","TYPE":"float","DEFAULT":0.35,"MIN":0.0,"MAX":1.0}, {"NAME":"partDrift","LABEL":"Particle Drift","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":3.0}, {"NAME":"sunAz","LABEL":"Sun Azimuth","TYPE":"float","DEFAULT":0.26,"MIN":-1.0,"MAX":1.0}, {"NAME":"sunEl","LABEL":"Sun Height","TYPE":"float","DEFAULT":0.22,"MIN":0.02,"MAX":0.7}, {"NAME":"lensBase","LABEL":"Lens (wide)","TYPE":"float","DEFAULT":1.15,"MIN":0.6,"MAX":2.5}, {"NAME":"lensPunch","LABEL":"Dolly Punch","TYPE":"float","DEFAULT":1.05,"MIN":0.0,"MAX":2.0}, {"NAME":"sway","LABEL":"Camera Sway","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":2.5}, {"NAME":"camHeight","LABEL":"Camera Height","TYPE":"float","DEFAULT":2.6,"MIN":0.8,"MAX":8.0}, {"NAME":"doReflect","LABEL":"Reflections","TYPE":"bool","DEFAULT":true}, {"NAME":"doShadow","LABEL":"Soft Shadows","TYPE":"bool","DEFAULT":true}, {"NAME":"audioMix","LABEL":"Audio Blend","TYPE":"float","DEFAULT":0.0,"MIN":0.0,"MAX":1.0}, {"NAME":"bassIn","LABEL":"Bass In","TYPE":"float","DEFAULT":0.0,"MIN":0.0,"MAX":1.0}, {"NAME":"midIn","LABEL":"Mid In","TYPE":"float","DEFAULT":0.0,"MIN":0.0,"MAX":1.0}, {"NAME":"highIn","LABEL":"High In","TYPE":"float","DEFAULT":0.0,"MIN":0.0,"MAX":1.0}, {"NAME":"bassGain","LABEL":"Bass Gain","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":3.0}, {"NAME":"midGain","LABEL":"Mid Gain","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":3.0}, {"NAME":"highGain","LABEL":"High Gain","TYPE":"float","DEFAULT":1.0,"MIN":0.0,"MAX":3.0}, {"NAME":"exposure","LABEL":"Exposure","TYPE":"float","DEFAULT":1.06,"MIN":0.2,"MAX":3.0}, {"NAME":"gammaAmt","LABEL":"Gamma","TYPE":"float","DEFAULT":0.9,"MIN":0.5,"MAX":1.6}, {"NAME":"vignette","LABEL":"Vignette","TYPE":"float","DEFAULT":0.32,"MIN":0.0,"MAX":1.0}, {"NAME":"grain","LABEL":"Grain","TYPE":"float","DEFAULT":0.016,"MIN":0.0,"MAX":0.08} ] }*/ // ============================================================================ // NEON // JUNGLE v7 - ISF port of the Shadertoy v5 raymarcher // // NEW IN v7 - FLY-BY PARTICLE FIELD // --------------------------------- // A world-locked point field the camera actually flies through, as opposed to // the screen-space rain / mote layers which only drift down the frame. // // - Points live on planes spaced PSPACE apart in world z. Because the field // is anchored to world space, points sweep outward from the vanishing // point, grow as they approach and streak past the lens - real parallax, // no fake velocity term needed. // - Depth-tested against the primary ray's hit distance (TOUT), so points // correctly disappear behind trunks, towers, rock and the door frame, and // stay visible through an open aperture. // - Each plane knows which world it sits in, so points ahead of a doorway // already carry the NEXT world's colour before you reach the gate: warm // pollen in the lagoon, cool spray / ash in the city. // - Points near a door plane pick up NEON, so the field flares as it passes // through the gate with you. // - ZPER/PSPACE = 40 (whole number), so the field tiles with the 16s loop // for free. Bob and sway use integer cycle counts for the same reason. // - Cost is one hash set per plane per pixel - trivial next to the march. // PSLICES x PSPACE = 60 units of depth, exactly one room. // // PORT NOTES (Shadertoy -> ISF) // ----------------------------- // iTime -> TIME // iResolution.xy -> RENDERSIZE // fragCoord -> gl_FragCoord.xy // mainImage() -> main() / gl_FragColor // iChannel0 audio -> bassIn / midIn / highIn float inputs (see main) // #define quality -> STEPS stays a compile-time cap, the "quality" slider // breaks the loop early (ES-safe: constant loop bounds) // NEON / SUN -> promoted from const to globals driven by inputs // // THE ENVIRONMENTS (the "in-between" of each door) // ------------------------------------------------ // LAGOON // - Water is depth-shaded. The channel floor height is sampled per water // pixel, so shallows near the banks go turquoise, the channel goes deep, // and a foam band forms wherever the terrain crosses the waterline. // - Lily-pad rafts on the surface (albedo + normal flatten, zero geometry). // - Understory: shrub / fern clumps along the bank, filling the gap between // the waterline and the palm trunks. // - Hanging vines off the canopy - fine vertical lines that catch the door // light and read strongly against the sky when a door is behind them. // - Rock gets stratification banding and a stronger wet/dry waterline. // // CITY // - Towers have podium bases and repeating ledge slabs, so the silhouettes // step instead of being flat extrusions. Rooftop tanks on the crowns. // - The city floor is a STREET: cambered asphalt crowned down the middle and // dipping into gutters at the kerb, raised pavements either side, lane // paint, crosswalks, manholes and drain grates. // - Puddles pool in the gutters and low spots, and only those patches // reflect - so the neon still lands on the ground without the whole world // reading as a canal. // - Lantern cables strung across the street, lamp posts along the kerb. // - Tower facades gained ledge glow bands, a scrolling ticker strip and // blocky sign panels on top of the existing window grid. // - Rain: screen-space streaks plus high-frequency ripple on the water, both // masked to the city world by the ray's final world state. // // Seamless loop: ZPER/SPEED = 16.0s at speed 1.0. // ============================================================================ #define TAU 6.28318530 // ---- quality caps (loop bounds must stay constant) ------------------------ #define STEPS 190 #define RSTEPS 60 #define SHSTEPS 20 #define PSLICES 20 // particle planes marched per pixel // ---- layout --------------------------------------------------------------- const float L = 60.0; // door spacing - how deep each world is const float ZPER = 120.0; // world content period, even multiple of L const float LOOPT = 16.0; // loop length in seconds // the door const float PW = 2.60; // aperture half width const float PHH = 4.40; // aperture half height const float PCY = 5.60; // centre height const float PTUBE = 0.075; const float PCR = 0.12; // the particle field const float PSPACE = 3.0; // world z between particle planes. 120/3 = 40 const float PCELL = 1.75; // lateral cell size on each plane // ---- globals -------------------------------------------------------------- float PH = 0.0; float BASS = 0.0; float MIDS = 0.0; float HIGH = 0.0; vec3 NEON = vec3(1.60, 0.10, 0.16); vec3 SUN = vec3(0.2540, 0.2235, 0.9410); float WOUT = 0.0; // world the ray ended in, for the overlays float TOUT = 1e5; // distance the ray ended at, for particle occlusion // detail tiers off one slider bool D1 = true; // ledges, shrubs, strata bool D2 = true; // cables, lanterns, vines bool D3 = true; // pylons, lily pads, tower signage // ---- hashing / noise ------------------------------------------------------ float hash11(float n){ return fract(sin(n)*43758.5453123); } float hash21(vec2 p){ return fract(sin(dot(p, vec2(27.17,113.71)))*43758.5453); } float hash3(float x, float y, float z){ return hash11(x + y*57.0 + z*113.0); } vec3 hsv2rgb(vec3 c){ vec3 p = abs(fract(c.xxx + vec3(0.0, 2.0/3.0, 1.0/3.0))*6.0 - 3.0); return c.z*mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), c.y); } // value noise with its lattice index wrapped in z - exactly periodic, free float vnoiseZP(vec3 x, float per){ vec3 p = floor(x), f = fract(x); f = f*f*(3.0-2.0*f); float z0 = mod(p.z, per); float z1 = mod(p.z + 1.0, per); float a = mix(mix(hash3(p.x, p.y, z0), hash3(p.x+1.0, p.y, z0), f.x), mix(hash3(p.x, p.y+1.0, z0), hash3(p.x+1.0, p.y+1.0, z0), f.x), f.y); float b = mix(mix(hash3(p.x, p.y, z1), hash3(p.x+1.0, p.y, z1), f.x), mix(hash3(p.x, p.y+1.0, z1), hash3(p.x+1.0, p.y+1.0, z1), f.x), f.y); return mix(a, b, f.z); } // s0*ZPER must be a whole number so the lattice tiles. 0.025 -> 3 cells. float fbmZ2(vec3 p, float s0){ float per = s0*ZPER; return 0.65*vnoiseZP(p*s0, per) + 0.35*vnoiseZP(p*s0*2.0, per*2.0); } // non-periodic - only ever used on ray directions, never on positions float vnoise(vec3 x){ vec3 p = floor(x), f = fract(x); f = f*f*(3.0-2.0*f); float n = p.x + p.y*57.0 + 113.0*p.z; return mix(mix(mix(hash11(n),hash11(n+1.0),f.x), mix(hash11(n+57.0),hash11(n+58.0),f.x), f.y), mix(mix(hash11(n+113.0),hash11(n+114.0),f.x), mix(hash11(n+170.0),hash11(n+171.0),f.x), f.y), f.z); } float fbm(vec3 p){ float a = 0.5, s = 0.0; for(int i=0;i<4;i++){ s += a*vnoise(p); p *= 2.03; a *= 0.5; } return s; } vec2 U(vec2 a, vec2 b){ return (a.x < b.x) ? a : b; } float sdBox(vec3 p, vec3 b){ vec3 d = abs(p) - b; return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); } float sdBox2(vec2 p, vec2 b){ vec2 d = abs(p) - b; return min(max(d.x,d.y),0.0) + length(max(d,0.0)); } float sdRBox2(vec2 p, vec2 b, float r){ return sdBox2(p, b - r) - r; } float sdCapsule(vec3 p, vec3 a, vec3 b, float r){ vec3 pa = p-a, ba = b-a; float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0); return length(pa - ba*h) - r; } float sdEllipsoid(vec3 p, vec3 r){ float k0 = length(p/r); return k0*(k0 - 1.0)/max(length(p/(r*r)), 1e-5); } float lineMask(float x, float period, float w){ float d = abs(fract(x/period - 0.5) - 0.5)*period; return smoothstep(w, 0.0, d); } float doorZ(float z){ return z - floor(z/L + 0.5)*L; } // ============================================================================ // THE DOOR - a thin emissive tube ring, nothing else // ============================================================================ float doorProfile(vec2 q){ return sdRBox2(q - vec2(0.0, PCY), vec2(PW, PHH), PCR); } vec2 mapDoor(vec3 p){ float ring = abs(doorProfile(p.xy)) - PTUBE; return vec2(max(ring, abs(doorZ(p.z)) - PTUBE), 30.0); } bool insideAperture(vec3 p){ return doorProfile(p.xy) < 0.0; } float doorRingDist(vec3 p){ float a = abs(doorProfile(p.xy)); float b = doorZ(p.z); return sqrt(a*a + b*b); } // ============================================================================ // WORLD A - TROPICAL LAGOON // mats: 1 water, 2 rock, 2.5 bark, 3 foliage, 3.4 understory, 3.8 vine // ============================================================================ float bankBase(float ax){ return -3.5 + smoothstep(9.0, 32.0, ax)*13.0; } // Two-tier heightfield: the coarse landform is always evaluated, the fine // detail only within a band of the surface. float lagoonH(vec3 p){ float ax = abs(p.x); float bank = smoothstep( 9.0, 32.0, ax); float dist = smoothstep(55.0, 150.0, ax); // distant ridges if(bank <= 0.0) return -3.5; // open channel floor vec3 q = vec3(p.x, 0.0, p.z); float n1 = fbmZ2(q, 0.025); float h = -3.5 + bank*(8.0 + 11.0*n1) + dist*(18.0 + 34.0*n1); float k = smoothstep(9.0, 4.0, abs(p.y - h)); if(k > 0.001){ float n2 = vnoiseZP(q*0.05, 6.0); float rg = 1.0 - abs(2.0*vnoiseZP(q*0.15, 18.0) - 1.0); float n3 = vnoiseZP(q*0.40, 48.0); h += k*(bank*((n2 - 0.5)*5.5 + rg*2.2) + (n3 - 0.5)*0.6); } return h; } vec2 mapTropic(vec3 p){ vec2 res = vec2(p.y, 1.0); // water plane if(p.y > 78.0) return U(res, vec2(p.y - 78.0, 2.0)); res = U(res, vec2((p.y - lagoonH(p))*0.42, 2.0)); // one lattice, five jobs: trunks, canopy, islets, understory, vines const vec2 CELL = vec2(20.0, 20.0); // 120/20 = 6 cells, tiles vec2 uv = vec2(p.x, p.z)/CELL; vec2 base = floor(uv); vec2 sg = sign(fract(uv) - 0.5); sg += 1.0 - abs(sg); // guard: never 0 float bestT = 1e5, bestF = 1e5, bestI = 1e5, bestU = 1e5, bestV = 1e5; for(int j=0;j<2;j++) for(int k=0;k<2;k++){ vec2 id = base + vec2(float(j)*sg.x, float(k)*sg.y); vec2 idw = vec2(id.x, mod(id.y, ZPER/CELL.y)); float r1 = hash21(idw*1.70 + 1.0); float r2 = hash21(idw*3.10 + 7.0); float r3 = hash21(idw*5.30 + 13.0); float r4 = hash21(idw*7.90 + 23.0); float cx = (id.x + 0.5)*CELL.x + (r1-0.5)*8.0; float cz = (id.y + 0.5)*CELL.y + (r2-0.5)*8.0; float ax = abs(cx); // --- palms on the bank if(r3 < 0.80 && ax > 14.0 && ax < 46.0){ vec3 c = vec3(cx, bankBase(ax) + 0.5, cz); float H = 5.0 + 6.0*r1; vec3 top = c + vec3((r2-0.5)*2.4, H, (r3-0.5)*2.4); bestT = min(bestT, sdCapsule(p, c - vec3(0.0,9.0,0.0), top, 0.26 + 0.11*r2)); bestF = min(bestF, length((p - top - vec3(0.0,1.2,0.0))*vec3(1.0,1.25,1.0)) - (2.1 + 1.5*r1)); bestF = min(bestF, length((p - c - vec3(1.8*(r2-0.5), 0.7, 1.8*(r1-0.5))) *vec3(1.0,1.6,1.0)) - (1.4 + 0.9*r3)); // --- vines off the canopy. thin, vertical, and they read hard // against a lit door behind them if(D2 && r1 > 0.45){ vec3 vt = top + vec3(1.5*(r3-0.5), 0.9, 1.5*(r4-0.5)); bestV = min(bestV, sdCapsule(p, vt, vt - vec3(0.25*(r2-0.5), 3.0 + 4.5*r2, 0.0), 0.070)); } } // --- understory: shrubs and fern clumps down at the waterline, which // is where v5 had a bare mud gap between water and trunks if(D1 && r2 < 0.80){ float sxp = cx + (r3 - 0.5)*9.0; float axs = abs(sxp); if(axs > 8.0 && axs < 34.0){ vec3 c2 = vec3(sxp, bankBase(axs) + 0.35 + 1.1*r4, cz + 8.0*(r4-0.5)); bestU = min(bestU, sdEllipsoid(p - c2, vec3(1.7 + 1.6*r2, 1.1 + 1.5*r1, 1.7 + 1.6*r4))); if(r1 > 0.55){ vec3 c3 = c2 + vec3(3.2*(r1-0.5), -0.35, 3.4*(r2-0.5)); bestU = min(bestU, sdEllipsoid(p - c3, vec3(1.2 + 1.0*r3, 0.85 + 0.9*r4, 1.2 + 1.0*r1))); } } } // --- rock islets standing in the water. these sweep past close to // the lens and are the strongest near-field parallax cue. if(r4 < 0.55 && ax > 7.0 && ax < 34.0){ vec3 ic = vec3(cx, -1.2 + 2.6*r4, cz + 6.0*(r3-0.5)); vec3 ir = vec3(1.4 + 2.6*r1, 1.6 + 3.4*r4, 1.4 + 2.6*r2); bestI = min(bestI, sdEllipsoid(p - ic, ir)); } } bestF -= (fbmZ2(p, 0.80) - 0.40)*0.65; bestI -= (fbmZ2(p, 0.40) - 0.45)*0.85; bestU -= (fbmZ2(p, 0.70) - 0.42)*0.95; res = U(res, vec2(bestT*0.85, 2.5)); res = U(res, vec2(bestF*0.62, 3.0)); res = U(res, vec2(bestI*0.60, 2.0)); res = U(res, vec2(bestU*0.58, 3.4)); res = U(res, vec2(bestV*0.85, 3.8)); return res; } // ============================================================================ // WORLD B - FLOODED NEON CITY // mats: 11 street, 12 tower, 13 sign, 14 skybridge, 15 mast, // 16 cable, 17 lamp head, 18 post, 19 pavement // ============================================================================ vec2 rowTowers(vec3 p, float xc, float cz, float seed, float sx, float bw, float hmax){ float best = 1e5, bestS = 1e5; float zf = p.z/cz + 0.5; float zi = floor(zf); float sgz = sign(fract(zf) - 0.5); sgz += 1.0 - abs(sgz); for(int k=0;k<2;k++){ float id = zi + float(k)*sgz; float idw = mod(id, ZPER/cz); float h1 = hash21(vec2(idw*1.13 + seed*7.7, sx*3.3 + seed)); float h2 = hash21(vec2(idw*2.71 + seed*3.1, sx*9.1 + seed*2.0)); float h3 = hash21(vec2(idw*4.37 + seed*5.3, sx*1.7 + seed*3.0)); float H = mix(12.0, hmax, h1); float dz = cz*0.5 - 1.2 - 1.8*h2; float dx = bw*(0.70 + 0.55*h2); vec3 q = p - vec3(xc + 2.2*h2, 0.0, id*cz); // shaft + crown best = min(best, sdBox(vec3(q.x, q.y - H*0.5, q.z), vec3(dx, H*0.5, dz))); best = min(best, sdBox(vec3(q.x, q.y - H - 2.8, q.z), vec3(dx*0.55, 2.8, dz*0.55))); if(D1){ // podium: the street-level mass. breaks the flat extrusion and // gives the water something to reflect other than a plain wall float ph = 2.6 + 3.4*h3; best = min(best, sdBox(vec3(q.x - dx*0.30, q.y - ph*0.5, q.z), vec3(dx*0.92, ph*0.5, dz*1.02))); // ledge slabs repeating up the shaft, clamped to the roofline float per = 6.0 + 3.0*h3; float nid = clamp(floor(q.y/per + 0.5), 1.0, max(floor((H - 2.0)/per), 1.0)); float ly = q.y - nid*per; best = min(best, sdBox(vec3(q.x, ly, q.z), vec3(dx + 0.34, 0.17, dz + 0.34))); // rooftop tank best = min(best, sdBox(vec3(q.x - dx*0.30, q.y - H - 6.4, q.z + dz*0.25), vec3(0.85, 0.85, 0.85))); } if(h2 > 0.55){ bestS = min(bestS, sdCapsule(q, vec3(0.0, H + 5.2, 0.0), vec3(0.0, H + 5.2 + 6.0*h1, 0.0), 0.17)); } } return U(vec2(best, 12.0), vec2(bestS, 15.0)); } // stripped-down version for the far rows - one box, no crown, no mast vec2 rowFar(vec3 p, float xc, float cz, float seed, float sx, float bw, float hmax){ float best = 1e5; float zf = p.z/cz + 0.5; float zi = floor(zf); float sgz = sign(fract(zf) - 0.5); sgz += 1.0 - abs(sgz); for(int k=0;k<2;k++){ float id = zi + float(k)*sgz; float idw = mod(id, ZPER/cz); float h1 = hash21(vec2(idw*1.13 + seed*7.7, sx*3.3 + seed)); float h2 = hash21(vec2(idw*2.71 + seed*3.1, sx*9.1 + seed*2.0)); float H = mix(16.0, hmax, h1); vec3 q = p - vec3(xc + 4.0*h2, 0.0, id*cz); best = min(best, sdBox(vec3(q.x, q.y - H*0.5, q.z), vec3(bw*(0.7 + 0.6*h2), H*0.5, cz*0.5 - 2.0 - 3.0*h2))); } return vec2(best, 12.0); } // Lantern cables. A sagging span across the street with beads of light // repeating along it. Period 12 (10 per loop), offset 6 so it never lands on // a door plane. The curve is extended flat past the span so it buries itself // inside the tower rows instead of ending in mid-air. vec2 cityCables(vec3 p, float px){ const float per = 12.0; float cid = floor((p.z - 6.0)/per + 0.5); float zo = p.z - (cid*per + 6.0); float h = hash21(vec2(mod(cid, 10.0), 4.7)); float h2 = hash21(vec2(mod(cid, 10.0), 8.3)); float y0 = 14.0 + 5.5*h; float sag = 2.2 + 2.0*h2; float u = clamp(px/12.5, 0.0, 1.0); float yc = y0 - sag*(1.0 - u*u); float cab = length(vec2(p.y - yc, zo)) - 0.075; float lx = px - floor(px/1.70 + 0.5)*1.70; float lan = length(vec3(lx, p.y - (yc - 0.42), zo)) - 0.195; return U(vec2(cab*0.70, 16.0), vec2(lan*0.80, 17.0)); } // Street furniture: lamp posts along the kerb, arms reaching over the road. vec2 cityPosts(vec3 p, float sx){ const float per = 8.0; // 120/8 = 15 cells float zf = p.z/per + 0.5; float zi = floor(zf); float sgz = sign(fract(zf) - 0.5); sgz += 1.0 - abs(sgz); float best = 1e5, bestL = 1e5; for(int k=0;k<2;k++){ float id = zi + float(k)*sgz; float idw = mod(id, 15.0); float h1 = hash21(vec2(idw*1.71, sx*2.3 + 1.0)); float h2 = hash21(vec2(idw*3.37, sx*5.7 + 2.0)); float h3 = hash21(vec2(idw*7.13, sx*9.1 + 3.0)); if(h1 < 0.42) continue; float cx = 9.9 + 1.7*h2; // standing on the pavement vec3 q = p - vec3(cx, 0.0, id*per + (h3 - 0.5)*3.0); float H = 4.2 + 2.6*h1; best = min(best, sdCapsule(q, vec3(0.0, -0.4, 0.0), vec3(0.0, H, 0.0), 0.115 + 0.05*h2)); // arm over the roadway + the lit head on the end of it vec3 hd = vec3(-1.10 - 0.85*h2, H - 0.22, 0.0); best = min(best, sdCapsule(q, vec3(0.0, H, 0.0), hd, 0.085)); bestL = min(bestL, sdEllipsoid(q - hd - vec3(0.0, 0.10, 0.0), vec3(0.32, 0.19, 0.24))*0.70); // kerbside bollard if(h2 > 0.55) best = min(best, sdCapsule(q + vec3(0.75, 0.0, 1.9), vec3(0.0, -0.3, 0.0), vec3(0.0, 0.85, 0.0), 0.11)); } return U(vec2(best*0.88, 18.0), vec2(bestL*0.88, 17.0)); } vec2 mapCity(vec3 p){ float px = abs(p.x); float sx = sign(p.x + 0.0001); vec3 pf = vec3(px, p.y, p.z); // ROAD SURFACE. Crowned down the centreline and dipping into the gutters // at the kerb, so rain has a low line to pool along. float ys = -0.11*smoothstep(5.4, 9.2, px); vec2 res = vec2((p.y - ys)*0.90, 11.0); if(p.y > 86.0) return U(res, vec2(p.y - 86.0, 12.0)); // raised pavement either side, kerb face included. 2D box extruded in z, // so this is exact and costs almost nothing. res = U(res, vec2(sdBox2(vec2(px - 11.6, p.y - 0.14), vec2(2.20, 0.15))*0.95, 19.0)); res = U(res, rowTowers(pf, 13.0, 15.0, 1.0, sx, 3.6, 34.0)); res = U(res, rowTowers(pf, 29.0, 20.0, 2.0, sx, 5.0, 44.0)); res = U(res, rowTowers(pf, 50.0, 24.0, 3.0, sx, 6.5, 52.0)); res = U(res, rowFar (pf, 78.0, 30.0, 4.0, sx, 9.0, 60.0)); res = U(res, rowFar (pf, 115.0, 40.0, 5.0, sx, 13.0, 72.0)); // skybridges: period 15, offset 7.5 - never lands on a door plane float bo = p.z - (floor((p.z - 7.5)/15.0 + 0.5)*15.0 + 7.5); res = U(res, vec2(sdBox(vec3(p.x, p.y - 18.0, bo), vec3(16.0, 0.9, 1.8)), 14.0)); // hanging signage: period 10, offset 4 float sid = floor((p.z - 4.0)/10.0 + 0.5); float so = p.z - (sid*10.0 + 4.0); float sh = 5.0 + 6.0*hash21(vec2(mod(sid, 12.0), sx*5.0)); res = U(res, vec2(sdBox(vec3(px - 9.5, p.y - sh, so), vec3(0.20, 2.2, 0.90)), 13.0)); if(D2) res = U(res, cityCables(pf, px)); if(D1) res = U(res, cityPosts(pf, sx)); return res; } // ---- dispatch ------------------------------------------------------------- vec2 mapWorld(vec3 p, float w){ return (w < 0.5) ? mapTropic(p) : mapCity(p); } vec2 mapAll (vec3 p, float w){ return U(mapDoor(p), mapWorld(p, w)); } vec3 calcNormal(vec3 p, float w){ vec2 e = vec2(1.0,-1.0)*0.0030; return normalize( e.xyy*mapAll(p+e.xyy,w).x + e.yyx*mapAll(p+e.yyx,w).x + e.yxy*mapAll(p+e.yxy,w).x + e.xxx*mapAll(p+e.xxx,w).x ); } float calcAO(vec3 p, vec3 n, float w){ float o = 0.0, sca = 1.0; for(int i=0;i<5;i++){ float h = 0.06 + 0.36*float(i); o += (h - mapAll(p + n*h, w).x)*sca; sca *= 0.70; } return clamp(1.0 - 0.80*o, 0.0, 1.0); } float softShadow(vec3 ro, vec3 rd, float w){ if(!doShadow) return 1.0; float res = 1.0, t = 0.30; for(int i=0;i<SHSTEPS;i++){ float h = mapWorld(ro + rd*t, w).x; res = min(res, 10.0*h/t); t += clamp(h, 0.30, 4.5); if(res < 0.02 || t > 65.0) break; } return clamp(res, 0.0, 1.0); } // ============================================================================ // SKIES // ============================================================================ vec3 skyTropic(vec3 rd){ float h = max(rd.y, 0.0); vec3 c = mix(vec3(1.20,0.55,0.32), vec3(0.09,0.21,0.50), pow(h,0.45)); float s = max(dot(rd, SUN), 0.0); c += vec3(3.00,1.70,0.80)*pow(s, 900.0); c += vec3(1.50,0.64,0.28)*pow(s, 7.0)*0.70; float iy = 1.0/max(rd.y, 0.075); vec3 cp = vec3(rd.x*iy, 0.0, rd.z*iy)*0.34 + vec3(0.7*sin(TAU*PH), 0.0, 0.0); float cl = fbm(cp); vec3 cc = mix(vec3(0.30,0.19,0.24), vec3(1.65,1.05,0.75), 0.35 + 0.6*pow(s,1.5)); c = mix(c, cc, smoothstep(0.40,0.88,cl)*smoothstep(0.0,0.17,rd.y)*0.88); // high cirrus, a second cloud deck so the sky has depth of its own if(D1){ vec3 hp = vec3(rd.x*iy, 0.0, rd.z*iy)*0.11 + vec3(1.4*sin(TAU*PH), 0.0, 0.0); float hc = fbm(hp*vec3(1.0,1.0,3.0)); c = mix(c, mix(vec3(0.85,0.62,0.58), vec3(1.45,1.02,0.80), pow(s,1.2)), smoothstep(0.52,0.86,hc)*smoothstep(0.05,0.35,rd.y)*0.40); } return c; } vec3 skyCity(vec3 rd){ float h = max(rd.y, 0.0); vec3 c = mix(vec3(0.11,0.045,0.16), vec3(0.012,0.016,0.042), pow(h,0.55)); c += vec3(0.62,0.13,0.52)*pow(1.0-h, 9.0)*0.9; c += vec3(0.06,0.30,0.52)*pow(1.0-h, 3.0)*0.30; c += vec3(0.35,0.10,0.30)*exp(-h*7.0)*0.55; float st = smoothstep(0.9986, 1.0, hash21(floor(rd.xz*430.0))); c += st*vec3(0.80,0.85,1.0)*smoothstep(0.15,0.6,rd.y); // low overcast catching the city glow from underneath if(D1){ float iy = 1.0/max(rd.y, 0.09); float cl = fbm(vec3(rd.x*iy, 0.0, rd.z*iy)*0.22 + vec3(0.5*sin(TAU*PH),0.0,0.0)); c = mix(c, vec3(0.16,0.055,0.19), smoothstep(0.42,0.85,cl) *smoothstep(0.0,0.30,rd.y)*0.75); } // far skyline behind the marched rows float ang = atan(rd.x, max(rd.z, 0.001)); float slope = rd.y/max(length(rd.xz), 0.001); float prof = 0.055 + 0.110*fbm(vec3(ang*7.0, 0.0, 0.0)); float inside = smoothstep(prof + 0.004, prof - 0.004, slope); vec3 far = vec3(0.024,0.019,0.044); far += step(0.80, hash21(floor(vec2(ang*260.0, slope*900.0))))*vec3(0.55,0.42,0.25)*0.5; return mix(c, far, inside); } vec3 skyFor(vec3 rd, float w){ return (w < 0.5) ? skyTropic(rd) : skyCity(rd); } // Aerial perspective. Cheap stand-in for the sky so distance desaturates and // shifts hue instead of washing to one flat colour. vec3 fogColor(vec3 rd, float w){ float up = smoothstep(-0.05, 0.40, rd.y); if(w < 0.5){ float s = max(dot(rd, SUN), 0.0); return mix(vec3(0.66,0.48,0.38), vec3(0.34,0.40,0.54), up) + vec3(0.95,0.48,0.22)*pow(s, 4.0)*0.55; } return mix(vec3(0.090,0.048,0.120), vec3(0.028,0.024,0.055), up) + vec3(0.30,0.09,0.28)*0.30; } vec3 applyFog(vec3 col, vec3 rd, float t, float w){ float den = (w < 0.5) ? fogTropic : fogCity; return mix(col, fogColor(rd, w), 1.0 - exp(-t*den)); } // ============================================================================ // PORTAL-AWARE MARCH // ============================================================================ vec3 trace(vec3 ro, vec3 rd, float w0, float tmax, int maxSteps, out vec3 vol, float volScale) { float t = 0.03, w = w0, mat = -1.0; vol = vec3(0.0); for(int i=0;i<STEPS;i++){ if(i >= maxSteps) break; vec3 p = ro + rd*t; vec2 h = mapAll(p, w); // hit resolves before any world toggle, so mat and w always agree if(h.x < 0.0014*t + 0.0015){ mat = h.y; break; } // floor the step first, then test the plane against the step actually // taken - otherwise small steps jump a plane without testing it float st = max(h.x*0.66, 0.014); if(abs(rd.z) > 0.001){ float zn = (rd.z > 0.0) ? (floor(p.z/L) + 1.0)*L : floor(p.z/L)*L; float dp = (zn - p.z)/rd.z; if(dp > 0.0 && dp < st){ st = dp + 0.004; if(insideAperture(ro + rd*(t + dp))) w = 1.0 - w; } } if(volScale > 0.0){ float tr = exp(-t*0.0075); if(w < 0.5){ float g = exp(-max(p.y,0.0)*0.13); float fs = pow(max(dot(rd,SUN),0.0), 6.0); vol += (vec3(0.30,0.27,0.28) + vec3(1.50,0.88,0.46)*fs) *g*st*0.0060*tr*volScale; }else{ float g = exp(-max(p.y,0.0)*0.19); vec3 c = mix(vec3(0.55,0.10,0.45), vec3(0.08,0.34,0.60), 0.5 + 0.5*sin(p.z*0.10472)); vol += c*g*st*0.0058*tr*volScale*(0.8 + 0.5*BASS); } // three-lobe door bloom: tight core, mid halo, wide wash. the wide // term is what lets far doors in the chain still read through haze. float dr = doorRingDist(p); vol += NEON*( 1.00/(1.0 + 6.0*dr*dr) + 0.22/(1.0 + 0.30*dr*dr) + 0.05/(1.0 + 0.02*dr*dr) ) *st*0.060*tr*volScale*(0.85 + 0.75*BASS); } t += st; if(t > tmax) break; } return vec3(t, mat, w); } // ============================================================================ // MATERIALS // ============================================================================ float wave(vec2 q){ float t = TAU*PH; return 0.055*( sin(q.x*2.10 + 1.5*sin(t)) + sin(q.y*2.7227 + 1.3*cos(t)) + 0.5*sin((q.x + q.y)*4.2935 + 2.0*sin(t)) ) + 0.020*sin(q.x*7.30 + q.y*5.1313 + 3.0*sin(t)); } // rain chop: integer multipliers on PH keep it loop-exact float ripple(vec2 q){ return 0.010*sin(q.x*9.10 + TAU*PH*40.0)*sin(q.y*8.30 - TAU*PH*33.0) + 0.006*sin((q.x - q.y)*15.7 + TAU*PH*57.0); } vec3 waterNormal(vec3 p, float w){ float e = 0.28; float r = (w > 0.5) ? rainAmt*0.9 : 0.0; float h0 = wave(p.xz) + r*ripple(p.xz); float hx = wave(p.xz + vec2(e,0.0)) + r*ripple(p.xz + vec2(e,0.0)); float hz = wave(p.xz + vec2(0.0,e)) + r*ripple(p.xz + vec2(0.0,e)); return normalize(vec3(-(hx - h0)/e, 1.0, -(hz - h0)/e)); } // Puddle field for the city street. Periodic in z via fbmZ2, biased toward // the gutters where the camber sends the water. float puddleMask(vec3 p){ float d = fbmZ2(vec3(p.x, 0.0, p.z), 0.40); float m = smoothstep(0.46, 0.62, d); m = max(m, smoothstep(6.2, 9.2, abs(p.x))*0.85); // gutter pooling return clamp(m*mix(0.35, 1.0, wetness), 0.0, 1.0); } // shallow standing water: rain rings only, no swell vec3 puddleNormal(vec3 p){ float e = 0.20; float a = 0.45 + 1.1*rainAmt; float h0 = a*ripple(p.xz) + 0.30*wave(p.xz*0.55); float hx = a*ripple(p.xz + vec2(e,0.0)) + 0.30*wave((p.xz + vec2(e,0.0))*0.55); float hz = a*ripple(p.xz + vec2(0.0,e)) + 0.30*wave((p.xz + vec2(0.0,e))*0.55); return normalize(vec3(-(hx - h0)/e, 1.0, -(hz - h0)/e)); } vec3 doorLight(vec3 p, vec3 n, vec3 alb){ vec3 lv = vec3(0.0, PCY, floor(p.z/L + 0.5)*L) - p; float d = length(lv); return alb*NEON*max(dot(n, lv/max(d,0.001)), 0.0) / (1.0 + 0.045*d*d)*(16.0 + 11.0*BASS); } vec3 shadeTropic(vec3 p, vec3 n, vec3 rd, float mat){ vec3 alb; bool water = (mat < 1.5); bool leafy = (mat > 2.7); float foam = 0.0; if(water){ // depth shading off the actual channel floor: shallows read turquoise, // the channel reads deep, and the crossing point foams float gh = lagoonH(vec3(p.x, 0.0, p.z)); float dep = clamp(-gh, 0.0, 9.0); alb = mix(vec3(0.060,0.210,0.195), vec3(0.011,0.033,0.038), smoothstep(0.35, 4.5, dep)); float fn = fbmZ2(vec3(p.x, 0.0, p.z), 0.90); foam = smoothstep(1.00, 0.02, abs(gh))*smoothstep(0.30, 0.72, fn); if(D3){ // lily-pad rafts, surface only vec2 lc = vec2(p.x, mod(p.z, ZPER))/2.40; vec2 lid = floor(lc); vec2 lf = fract(lc) - 0.5; vec2 jit = (vec2(hash21(lid*2.13 + 1.0), hash21(lid*3.71 + 5.0)) - 0.5)*0.42; float pad = smoothstep(0.36, 0.25, length(lf - jit)); pad *= step(0.58, hash21(lid*1.31 + 9.0)); pad *= smoothstep(5.0, 11.0, abs(p.x))*smoothstep(31.0, 21.0, abs(p.x)); alb = mix(alb, mix(vec3(0.055,0.150,0.045), vec3(0.11,0.24,0.06), hash21(lid*5.9)), pad*0.92); n = normalize(mix(n, vec3(0.0,1.0,0.0), pad*0.85)); water = (pad < 0.5); } } else if(mat < 2.7){ // rock / bark float m = fbmZ2(p, 0.40); vec3 dry = mix(vec3(0.21,0.16,0.12), vec3(0.36,0.30,0.23), m); vec3 wet = mix(vec3(0.06,0.05,0.04), vec3(0.12,0.10,0.08), m); alb = mix(wet, dry, smoothstep(0.05, 2.4, p.y)); if(D1){ // stratification: horizontal banding so the rock stops reading as // one noise blob and gains a sense of scale float band = 0.5 + 0.5*sin(p.y*1.55 + fbmZ2(p, 0.10)*7.0); alb *= 0.80 + 0.40*smoothstep(0.35, 0.75, band); alb += vec3(0.05,0.04,0.03)*smoothstep(0.80, 0.98, band); } alb = mix(alb, vec3(0.62,0.60,0.55), smoothstep(0.60, 0.0, abs(p.y - 0.10))*smoothstep(0.35,0.70,m)*0.55); float moss = smoothstep(0.50,0.92,n.y)*smoothstep(0.40,0.72,fbmZ2(p,0.20)) * smoothstep(0.90, 3.5, p.y); alb = mix(alb, mix(vec3(0.06,0.14,0.045), vec3(0.15,0.29,0.075), m), moss*0.85); } else if(mat < 3.6){ // foliage + shrubs float v = fbmZ2(p, 0.80); alb = mix(vec3(0.05,0.16,0.04), vec3(0.20,0.42,0.09), v); alb = mix(alb, vec3(0.30,0.36,0.10), smoothstep(0.62,0.95,v)*0.45); if(mat > 3.2){ // understory is alb *= 0.72; // darker, cooler alb = mix(alb, vec3(0.09,0.20,0.10), 0.35); } } else{ // vine alb = mix(vec3(0.10,0.14,0.05), vec3(0.19,0.26,0.08), fbmZ2(p, 0.90)); } float sh = softShadow(p + n*0.09, SUN, 0.0); float dif = max(dot(n, SUN), 0.0); vec3 sky = skyTropic(vec3(0.0,1.0,0.0)); vec3 col = alb*dif*sh*vec3(2.60,1.80,1.18); col += alb*sky*(0.30 + 0.42*max(n.y,0.0)); col += alb*vec3(0.16,0.13,0.09)*max(-n.y,0.0)*0.5; if(leafy) col += alb*max(dot(-n,SUN),0.0)*sh*vec3(1.20,1.50,0.48)*0.85; if(water){ vec3 hv = normalize(SUN - rd); col += vec3(2.8,2.1,1.5)*pow(max(dot(n,hv),0.0), 260.0)*(0.6 + 1.4*HIGH); col += vec3(0.55,0.62,0.58)*foam*(0.9 + 0.5*HIGH); // shoreline foam } return col + doorLight(p, n, alb); } vec3 shadeCity(vec3 p, vec3 n, vec3 rd, float mat){ vec3 alb = vec3(0.05), emi = vec3(0.0); bool street = (mat < 11.5); float pud = 0.0; if(street){ // --- asphalt float px2 = abs(p.x); float zz = mod(p.z, ZPER); float grit = fbmZ2(vec3(p.x, 0.0, p.z), 0.90); float wear = fbmZ2(vec3(p.x, 0.0, p.z), 0.20); alb = mix(vec3(0.028,0.028,0.032), vec3(0.058,0.056,0.060), grit); alb *= 0.80 + 0.35*wear; // patching and repairs // --- lane paint. dashes are period 4 (30 per loop), so they tile float dash = step(0.5, fract(zz/4.0)); float ctr = smoothstep(0.14, 0.09, abs(px2 - 0.24)); float lane = smoothstep(0.12, 0.07, abs(px2 - 4.60))*dash; float edge = smoothstep(0.13, 0.08, abs(px2 - 8.60)); float paint = clamp(ctr + lane + edge, 0.0, 1.0)*(0.55 + 0.45*grit); alb = mix(alb, vec3(0.36,0.33,0.20), paint*0.90); // --- crosswalk under every cable span (period 12, offset 6) float ccid = floor((p.z - 6.0)/12.0 + 0.5); float czo = p.z - (ccid*12.0 + 6.0); float cw = smoothstep(2.05, 1.60, abs(czo)) * step(0.5, fract(px2/1.30)) * step(px2, 8.9); alb = mix(alb, vec3(0.40,0.39,0.35), cw*0.80*(0.6 + 0.4*grit)); // --- manhole covers and gutter drains vec2 mid = floor(vec2(p.x, zz)/9.0); vec2 mc = (mid + 0.5)*9.0 + (vec2(hash21(mid*1.7), hash21(mid*3.3)) - 0.5)*5.0; float man = smoothstep(0.62, 0.50, length(vec2(p.x, zz) - mc)); alb = mix(alb, vec3(0.075,0.068,0.058)*(0.7 + 0.6*grit), man); float drain = smoothstep(0.35, 0.20, abs(px2 - 8.95)) * step(0.55, fract(zz/16.0))*step(0.5, fract(zz/1.6)); alb *= 1.0 - 0.55*drain; // --- wet. puddles darken the asphalt and go specular in render() pud = puddleMask(p); alb *= 1.0 - 0.62*pud; alb *= 1.0 - 0.22*wetness; // damp sheen everywhere } else if(mat < 12.5){ // tower alb = vec3(0.045,0.048,0.060) * (0.7 + 0.6*hash21(floor(vec2(p.x, mod(p.z, ZPER))*0.10))); float vert = 1.0 - abs(n.y); float u = (abs(n.x) > 0.5) ? mod(p.z, ZPER) : p.x; vec2 cid = vec2(floor(u/2.0), floor(p.y/2.30)); vec2 fq = vec2(fract(u/2.0), fract(p.y/2.30)); float pane = smoothstep(0.10,0.18,fq.x)*smoothstep(0.90,0.82,fq.x) * smoothstep(0.15,0.24,fq.y)*smoothstep(0.86,0.78,fq.y); float on = step(0.46, hash21(cid + floor(p.x*0.02))); float flk = step(0.985, hash21(cid + floor(PH*8.0))); vec3 wc = mix(vec3(1.00,0.72,0.34), vec3(0.35,0.85,1.35), hash21(cid*1.7 + 3.0)); emi += vert*pane*on*(1.0-flk)*wc*(1.7 + 1.3*MIDS); float spine = lineMask(u, 10.0, 0.10)*vert; emi += spine*mix(vec3(1.4,0.15,0.7), vec3(0.15,1.0,1.4), step(0.5, hash21(floor(vec2(u/10.0, 0.0)))))*1.7; if(D1){ // strip lighting tucked under every ledge slab float led = lineMask(p.y - 0.35, 6.8, 0.09); emi += led*vert*vec3(0.35,0.80,1.20)*1.15; // top-of-podium wash where the street light pools emi += vert*smoothstep(6.5, 0.0, p.y)*vec3(0.90,0.35,0.55)*0.55; } if(D3){ // blocky sign panels bolted to the facade vec2 sid2 = vec2(floor(u/7.0), floor(p.y/9.0)); float sgn = step(0.72, hash21(sid2*3.17 + 11.0)); vec2 sf = vec2(fract(u/7.0), fract(p.y/9.0)); float box = smoothstep(0.26,0.32,sf.x)*smoothstep(0.74,0.68,sf.x) * smoothstep(0.30,0.36,sf.y)*smoothstep(0.70,0.64,sf.y); float glyph = step(0.45, hash21(floor(vec2(u*4.0, p.y*4.0)) + floor(PH*6.0)*0.0)); vec3 sc2 = mix(vec3(1.60,0.20,0.55), vec3(0.20,1.10,1.45), hash21(sid2*7.7 + 2.0)); emi += vert*sgn*box*glyph*sc2*(1.4 + 1.6*MIDS); // scrolling ticker band, loop-locked (integer cycles per loop) float tb = smoothstep(0.45, 0.0, abs(p.y - 8.6)); float tk = step(0.55, hash21(floor(vec2(u*3.0 - PH*120.0, 0.0)))); emi += vert*tb*tk*vec3(1.30,0.55,0.10)*1.5; } emi += vert*exp(-max(p.y,0.0)*0.36)*vec3(0.55,0.12,0.45)*0.85; } else if(mat < 13.5){ // signage vec3 sc = mix(vec3(1.7,0.15,0.70), vec3(0.15,1.15,1.60), step(0.5, fract(p.z*0.05))); emi += sc*(2.8 + 2.2*BASS); alb = vec3(0.02); } else if(mat < 14.5){ // skybridge alb = vec3(0.06,0.065,0.08); emi += smoothstep(0.38,0.0,abs(p.y - 17.55))*vec3(0.25,0.9,1.3)*1.6; } else if(mat < 15.5){ // mast + beacon alb = vec3(0.05,0.05,0.06); emi += vec3(1.6,0.15,0.12)*smoothstep(0.4,1.0,sin(TAU*PH*4.0))*1.4; } else if(mat < 16.5){ // cable alb = vec3(0.03,0.03,0.035); } else if(mat < 17.5){ // lantern / lamp float f = hash21(vec2(floor(abs(p.x)/1.7), floor(mod(p.z, ZPER)))); vec3 lc2 = mix(vec3(1.70,0.62,0.20), vec3(1.30,0.25,0.55), f); emi += lc2*(2.6 + 2.0*BASS)*(0.85 + 0.15*sin(TAU*PH*6.0 + f*9.0)); alb = vec3(0.04); } else if(mat < 18.5){ // lamp post, bollard alb = vec3(0.050,0.048,0.052)*(0.7 + 0.6*fbmZ2(p, 0.60)); alb = mix(alb*0.55, alb, smoothstep(0.0, 1.4, p.y)); // road grime } else{ // pavement alb = vec3(0.078,0.076,0.082)*(0.72 + 0.55*fbmZ2(p, 0.90)); // paving joints, 1.6 grid, z folded so it tiles float j = max(lineMask(p.x, 1.6, 0.045), lineMask(mod(p.z, ZPER), 1.6, 0.045)); alb *= 1.0 - 0.42*j; // kerb face catches the road lighting harder than the top does alb *= 0.80 + 0.45*(1.0 - abs(n.y)); alb *= 1.0 - 0.18*wetness; } vec3 col = alb*vec3(0.045,0.035,0.085); for(int i=0;i<2;i++){ float side = (i==0) ? 1.0 : -1.0; vec3 lv = vec3(side*8.9, 5.1, p.z) - p; float d = length(lv); vec3 lc = (i==0) ? vec3(1.45,0.20,0.80) : vec3(0.15,0.85,1.50); col += alb*lc*max(dot(n, lv/max(d,0.001)),0.0)/(1.0 + 0.09*d*d)*(11.0 + 7.0*BASS); } col += alb*skyCity(vec3(0.0,1.0,0.0))*max(n.y,0.0)*0.55; if(street){ // sheen: sharp where there is standing water, broad and dull on damp // asphalt. this is what sells "wet road" even before the reflection. vec3 hv = normalize(vec3(0.0,1.0,0.2) - rd); float sp = max(dot(n,hv), 0.0); col += vec3(0.55,0.30,0.65)*pow(sp, 90.0)*(0.25 + 0.85*pud); col += vec3(0.30,0.22,0.38)*pow(sp, 12.0)*0.12*wetness; // rain hitting the road throws a fine sparkle col += vec3(0.45,0.38,0.60)*rainAmt * pow(max(dot(n, normalize(vec3(0.0,1.0,0.0) - rd)),0.0), 30.0)*0.18; } return col + emi + doorLight(p, n, alb); } vec3 shadeDoor(vec3 p){ float prof = doorProfile(p.xy); float core = smoothstep(PTUBE, 0.0, abs(prof)); float puls = 0.90 + 0.18*sin(TAU*PH*8.0 + p.y*2.2) + 1.1*BASS; // chase segments running the perimeter - free frame detail, no geometry. // 12 cycles per loop keeps it seamless. float s = (p.x + p.y)*1.30 - PH*12.0*TAU*0.16; float chase = 0.55 + 0.45*sin(s*TAU); puls *= mix(1.0, 0.55 + 0.85*chase, 0.45); return mix(NEON*3.4, vec3(2.7,2.3,2.3), core*0.75)*puls; } vec3 shadeHit(vec3 p, vec3 n, vec3 rd, float mat, float w){ if(mat > 29.5) return shadeDoor(p); return (w < 0.5) ? shadeTropic(p, n, rd, mat) : shadeCity(p, n, rd, mat); } // ============================================================================ // RENDER // ============================================================================ vec3 render(vec3 ro, vec3 rd, float w0, int steps, int rsteps){ vec3 vol; vec3 hit = trace(ro, rd, w0, 520.0, steps, vol, volAmt); float t = hit.x, mat = hit.y, w = hit.z; WOUT = w; TOUT = (mat < 0.0) ? 1e5 : t; // depth for the particle field vec3 col; if(mat < 0.0){ col = skyFor(rd, w); }else{ vec3 p = ro + rd*t; vec3 n = calcNormal(p, w); // lagoon water is a full mirror surface; the city street only mirrors // where puddles have collected bool isWater = (mat < 1.5); bool isStreet = (mat > 10.5 && mat < 11.5); float pud = isStreet ? puddleMask(p) : 0.0; if(isWater) n = normalize(mix(n, waterNormal(p, w), 0.88)); else if(pud > 0.01) n = normalize(mix(n, puddleNormal(p), 0.80*pud)); if(mat > 29.5) col = shadeDoor(p); else col = shadeHit(p, n, rd, mat, w)*calcAO(p, n, w); if(doReflect && (isWater || pud > 0.03)){ float k = 0.14 + 0.86*pow(1.0 - max(dot(n, -rd), 0.0), 4.0); if(isStreet) k *= pud*0.92; vec3 rvol, rr = reflect(rd, n); vec3 rh = trace(p + n*0.06, rr, w, 300.0, rsteps, rvol, volAmt*0.75); vec3 rc; if(rh.y < 0.0) rc = skyFor(rr, rh.z); else{ vec3 p2 = p + n*0.06 + rr*rh.x; rc = (rh.y > 29.5) ? shadeDoor(p2) : shadeHit(p2, calcNormal(p2, rh.z), rr, rh.y, rh.z); rc = applyFog(rc, rr, rh.x, rh.z); // reflections fade too } col = mix(col, rc + rvol, clamp(k, 0.0, 1.0)); } col = applyFog(col, rd, t, w); } return col + vol; } vec3 aces(vec3 x){ return clamp((x*(2.51*x + 0.03))/(x*(2.43*x + 0.59) + 0.14), 0.0, 1.0); } // ---- screen-space weather / air ------------------------------------------ // Both are locked to PH with integer cycle counts so they survive the loop. float rainLayer(vec2 q, float sc, float cyc, float seed){ vec2 g = vec2(q.x*sc, q.y*sc*0.16 - PH*cyc); vec2 id = floor(g); vec2 f = fract(g) - 0.5; float h = hash21(id + seed); float streak = smoothstep(0.5, 0.0, abs(f.x - (h - 0.5)*0.55)*11.0) * smoothstep(0.5, 0.0, abs(f.y)*1.25); return streak*step(0.62, h); } float moteLayer(vec2 q, float sc, float cyc, float seed){ vec2 g = vec2(q.x*sc + sin(PH*TAU + seed)*0.35, q.y*sc - PH*cyc); vec2 id = floor(g); vec2 f = fract(g) - 0.5; float h = hash21(id + seed); return smoothstep(0.16, 0.0, length(f - (vec2(h, fract(h*7.3)) - 0.5)*0.6)) * step(0.78, h); } // ============================================================================ // FLY-BY PARTICLE FIELD // --------------------------------------------------------------------------- // World-locked points on planes spaced PSPACE apart in z. For each plane we // solve the ray/plane intersection analytically, drop into a lateral cell // grid and draw one soft point per cell. Because the points are anchored in // world space and the camera is the thing moving, they stream outward from // the vanishing point and blow past the lens with correct parallax - no // velocity term, no simulation, no extra buffer. // // Loop safety: ZPER/PSPACE = 40, so the cell hash wraps exactly with the // 16s loop. Bob and sway use integer cycle counts on PH. // Occlusion: every point is depth-tested against hitT, so geometry hides // them and an open doorway does not. // ============================================================================ vec3 particleField(vec3 ro, vec3 rd, float hitT, float pxScale){ if(partAmt < 0.001 || rd.z < 0.05) return vec3(0.0); vec3 acc = vec3(0.0); float z0 = (floor(ro.z/PSPACE) + 1.0)*PSPACE; // first plane ahead of us float cut = 1.0 - partDens; // density thinning for(int i=0;i<PSLICES;i++){ float zw = z0 + float(i)*PSPACE; float t0 = (zw - ro.z)/rd.z; if(t0 > hitT) break; // planes only recede vec2 gp = (ro.xy + rd.xy*t0)/PCELL; vec2 id = floor(gp); // wrapped plane index - this is what makes the field loop float sid = mod(floor(zw/PSPACE), ZPER/PSPACE); float h1 = hash21(id*1.31 + vec2(sid*0.71, sid*1.93) + 3.0); if(h1 < cut) continue; float h2 = hash21(id*2.17 + vec2(sid*1.37, sid*0.53) + 11.0); float h3 = hash21(id*3.71 + vec2(sid*0.29, sid*2.11) + 23.0); // scatter the point in z inside its slab, so points cross the lens // continuously instead of arriving in flat waves float t = t0 + (h3 - 0.5)*PSPACE*0.36/max(rd.z, 0.25); if(t < 0.35 || t > hitT) continue; // cell-local coords at the jittered depth vec2 pf = (ro.xy + rd.xy*t)/PCELL - id; vec2 c = vec2(0.30 + 0.40*h2, 0.30 + 0.40*fract(h2*7.13)); c += partDrift*vec2(0.10*sin(TAU*(PH*2.0 + h1)), 0.12*sin(TAU*(PH*3.0 + h2))); // loop-exact bob vec2 d = (pf - c)*PCELL; // streak along the on-screen travel direction, which for forward // flight is radially away from the vanishing point vec2 dir = normalize(rd.xy*t + vec2(1e-4, 0.0)); float al = dot(d, dir); float pe = dot(d, vec2(-dir.y, dir.x)); float sk = 1.0 + partStreak*3.5; float r = length(vec2(al/sk, pe)); float core = (0.020 + 0.045*h2)*partSize; float rr = max(core, t*pxScale*1.15); // never thinner than a pixel float s = 1.0 - smoothstep(0.0, rr, r); s += 0.30*exp(-(r*r)/(rr*rr*9.0)); // soft halo // colour by the world this plane sits in, so points beyond a doorway // already wear the next world's palette float w = mod(floor(zw/L), 2.0); vec3 pc = (w < 0.5) ? vec3(1.00,0.80,0.42)*(0.70 + 0.90*HIGH) // pollen : vec3(0.55,0.78,1.25)*(0.70 + 0.90*MIDS); // spray float dz = doorZ(zw); pc = mix(pc, NEON*1.4, exp(-dz*dz*0.02)*0.75); // gate light spill float fade = smoothstep(0.5, 3.0, t) // no blobs on the lens * exp(-t*((w < 0.5) ? fogTropic : fogCity)*1.6) * (0.55 + 0.65*hash21(id + sid)); acc += pc*s*fade; } return acc*partAmt*0.30; } // ============================================================================ // MAIN // ============================================================================ void main(){ vec2 fragCoord = gl_FragCoord.xy; PH = fract(TIME*speed/LOOPT + phaseOffset); D1 = detail > 0.05; D2 = detail > 0.40; D3 = detail > 0.72; int steps = int(float(STEPS)*quality); int rsteps = int(float(RSTEPS)*quality); NEON = hsv2rgb(vec3(neonHue, neonSat, 1.0))*1.6*neonGain; float ca = sunAz*1.4; SUN = normalize(vec3(sin(ca)*0.97, sunEl, cos(ca)*0.97)); // ---- audio ------------------------------------------------------------ // No FFT texture: not every ISF host declares an "audioFFT" uniform, and // sampling one that was never emitted is a compile error. bassIn / midIn / // highIn are plain floats - drive them from the host's audio analysis, // MIDI, OSC, or an LFO. audioMix crossfades between the internal loop- // locked LFO (0) and those inputs (1). // // On a host that DOES support audioFFT, add this input: // { "NAME": "fft", "TYPE": "audioFFT", "MAX": 64 } // and read a band with: // IMG_NORM_PIXEL(fft, vec2(0.03, 0.5)).r vec3 lfo = vec3(0.35 + 0.35*sin(TAU*PH*4.0), 0.30 + 0.30*sin(TAU*PH*3.0 + 1.7), 0.25 + 0.25*sin(TAU*PH*6.0 + 0.4)); vec3 ext = vec3(bassIn, midIn, highIn); vec3 lvl = mix(lfo, ext, audioMix)*vec3(bassGain, midGain, highGain); BASS = clamp(lvl.x, 0.0, 2.0); MIDS = clamp(lvl.y, 0.0, 2.0); HIGH = clamp(lvl.z, 0.0, 2.0); // ---- forward motion --------------------------------------------------- // g maps 0..1 onto exactly one loop, but its derivative PEAKS at PH = 0.25 // and 0.75 - where the doors are. So the camera lingers in each world and // accelerates through the doorway. const float EASE = 0.45; float g = PH + EASE*sin(TAU*2.0*(PH - 0.25))/(TAU*2.0); float cz = ZPER*g + 30.0; // starts mid-room float dz = doorZ(cz); // all camera character damps to zero near a door, so every transit is // dead-centre through the aperture float open = smoothstep(0.0, 10.0, abs(dz))*sway; vec3 ro = vec3(0.70*sin(TAU*PH*2.0)*open, camHeight + 0.28*sin(TAU*PH*3.0)*open, cz); vec3 ta = ro + vec3(0.85*sin(TAU*PH*2.0 + 1.0)*open, 0.55*sin(TAU*PH)*open + 0.55, 4.0); vec2 uv = (fragCoord - 0.5*RENDERSIZE.xy)/RENDERSIZE.y; float rl = 0.050*sin(TAU*PH*2.0)*open; uv = mat2(cos(rl), -sin(rl), sin(rl), cos(rl))*uv; vec3 fw = normalize(ta - ro); vec3 rt = normalize(cross(vec3(0.0,1.0,0.0), fw)); vec3 up = cross(fw, rt); // wide lens for perspective, tightening hard at each transit. the tighten // also pushes the frame edges off screen sooner, keeping the swap seamless float lens = lensBase + lensPunch*exp(-0.06*dz*dz); vec3 rd = normalize(uv.x*rt + uv.y*up + lens*fw); float w0 = mod(floor(cz/L), 2.0); vec3 col = render(ro, rd, w0, steps, rsteps); // ---- fly-by particles, depth-tested against the primary hit ----------- // pxScale is the world size of one pixel per unit of distance, so points // stay at least a pixel wide instead of aliasing into fireflies far off. col += particleField(ro, rd, TOUT, 1.0/(lens*RENDERSIZE.y)); // ---- weather / air, masked by the world the ray actually ended in ------ if(WOUT > 0.5){ if(rainAmt > 0.001){ float r = rainLayer(uv, 42.0, 26.0, 1.0)*0.60 + rainLayer(uv, 76.0, 41.0, 7.0)*0.35 + rainLayer(uv, 22.0, 17.0, 3.0)*0.30; col += vec3(0.55,0.60,0.85)*r*rainAmt*0.16; } }else{ if(moteAmt > 0.001){ float m = moteLayer(uv, 26.0, 3.0, 2.0)*0.7 + moteLayer(uv, 44.0, 5.0, 9.0)*0.4; col += vec3(1.00,0.82,0.52)*m*moteAmt*0.22*(0.6 + 0.8*HIGH); } } // transit flash col += NEON*exp(-2.0*dz*dz)*(0.09 + 0.26*BASS)*portalFlash; col = aces(col*exposure); col = pow(col, vec3(gammaAmt)); col *= 1.0 - vignette*dot(uv,uv); col += (hash21(fragCoord + PH*137.0) - 0.5)*grain; gl_FragColor = vec4(col, 1.0); }
forking #d621g6zo — 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.