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": "SUBURB. An endless residential street, generated rather than modelled: no level file and no mesh, every lot decided by hashing an integer index, so the neighbourhood is unbounded and identical for everyone who runs it. The street itself is the same trick the backrooms uses on its corridors - a 2D CROSS-SECTION swept along a curve. Road crown, gutter, curb, verge, sidewalk and lawn are one plane figure in (lateral, height) extruded down z, so the entire streetscape costs six box tests no matter how far it runs; the road bends because the lateral coordinate is measured from a wandering centreline rather than from the axis, and the field is divided by sqrt(1 + slope^2) afterwards because shearing a distance field is not an isometry and an uncorrected shear overshoots exactly where the street turns. Houses are the part that genuinely cannot be 2D, so they are bounded instead: each lot is tested against its bounding box first and only opened up when the sample is within half a metre of it, which is what keeps six lots in flight at the price of six box tests and one house. A house is a main mass, a gable whose ridge runs to the street or along it, a garage wing, a porch roof and sometimes a chimney - and the gable is built as the intersection of two half-spaces rather than as a rotated box, because the intersection is convex and stays a usable field where a rotation would not. Windows are never geometry. They are drawn on the facade from the lot's own hashes, recomputed once per shaded pixel rather than threaded out through two hundred marcher steps, and lit individually - which is the whole night-time image, because a dark house with three warm rectangles in it reads as occupied and an emissive box does not. A few of them are a television instead: colder, and flickering on a beat. Time of day is one continuous control rather than three presets, and it moves five things at once - the key light's elevation and colour, the sky gradient, whether the lamps have struck, how many windows are lit, and the fog, which is the one that matters most, since a suburban street at night is not a dark version of the same street but a completely different scene made almost entirely of scattered light. THE LAMPS ARE THE SUBJECT AT NIGHT. Each is gathered as a real fixture with an inverse-square falloff and a downward bias, three at a time with a window that fades each to nothing before the set changes, because a lamp that vanishes at a set boundary paints a hard plane across the road. The cone of light in the air is integrated along the ray rather than smeared on afterwards, so it sits correctly in front of and behind geometry, and it is thickest inside a drifting ground-mist layer that sits below head height, which is what puts the light on the road rather than in the sky. There is also an optional CEILING: a fluorescent deck a few metres over the rooftops, strips running across the street on their own lattice, at which point the sky is never seen again and the whole neighbourhood is indoors. A strip is a line light, so it falls off as one over distance rather than one over distance squared - that single exponent is the difference between a room lit by tubes and a street lit by lamps. Wet asphalt reflects analytically: the reflected ray samples only the sky and the lamp field, never the geometry, because on a horizontal mirror at grazing incidence what you see is almost entirely the lamps anyway, and the specular lobe is stretched vertically so they smear into columns instead of dots. The camera's own optics get a diagonal anamorphic streak off each lamp, projected from the lamp's world position rather than found in the frame, which is the artefact in every phone photograph of an empty street at night. THE TREES ARE BUILT OUTLINE FIRST, because at forty metres in fog the outline is the only thing left of them. A spruce is a stack of drooping skirts and not a cone - one whorl of branches per year, each overhanging the one below - and folding the height into tiers buys every whorl for the price of one, with two tiers evaluated rather than one because a fold makes the field jump where the index changes and a marcher walks through a jump. A broadleaf is three lumpy masses wider than they are tall, edge-broken at two scales, on a crown lifted clear of the ground so you can see under it and down the street: a canopy that reaches the grass turns the row into a hedge and takes the depth of the frame with it. A new street is planted with staked saplings instead, which are bare, so there is nowhere to hide and the branching itself has to carry them - a whip, five whips off it, a stake and a tie. None of the three stands perfectly vertical, because a row that does reads as instanced however good each tree is. The verge tree also dodges the driveway, which costs one sign flip and removes the loudest tell that a street was generated; a second, larger rank sits back in the lawns, because trees all at one distance from the kerb give the row no depth. Foliage is lit as a translucent thing rather than an opaque one, since a canopy with a streetlamp behind it is the brightest object on the street and not a black cutout. Forty-six controls: walk, lens, aim, camera height and how far across the road it stands, street curve, lot frontage, road width, setback, house density, second storeys, roof pitch, garage frequency, trees and the mix of spruce, broadleaf and staked sapling, parked cars, driveways, time of day, sun azimuth, lamp spacing, lamp colour from sodium to LED, working lamps, brightness, lit windows, televisions, ceiling height and its strips, fog, mist, haze, wetness, moon, siding variety, grass, grime, ambient occlusion, shadow, exposure, and five camcorder controls.", "CREDIT": "CC0. Section sweeping, footprint filtering, the windowed light gather and the in-scatter integration follow the CC0 BACKROOMS shader.", "CATEGORIES": ["generator", "raymarch", "liminal", "suburbia", "backrooms", "night", "architecture", "procedural", "3d"], "INPUTS": [ { "NAME": "uSpeed", "TYPE": "float", "DEFAULT": 2.2, "MIN": 0.0, "MAX": 6.0 }, { "NAME": "uLens", "TYPE": "float", "DEFAULT": 1.45, "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.02, "MIN": -0.6, "MAX": 0.6 }, { "NAME": "uBob", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uEye", "TYPE": "float", "DEFAULT": 1.62, "MIN": 0.4, "MAX": 3.0 }, { "NAME": "uSide", "TYPE": "float", "DEFAULT": 0.35, "MIN": -1.0, "MAX": 1.0 }, { "NAME": "uCurve", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uLot", "TYPE": "float", "DEFAULT": 13.0, "MIN": 8.0, "MAX": 26.0 }, { "NAME": "uRoadW", "TYPE": "float", "DEFAULT": 7.2, "MIN": 4.0, "MAX": 14.0 }, { "NAME": "uSetback", "TYPE": "float", "DEFAULT": 7.0, "MIN": 2.0, "MAX": 16.0 }, { "NAME": "uDensity", "TYPE": "float", "DEFAULT": 0.88, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uStories", "TYPE": "float", "DEFAULT": 0.45, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uPitchR", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.15, "MAX": 1.0 }, { "NAME": "uGarage", "TYPE": "float", "DEFAULT": 0.75, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uTrees", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uConifer", "TYPE": "float", "DEFAULT": 0.30, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uSapling", "TYPE": "float", "DEFAULT": 0.35, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uCars", "TYPE": "float", "DEFAULT": 0.40, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uDrives", "TYPE": "float", "DEFAULT": 0.85, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uTOD", "TYPE": "float", "DEFAULT": 2.0, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uSunAz", "TYPE": "float", "DEFAULT": 2.3, "MIN": 0.0, "MAX": 6.283 }, { "NAME": "uLampSpace", "TYPE": "float", "DEFAULT": 34.0, "MIN": 12.0, "MAX": 90.0 }, { "NAME": "uLampWarm", "TYPE": "float", "DEFAULT": 0.35, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uLampOn", "TYPE": "float", "DEFAULT": 0.92, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uLampBright","TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 3.0 }, { "NAME": "uWindows", "TYPE": "float", "DEFAULT": 0.30, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uTV", "TYPE": "float", "DEFAULT": 0.25, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uCeiling", "TYPE": "float", "DEFAULT": 0.0, "MIN": 0.0, "MAX": 26.0 }, { "NAME": "uStrip", "TYPE": "float", "DEFAULT": 3.6, "MIN": 1.2, "MAX": 12.0 }, { "NAME": "uCeilBright","TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 3.0 }, { "NAME": "uFog", "TYPE": "float", "DEFAULT": 0.022, "MIN": 0.0, "MAX": 0.16 }, { "NAME": "uMist", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uHaze", "TYPE": "float", "DEFAULT": 0.85, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uWet", "TYPE": "float", "DEFAULT": 0.30, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uMoon", "TYPE": "float", "DEFAULT": 0.70, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uVariety", "TYPE": "float", "DEFAULT": 0.70, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uGrass", "TYPE": "float", "DEFAULT": 0.70, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uGrime", "TYPE": "float", "DEFAULT": 0.45, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uAO", "TYPE": "float", "DEFAULT": 0.70, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uShadow", "TYPE": "float", "DEFAULT": 0.80, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uExposure", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.2, "MAX": 3.0 }, { "NAME": "uFlare", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 1.5 }, { "NAME": "uVHS", "TYPE": "float", "DEFAULT": 0.30, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uGrain", "TYPE": "float", "DEFAULT": 0.30, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uVignette", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 1.0 } ] }*/ // SUBURB - ISF / GLSL ES 1.00 (gl_FragColor, TIME, RENDERSIZE). // // An endless residential street. The streetscape is one 2D cross-section swept // along a wandering centreline; the houses are bounded boxes that only open up // when a sample is near them; the night is made almost entirely of scattered // light rather than of surfaces. #define PI 3.14159265359 #define FAR 190.0 float gLOT, gRW, gSB, gLS; float gTOD, gNight, gDusk; vec3 gKeyDir, gKeyCol, gLampCol, gSkyZen, gSkyHor, gFogCol; float gLampLit, gWinLit; float gPX; float gCeil; // --------------------------------------------------------------- // hashes and noise (Dave Hoskins' constants) // --------------------------------------------------------------- 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); } 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); } // every octave rotated as well as scaled, or four sets of axis-aligned cell // edges stack into a visible quilt across any large flat surface - which is // exactly what makes a procedural lawn look like a chessboard float fbm(vec2 p){ mat2 R = mat2(0.8040, 0.5946, -0.5946, 0.8040); float s = 0.0, a = 0.5; for (int i = 0; i < 4; i++){ s += a*vnoise(p); p = R*p*2.07 + 19.3; a *= 0.5; } return s; } // --------------------------------------------------------------- // primitives and the footprint filter // --------------------------------------------------------------- float sdBox2(vec2 p, vec2 b){ vec2 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0); } float sdBox3(vec3 p, vec3 b){ vec3 q = abs(p) - b; return length(max(q, 0.0)) + min(max(max(q.x, q.y), q.z), 0.0); } float sdCyl(vec3 p, float h, float r){ vec2 d = vec2(length(p.xz) - r, abs(p.y) - h); return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); } float smin(float a, float b, float k){ float h = clamp(0.5 + 0.5*(b - a)/k, 0.0, 1.0); return mix(b, a, h) - k*h*(1.0 - h); } // A tapered capsule - trunks, limbs, branches. // // The naive form, lateral distance minus the interpolated radius, OVERSHOOTS // wherever the two radii differ: the true distance to a slanted surface is the // perpendicular one, and the naive value is that divided by the cosine of the // taper. Overshooting is what puts holes in a sphere-traced trunk. The // backrooms figure could ignore this because it was only ever sampled as a // volume and never marched; a tree is marched, so the slope correction is not // optional. It is one reciprocal square root. float sdTaper(vec3 p, vec3 a, vec3 b, float ra, float rb){ vec3 pa = p - a, ba = b - a; float L = max(length(ba), 1e-4); float h = clamp(dot(pa, ba)/max(dot(ba, ba), 1e-6), 0.0, 1.0); float d = length(pa - ba*h) - mix(ra, rb, h); float k = (ra - rb)/L; return d*inversesqrt(1.0 + k*k); } // Value noise in 3D, from two 2D slices: two vnoise calls rather than eight // hashes. This is what puts lobes on a canopy, and a canopy without lobes is a // lollipop at any distance you care to look at it from. float n3(vec3 p){ float i = floor(p.y), f = fract(p.y); f = f*f*(3.0 - 2.0*f); return mix(vnoise(p.xz + i*37.13), vnoise(p.xz + (i + 1.0)*37.13), f); } // an ellipsoid in the bounded form rather than the naive scale-and-subtract, // which under-reports badly on a squashed one - and a crown is squashed float sdEllip(vec3 p, vec3 r){ float k0 = length(p/r); float k1 = length(p/(r*r)); return k0*(k0 - 1.0)/max(k1, 1e-6); } // Box-filter coverage of |x| < r by a pixel w wide. A siding lap is 3 mm of // shadow line and a sidewalk joint is 6 mm; both are thinner than a pixel at // twenty metres, and a hard-edged test keeps drawing them at full strength // wherever they land, which beats against the pixel grid as moire. float band(float x, float r, float w){ float h = 0.5*max(w, 1e-5); return clamp((min(x + h, r) - max(x - h, -r))/(2.0*h), 0.0, 1.0); } float fill(float d, float w){ return clamp(0.5 - d/max(w, 1e-5), 0.0, 1.0); } // The transition has to be WIDE: every texture keys off the footprint, and the // footprint depends on dot(n, rd), so a narrow fade paints conics about the // normal - concentric rings across every oblique surface. float mip(float feature, float foot){ return smoothstep(1.10, 0.10, foot/feature); } vec3 pick3(vec3 a, vec3 b, vec3 c, float f){ float i = clamp(f, 0.0, 2.0); return i < 1.0 ? mix(a, b, i) : mix(b, c, i - 1.0); } float pick1(float a, float b, float c, float f){ float i = clamp(f, 0.0, 2.0); return i < 1.0 ? mix(a, b, i) : mix(b, c, i - 1.0); } // --------------------------------------------------------------- // the centreline // --------------------------------------------------------------- // The street bends, and it bends by MEASURING FROM a curve rather than by // bending anything: the lateral coordinate is x - centre(z), so the whole // section, houses included, follows it for free. float centre(float z){ return uCurve*(7.0*sin(z*0.0081 + 1.3) + 3.4*sin(z*0.0169 + 4.1) + 1.6*sin(z*0.0327 + 0.4)); } float centreSlope(float z){ return uCurve*(0.0567*cos(z*0.0081 + 1.3) + 0.0575*cos(z*0.0169 + 4.1) + 0.0523*cos(z*0.0327 + 0.4)); } // --------------------------------------------------------------- // lots // --------------------------------------------------------------- // Everything about a house comes out of two hashes of (lot index, side). They // are recomputed in the shading pass rather than threaded out of the marcher, // which is far cheaper than carrying eight floats through two hundred steps // and is only paid once per visible pixel. vec4 lotHashA(float li, float side){ return hash42(vec2(li, side*57.31)); } vec4 lotHashB(float li, float side){ return hash42(vec2(li + 91.7, side*13.19 + 3.3)); } float lotWidth(vec4 h){ return mix(8.4, 11.6, h.x)*mix(1.0, 0.82 + 0.36*h.y, uVariety); } float lotDepth(vec4 h){ return mix(7.6, 10.4, h.y); } float wallH(vec4 h, vec4 g){ return step(g.x, uStories) > 0.5 ? mix(5.1, 5.9, h.z) : mix(2.72, 3.25, h.z); } // Where the driveway crosses the verge and the sidewalk. It also depresses the // curb, which is the detail that makes a curb read as a real curb: an // unbroken 150 mm edge running to the horizon is the giveaway that nobody // lives here. float driveMask(float v, vec4 h, float li, float side){ float on = step(hash11(li*3.7 + side*11.1), uDrives); float off = (h.w - 0.5)*(gLOT*0.32); return on*smoothstep(2.6, 1.9, abs(v - off)); } // --------------------------------------------------------------- // THE STREET SECTION // // Road crown, gutter, curb, verge, sidewalk and lawn are one plane figure in // (lateral, height), extruded down z. That is the backrooms trick applied to a // street: the expensive dimension is not there, so the whole streetscape costs // six box tests however far it runs. // --------------------------------------------------------------- float section(float a, float y, float v, vec4 h, float li, float side, out float mat){ float RW = gRW; float dv = driveMask(v, h, li, side); // the road is CROWNED for drainage - about 2 % - which is invisible dry // and is most of what shapes the reflections when it is wet float crown = -0.055*(a/RW)*(a/RW); float road = max(a - RW, y - crown); mat = 1.0; float d = road; // curb, dropped where a driveway crosses it float ch = mix(0.150, 0.030, dv); float curb = sdBox2(vec2(a - (RW + 0.085), y - 0.5*(ch - 0.6)), vec2(0.085, 0.5*(ch + 0.6))); if (curb < d){ d = curb; mat = 2.0; } // verge, sidewalk, lawn: three strips, and the driveway turns the first // and third to concrete without moving them float vergeC = RW + 0.17 + 0.42; float verge = sdBox2(vec2(a - vergeC, y - 0.5*(0.135 - 0.6)), vec2(0.42, 0.5*(0.135 + 0.6))); if (verge < d){ d = verge; mat = mix(4.0, 12.0, step(0.5, dv)); } float walkC = vergeC + 0.42 + 0.72; float walk = sdBox2(vec2(a - walkC, y - 0.5*(0.152 - 0.6)), vec2(0.72, 0.5*(0.152 + 0.6))); if (walk < d){ d = walk; mat = 3.0; } float lawnI = walkC + 0.72; float lawnO = gSB + 26.0; float lawn = sdBox2(vec2(a - 0.5*(lawnI + lawnO), y - 0.5*(0.145 - 0.9)), vec2(0.5*(lawnO - lawnI), 0.5*(0.145 + 0.9))); if (lawn < d){ d = lawn; mat = mix(4.0, 12.0, step(0.5, dv)*step(a, gSB)); } return d; } // --------------------------------------------------------------- // a house // // u = away from the street (0 at the front wall plane), v = along it. // --------------------------------------------------------------- float house(vec3 q, vec4 h, vec4 g, out float mat){ mat = 5.0; float W = 0.5*lotWidth(h); float D = lotDepth(h); float WH = wallH(h, g); float rise = W*mix(0.42, 0.95, uPitchR); // BOUND FIRST. Six lots are in flight and at most one of them is ever // near, so the bounding box is what makes the whole thing affordable: an // SDF is allowed to return its bound when the sample is far, and only the // sample that is close pays for the detail. float bound = sdBox3(q - vec3(0.5*D, 0.5*(WH + rise), 0.0), vec3(0.5*D + 0.55, 0.5*(WH + rise) + 0.35, W + 1.35)); if (bound > 0.55) return bound; float d = sdBox3(q - vec3(0.5*D, 0.5*WH, 0.0), vec3(0.5*D, 0.5*WH, W)); // THE GABLE, as the intersection of two half-spaces and not as a rotated // box: an intersection of convex half-spaces stays a usable field, where a // rotation of a box that is then unioned leaves a seam along the ridge and // an inside-out region under the eaves. float toStreet = step(0.5, g.y); // ridge across the street, or along it float lat = mix(q.x - 0.5*D, q.z, toStreet); float ext = mix(W, 0.5*D, toStreet); float halfB = mix(0.5*D, W, toStreet) + 0.34; // eaves overhang vec2 nrm = normalize(vec2(rise, halfB)); float ry = q.y - WH + 0.10; float roof = max(max(nrm.x*abs(lat) + nrm.y*ry - nrm.x*halfB, -ry), abs(mix(q.z, q.x - 0.5*D, toStreet)) - ext - 0.30); if (roof < d){ d = roof; mat = 6.0; } // the garage wing: forward of the main mass, lower, to one side if (g.z < uGarage){ float gs = (g.w < 0.5) ? -1.0 : 1.0; float gw = 3.2 + 0.7*h.w; vec3 gq = q - vec3(2.6, 1.30, gs*(W - gw*0.55)); float gar = sdBox3(gq, vec3(2.9, 1.30, gw)); float gnrm = 0.0; // and its own small gable, so it does not read as a shipping container vec2 gn = normalize(vec2(gw*0.55, gw)); float gry = q.y - 2.60 + 0.06; float grf = max(max(gn.x*abs(q.z - gs*(W - gw*0.55)) + gn.y*gry - gn.x*gw, -gry), abs(q.x - 2.6) - 3.10); if (gar < d){ d = gar; mat = 5.0; } if (grf < d){ d = grf; mat = 6.0; } } // porch roof over the front door, and a chimney on about a third of them float px = (g.w < 0.5) ? 1.0 : -1.0; float porch = sdBox3(q - vec3(-0.55, WH*0.62, px*W*0.34), vec3(0.75, 0.09, 1.35)); if (porch < d){ d = porch; mat = 6.0; } if (h.z > 0.66){ float ch = sdBox3(q - vec3(0.5*D + 0.9, WH*0.5 + rise*0.72, -px*W*0.55), vec3(0.34, WH*0.5 + rise*0.72, 0.28)); if (ch < d){ d = ch; mat = 7.0; } } return d; } // --------------------------------------------------------------- // trees // // A procedural tree fails on its SILHOUETTE, always, and always in one of the // same three ways: a smooth cone is a traffic cone, a sphere on a stick is a // lollipop, and a perfectly vertical trunk is CG. So these are built outline // first and surfaced second, because at forty metres in fog the outline is the // only thing that survives - which is most of what a street of them is. // --------------------------------------------------------------- // A SPRUCE IS NOT A CONE, IT IS A STACK OF SKIRTS. One whorl of branches per // year, each drooping and overhanging the one below, so the edge is sawtoothed // instead of straight. Folding the height into tiers buys every whorl for the // price of one - and two tiers are evaluated rather than one because a fold // makes the field jump where the tier index changes, and a marcher walks // straight through a jump. float conifer(vec3 q, float hh, float seed){ float hc = hh*0.95; float rr = length(q.xz); float az = atan(q.z, q.x); float nT = 8.0; float tS = hc/nT; float d = 1e9; for (int i = 0; i < 2; i++){ // clamped, not skipped: a sample below the tree still has to be told // about the bottom skirt or it oversteps into it from underneath float ti = clamp(floor(q.y/tS) - float(i), 0.0, nT - 1.0); float yTop = (ti + 1.0)*tS; // the envelope is slightly CONVEX rather than straight, which is a // large part of the difference between a spruce and a party hat float u = clamp(yTop/hc, 0.0, 1.0); float R = hh*0.30*pow(1.0 - u, 1.15) + hh*0.012; // and the outline is not a circle in plan either float rip = 1.0 - 0.14*sin(az*7.0 + ti*2.3 + seed*6.0) - 0.07*sin(az*13.0 - ti*1.7 + seed*3.0); float reach = R*rip*(0.94 + 0.12*hash11(ti*3.1 + seed)); // the skirt hangs lower the further out it goes float droop = -0.62*tS*clamp(rr/max(reach, 1e-3), 0.0, 1.0); d = min(d, max(rr - reach, abs(q.y - yTop - droop) - tS*0.46)); } // the leader poking out of the top, which is what finishes it d = min(d, sdTaper(q, vec3(0.0, hc - tS*1.2, 0.0), vec3(0.0, hc + tS*0.55, 0.0), hh*0.020, 0.004)); // the azimuthal ripple has an angular gradient, so this is no longer a // metric field; two thirds is a safe scale and costs one multiply return d*0.66; } // kind: 0 spruce, 1 broadleaf, 2 staked sapling float tree(vec3 q, float hh, float kind, float seed, out float mat){ mat = 10.0; float rad = hh*0.40 + 0.35; float bound = sdBox3(q - vec3(0.0, hh*0.52, 0.0), vec3(rad, hh*0.60, rad)); if (bound > 0.5) return bound; // A LEAN, and it accelerates up the trunk. Every tree on a real street is // a couple of degrees off vertical and no two agree; a row that all stand // perfectly upright reads as instanced no matter how good each one is. vec2 lean = (vec2(hash11(seed*1.7), hash11(seed*2.9 + 4.0)) - 0.5)*0.09; q.xz -= lean*q.y*(0.55 + 0.55*clamp(q.y/hh, 0.0, 1.0)); float shear = inversesqrt(1.0 + 2.0*dot(lean, lean)); // a shear is not an isometry float dF = 1e9, dB; if (kind < 0.5){ dB = sdTaper(q, vec3(0.0, 0.0, 0.0), vec3(0.0, hh*0.60, 0.0), hh*0.030, hh*0.014); dF = conifer(q, hh, seed); } else if (kind < 1.5){ // ---- broadleaf ---- // LIMBED UP: the crown starts at 40 % of the height, so you can see // under it and down the street. A canopy that reaches the ground turns // the row into a hedge and kills the depth of the whole frame. float cb = hh*0.42; dB = sdTaper(q, vec3(0.0, 0.0, 0.0), vec3(0.0, cb, 0.0), hh*0.040, hh*0.022); for (int i = 0; i < 3; i++){ float an = 6.2831*(float(i)/3.0 + hash11(seed + 7.0)*0.33); vec3 tip = vec3(cos(an), 0.0, sin(an))*hh*0.20 + vec3(0.0, cb + hh*0.26, 0.0); dB = min(dB, sdTaper(q, vec3(0.0, cb - hh*0.04, 0.0), tip, hh*0.024, hh*0.010)); } // The crown as three masses, not one. A real canopy is lumpy, WIDER // THAN TALL, and domed underneath; a single sphere gets all three // wrong simultaneously and no amount of texture recovers it. vec3 c = q - vec3(0.0, cb + hh*0.30, 0.0); float m = sdEllip(c, vec3(hh*0.36, hh*0.24, hh*0.34)); for (int i = 0; i < 2; i++){ float an = 6.2831*hash11(seed*3.3 + float(i)*5.1); vec3 o = vec3(cos(an), 0.0, sin(an))*hh*0.20 + vec3(0.0, hh*(0.04 - 0.11*float(i)), 0.0); m = smin(m, sdEllip(c - o, vec3(hh*0.24, hh*0.19, hh*0.22)), hh*0.10); } // and then the edge is broken at two scales, which is the entire // difference between foliage and a blob m -= hh*(0.055*(n3(q*(4.2/hh) + seed) - 0.5) + 0.030*(n3(q*(11.0/hh) + 3.0) - 0.5)); dF = m*0.62; // displaced, therefore no longer metric } else { // ---- a staked sapling: what a new street is actually planted with ---- // Bare, so there is no canopy to hide behind and the branching IS the // tree. Five whips off a leader, rising steeply, thinning as they go. dB = sdTaper(q, vec3(0.0, 0.0, 0.0), vec3(0.0, hh, 0.0), hh*0.022, hh*0.005); for (int i = 0; i < 5; i++){ float f = float(i); float an = 6.2831*(f*0.2 + hash11(seed + f)*0.18); float y0 = hh*(0.42 + 0.11*f); float ln = hh*(0.30 - 0.03*f)*(0.7 + 0.6*hash11(seed*2.0 + f)); vec3 tip = vec3(cos(an), 0.0, sin(an))*ln*0.72 + vec3(0.0, y0 + ln*0.80, 0.0); dB = min(dB, sdTaper(q, vec3(0.0, y0, 0.0), tip, hh*0.010, hh*0.003)); } // the stake and its tie, which is the detail that dates the street: // nobody stakes a tree that has been in the ground five years vec3 sp = vec3(cos(6.2831*hash11(seed*5.5)), 0.0, sin(6.2831*hash11(seed*5.5)))*0.17; dB = min(dB, sdTaper(q - sp, vec3(0.0, 0.0, 0.0), vec3(0.0, hh*0.58, 0.0), 0.024, 0.021)); dB = min(dB, sdTaper(q, vec3(0.0, hh*0.48, 0.0), sp + vec3(0.0, hh*0.48, 0.0), 0.012, 0.012)); } mat = (dF < dB) ? 10.0 : 14.0; return min(dF, dB)*shear; } float car(vec3 q, out float mat){ mat = 11.0; float bound = sdBox3(q - vec3(0.0, 0.72, 0.0), vec3(2.36, 0.78, 1.00)); if (bound > 0.45) return bound; float body = sdBox3(q - vec3(0.0, 0.62, 0.0), vec3(2.20, 0.34, 0.86)) - 0.14; float cab = sdBox3(q - vec3(-0.12, 1.10, 0.0), vec3(1.05, 0.26, 0.76)) - 0.12; return smin(body, cab, 0.18); } // --------------------------------------------------------------- // .x = distance, .y = material // 1 road 2 curb 3 sidewalk 4 lawn 5 siding 6 roof 7 trim // 8 pole 9 lamp glass 10 tree 11 car 12 concrete 13 ceiling // --------------------------------------------------------------- vec2 map(vec3 p){ float cz = centre(p.z); float sl = centreSlope(p.z); float s = p.x - cz; float a = abs(s); float side = (s < 0.0) ? -1.0 : 1.0; float best = 1e9, mat = 0.0; // the two lots bracketing this z, on the near side only for the section's // driveway, and on BOTH sides for the buildings float c0 = floor(p.z/gLOT - 0.5); // ---- the swept section ---- { float li = floor(p.z/gLOT + 0.5); vec4 h = lotHashA(li, side); float m; float d = section(a, p.y, p.z - li*gLOT, h, li, side, m); best = d; mat = m; } // ---- lots ---- for (int i = 0; i < 2; i++){ float li = c0 + float(i) + 0.5; float lz = li*gLOT; for (int k = 0; k < 2; k++){ float sd = (k == 0) ? -1.0 : 1.0; vec4 h = lotHashA(li, sd); vec4 g = lotHashB(li, sd); if (h.x < uDensity){ vec3 q = vec3(a - gSB, p.y, (p.z - lz)*sd); if (side*sd > 0.0){ float m; float d = house(q, h, g, m); if (d < best){ best = d; mat = m; } } else { // the far side still has to be represented or a ray // crossing the road walks straight through it: same house, // mirrored, with a negative lateral coordinate float m; float d = house(vec3(-a - gSB, p.y, (p.z - lz)*sd), h, g, m); if (d < best){ best = d; mat = m; } } } // ---- street trees ---- // // The verge tree has to DODGE THE DRIVEWAY. A tree standing in the // middle of a crossing is the loudest single tell that a street was // generated rather than laid out, and avoiding it costs one sign // flip: put the tree at whichever end of the lot the driveway is not. vec4 tv = hash42(vec2(li*3.0 + 19.0, sd*7.7)); if (tv.x < uTrees){ float tOff = ((h.w - 0.5) >= 0.0 ? -1.0 : 1.0)*gLOT*0.31; float kind = (tv.y < uConifer) ? 0.0 : ((tv.y < uConifer + (1.0 - uConifer)*uSapling) ? 2.0 : 1.0); float hh = mix(3.4, 6.2, tv.z); if (kind < 0.5) hh = mix(4.0, 9.5, tv.z*tv.z); if (kind > 1.5) hh = mix(2.2, 3.6, tv.z); float ax = gRW + 0.60 + 0.25*tv.w; float m; vec3 tq = vec3((side*sd < 0.0 ? -a : a) - ax, p.y, p.z - lz - tOff); float d = tree(tq, hh, kind, li*3.7 + sd*1.9, m); if (d < best){ best = d; mat = m; } } // and a bigger one set back in the lawn on some lots. Trees all at // one distance from the kerb give the row no depth at all - the // second rank is what makes it a neighbourhood rather than an avenue. if (tv.w < uTrees*0.55){ // clear of the front wall by more than the crown radius, and // at the same end of the lot as the verge tree, which is the // end the driveway is not float hh2 = mix(4.5, 9.0, tv.y); float ax = gSB - 3.6 - 1.8*tv.y; float m; vec3 tq = vec3((side*sd < 0.0 ? -a : a) - ax, p.y, p.z - lz - ((h.w - 0.5) >= 0.0 ? -1.0 : 1.0)*gLOT*0.36); float d = tree(tq, hh2, (tv.x < uConifer) ? 0.0 : 1.0, li*9.1 + sd*4.3 + 21.0, m); if (d < best){ best = d; mat = m; } } // a car in the driveway if (hash11(li*11.9 + sd*3.3) < uCars && h.x < uDensity){ float m; float off = (lotHashA(li, sd).w - 0.5)*(gLOT*0.32); vec3 cq = vec3(a - (gSB - 3.4), p.y, p.z - lz - off); if (side*sd < 0.0) cq.x = -a - (gSB - 3.4); // long axis pointing up the driveway, not along the street float d = car(vec3(cq.z, cq.y, cq.x), m); if (d < best){ best = d; mat = m; } } } } // ---- streetlamps, alternating sides ---- { float ln = floor(p.z/gLS + 0.5); for (int i = 0; i < 2; i++){ float lid = ln + float(i) - 0.0; float sd = (mod(lid, 2.0) < 0.5) ? 1.0 : -1.0; vec3 lq = vec3(a - (gRW + 0.55), p.y, p.z - lid*gLS); if (side*sd < 0.0) lq.x = -a - (gRW + 0.55); float pole = sdCyl(lq - vec3(0.0, 2.0, 0.0), 1.86, 0.052); pole = min(pole, sdCyl(lq - vec3(0.0, 0.17, 0.0), 0.17, 0.105)); if (pole < best){ best = pole; mat = 8.0; } // the lantern: a squashed ellipsoid with a small cap, which is the // acorn fixture every one of these streets has vec3 gq = lq - vec3(0.0, 4.12, 0.0); float lant = (length(gq/vec3(1.0, 1.28, 1.0)) - 0.20)*0.78; float cap = sdCyl(lq - vec3(0.0, 4.46, 0.0), 0.045, 0.145); if (min(lant, cap) < best){ best = min(lant, cap); mat = (lant < cap) ? 9.0 : 8.0; } } } // ---- the ceiling, if this neighbourhood is indoors ---- if (gCeil > 0.5){ float dc = gCeil - p.y; if (dc < best){ best = dc; mat = 13.0; } } // A sheared field is not an isometry. Measuring the lateral coordinate // from a curve stretches distance by sqrt(1 + slope^2) across the street, // so the raw value OVERSHOOTS exactly where the road turns - which is // where a marcher would punch through a curb. Dividing here costs one // reciprocal and makes the whole swept world safe. return vec2(best/sqrt(1.0 + sl*sl), mat); } vec3 calcNormal(vec3 p){ vec2 e = vec2(1.0, -1.0)*0.0022; 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); } float calcAO(vec3 p, vec3 n){ float o = 0.0, s = 1.0; for (int i = 0; i < 5; i++){ float h = 0.03 + 0.24*float(i); o += (h - map(p + n*h).x)*s; s *= 0.72; } return clamp(1.0 - 0.95*o, 0.0, 1.0); } float shadowRay(vec3 ro, vec3 rd, float maxt){ float res = 1.0, t = 0.20, prev = 1e12; for (int i = 0; i < 22; i++){ float h = map(ro + rd*t).x; float y = h*h/(2.0*prev); float d = sqrt(max(h*h - y*y, 0.0)); res = min(res, d/max(0.0001, 5.0*(t - y))); prev = h; t += clamp(h, 0.12, 1.6); if (res < 0.02 || t > maxt) break; } return clamp(res, 0.0, 1.0); } // --------------------------------------------------------------- // the lamps as light sources // --------------------------------------------------------------- vec3 lampPos(float lid){ float sd = (mod(lid, 2.0) < 0.5) ? 1.0 : -1.0; float z = lid*gLS; return vec3(centre(z) + sd*(gRW + 0.55), 4.12, z); } float lampOn(float lid){ float h = hash11(lid*3.71 + 0.7); float on = step(h, uLampOn)*gLampLit; // a dying lamp, which every street has exactly one of float dy = step(hash11(lid*9.13), 0.10); float fl = mix(1.0, 0.35 + 0.65*step(0.30, hash11(floor(TIME*11.0)*0.77 + lid)), dy); return on*fl; } // Three at a time, each faded to nothing before the set changes. // // The obvious thing is to light from the nearest lamp, and it is wrong the // same way picking the strongest of nine ceiling panels is wrong: the set // changes discontinuously halfway between two lamps, and that boundary is a // plane across the road which the shading steps over. A window that reaches // zero before the swap has no boundary to see. vec3 lampLight(vec3 p, vec3 n, out vec3 keyDir, out float keyDist){ float lb = floor(p.z/gLS + 0.5); vec3 acc = vec3(0.0), accL = vec3(0.0); float sum = 0.0, accD = 0.0; for (int i = 0; i < 3; i++){ float lid = lb + float(i) - 1.0; vec3 lp = lampPos(lid); vec3 L = lp - p; float dl = max(length(L), 0.35); L /= dl; float on = lampOn(lid); // a luminaire throws down and sideways, not up float dn = 0.35 + 0.65*clamp(-dot(L, vec3(0.0, -1.0, 0.0)), 0.0, 1.0); float w = on*max(dot(n, L), 0.0)*dn * smoothstep(1.75*gLS, 1.05*gLS, abs(p.z - lp.z))/(1.0 + 0.055*dl*dl); sum += w; acc += L*w; accD += dl*w; accL += gLampCol*w; } keyDir = normalize(acc + n*0.001 + vec3(0.0, 0.0001, 0.0)); keyDist = accD/max(sum, 0.0001); return accL*uLampBright; } // The fluorescent deck, when the neighbourhood is indoors. // // A strip is a LINE light: the closest point on it is clamped only in z, and // an infinite line falls off as one over distance, not one over distance // squared. That single exponent is the whole difference between a room lit by // tubes and a street lit by lamps - it is why a warehouse has no falloff you // can see and a suburb is all falloff. vec3 ceilLight(vec3 p, vec3 n){ if (gCeil < 0.5) return vec3(0.0); float sp = uStrip; float lb = floor(p.z/sp + 0.5); vec3 acc = vec3(0.0); for (int i = 0; i < 3; i++){ float sid = lb + float(i) - 1.0; if (hash11(sid*5.3) > 0.94) continue; // the odd dead run vec3 lp = vec3(p.x, gCeil - 0.12, sid*sp); vec3 L = lp - p; float dl = max(length(L), 0.5); L /= dl; acc += vec3(0.95, 0.97, 1.0)*max(dot(n, L), 0.0) * smoothstep(2.4*sp, 1.1*sp, abs(p.z - lp.z))/(0.6 + 0.55*dl); } return acc*uCeilBright*0.55; } // --------------------------------------------------------------- // sky // --------------------------------------------------------------- vec3 skyCol(vec3 rd){ if (gCeil > 0.5) return gFogCol; float h = clamp(rd.y, -0.2, 1.0); vec3 c = mix(gSkyHor, gSkyZen, pow(clamp(h, 0.0, 1.0), 0.55)); // the warm band low in the west at dusk, and only at dusk vec3 sunD = gKeyDir; float sd = max(dot(rd, sunD), 0.0); c += gKeyCol*pow(sd, 5.0)*gDusk*1.4; c += gKeyCol*pow(sd, 60.0)*(1.0 - gNight)*2.0; // stars and a moon at night, both faded by the ground haze near the horizon if (gNight > 0.01){ float hz = smoothstep(0.0, 0.35, h); vec2 sp = rd.xz/max(rd.y, 0.08); float st = hash12(floor(sp*140.0)); c += vec3(0.9, 0.93, 1.0)*step(0.9965, st)*hz*gNight*0.85; float md = max(dot(rd, sunD), 0.0); c += vec3(1.0, 0.97, 0.9)*smoothstep(0.99955, 0.99975, md)*uMoon*gNight*3.0; c += vec3(0.55, 0.62, 0.80)*pow(md, 900.0)*uMoon*gNight*0.5; } return c; } // --------------------------------------------------------------- // materials // --------------------------------------------------------------- vec3 shade(vec3 p, vec3 rd, vec3 n, float mat, float foot, out vec3 emis){ emis = vec3(0.0); float det = mip(0.006, foot); float detT = mip(0.035, foot); float detM = mip(0.60, foot); float cz = centre(p.z); float s = p.x - cz, a = abs(s); float side = (s < 0.0) ? -1.0 : 1.0; float li = floor(p.z/gLOT + 0.5); vec4 h = lotHashA(li, side), g = lotHashB(li, side); vec3 alb = vec3(0.5); float gloss = 0.0; if (mat < 1.5){ // ---- asphalt ---- // // Three scales, and the middle one is the one people forget: chip // aggregate at 3 cm, the polished WHEEL PATHS at a metre, and patching // at ten. Without the wheel paths the road is a uniform grey field and // reads as painted concrete. float chip = 0.5*vnoise(p.xz*60.0) + 0.5*vnoise(p.xz*145.0); float wear = smoothstep(1.35, 0.45, abs(a - gRW*0.46)); float patch = fbm(p.xz*0.10 + 3.0); alb = vec3(0.055, 0.055, 0.060)*(0.78 + 0.55*chip*det); alb *= 0.86 + 0.34*patch; alb = mix(alb, alb*1.28, wear*0.5*detM); // cracks, which follow a ridged field rather than scattering float crack = smoothstep(0.020, 0.0, abs(fbm(p.xz*0.55 + 12.0) - 0.5)); alb *= 1.0 - 0.45*crack*detT; gloss = mix(0.10, 0.92, uWet*(0.55 + 0.45*wear)); } else if (mat < 2.5){ float w = vnoise(p.xz*vec2(2.4, 24.0)); alb = vec3(0.40, 0.395, 0.38)*(0.86 + 0.28*w); // the joint every 3 m, which is what gives a curb its length alb *= 1.0 - 0.35*band(fract(p.z/3.0) - 0.5, 0.006, foot/3.0); alb *= 1.0 - 0.30*uGrime*smoothstep(0.10, 0.0, p.y)*detM; gloss = mix(0.06, 0.6, uWet); } else if (mat < 3.5){ // ---- sidewalk: slabs, and the joints matter more than the concrete ---- float jz = fract(p.z/1.52), jx = fract((a)/1.55); float w = foot/1.52; float joint = max(band(jz - 0.5, 0.010, w), band(jx - 0.5, 0.010, w)); float sl = 0.5*vnoise(p.xz*90.0) + 0.5*vnoise(p.xz*25.0); vec4 sh = hash42(vec2(floor(p.z/1.52), floor(a/1.55))); alb = vec3(0.44, 0.435, 0.425)*(0.88 + 0.22*sh.x)*(1.0 + 0.20*(sl - 0.5)*det); alb *= 1.0 - 0.42*joint; alb *= mix(1.0, 0.80 + 0.30*fbm(p.xz*0.7), uGrime*detM); gloss = mix(0.05, 0.55, uWet); } else if (mat < 4.5){ // ---- lawn ---- float bl = 0.55*vnoise(p.xz*150.0) + 0.45*vnoise(p.xz*420.0); float pt = fbm(p.xz*0.9 + 5.0); alb = mix(vec3(0.055, 0.085, 0.035), vec3(0.10, 0.145, 0.055), pt); alb = mix(alb, vec3(0.11, 0.10, 0.05), smoothstep(0.62, 0.95, pt)*(1.0 - uGrass)); alb *= 1.0 + 0.55*(bl - 0.5)*det; gloss = 0.05; } else if (mat < 5.5){ // ---- siding ---- // // Lap siding is a 150 mm course with a shadow line at the bottom of // each board, and that shadow line is the entire read: it is what makes // a flat box look like a house from forty metres, long after the colour // has gone grey. float u = (abs(n.y) > 0.5) ? a : ((abs(n.x) > 0.5) ? p.z : a); float lap = fract(p.y/0.152); vec3 base = mix(vec3(0.62, 0.63, 0.60), vec3(0.50, 0.44, 0.36), h.z); base = mix(base, vec3(0.36, 0.42, 0.40), step(0.72, g.z)); base = mix(vec3(0.60, 0.60, 0.58), base, uVariety); alb = base*(0.90 + 0.16*hash12(vec2(li, side))); alb *= 1.0 - 0.34*band(lap - 0.06, 0.055, foot/0.152); alb *= 1.0 + 0.10*(vnoise(vec2(u, p.y)*70.0) - 0.5)*det; alb *= mix(1.0, 0.80 + 0.30*fbm(vec2(u, p.y)*1.4), uGrime*0.7*detM); // ---- windows, drawn rather than built ---- // // Never geometry. A window is four numbers on a facade, and lighting it // individually is the whole night-time image: three warm rectangles in // a dark mass reads as a house with somebody in it, and an emissive box // reads as a lamp. if (abs(n.y) < 0.5){ float lz = li*gLOT; float fu = (abs(n.x) > 0.5) ? (p.z - lz)*side : (p.z - lz)*side; float W = 0.5*lotWidth(h); float WH = wallH(h, g); float col = floor(fu/1.95 + 0.5); float fx = fu - col*1.95; float twoFl = step(4.4, WH); // ground floor, then an upper row only if there is an upper floor for (int fl = 0; fl < 2; fl++){ float fy = (fl == 0) ? 1.55 : 4.05; if (fl == 1 && twoFl < 0.5) break; float dW = sdBox2(vec2(fx, p.y - fy), vec2(0.46, 0.66)); float ins = fill(dW + 0.055, foot); float frame = fill(dW, foot) - ins; if (ins + frame < 0.001) continue; vec4 wh = hash42(vec2(li*13.0 + col, side*7.0 + float(fl))); float lit = step(wh.x, gWinLit)*step(abs(col), 2.5); float tv = step(wh.y, uTV)*lit; float flick = mix(1.0, 0.55 + 0.75*hash11(floor(TIME*7.0) + wh.z*40.0), tv); vec3 wc = mix(vec3(1.00, 0.74, 0.40), vec3(0.55, 0.72, 1.00), tv); // an unlit pane is not black, it is a dark mirror of the sky vec3 dark = mix(vec3(0.030, 0.033, 0.040), gSkyHor*0.30, 0.5); alb = mix(alb, dark, ins); alb = mix(alb, vec3(0.80, 0.80, 0.78), frame); emis += wc*lit*flick*ins*(2.2 + 1.4*uExposure) * (1.0 - 0.35*fill(abs(fx) - 0.02, foot)); // a mullion } // the front door, and a garage door on the wing float dr = sdBox2(vec2(fu - (g.w < 0.5 ? 1.0 : -1.0)*W*0.34, p.y - 1.05), vec2(0.44, 1.05)); alb = mix(alb, mix(vec3(0.30, 0.16, 0.10), vec3(0.20, 0.22, 0.24), g.x), fill(dr, foot)); } gloss = 0.10; } else if (mat < 6.5){ // ---- asphalt shingle: courses running across the slope ---- float u = p.z, vv = p.y; float row = fract(vv/0.145); alb = mix(vec3(0.115, 0.110, 0.105), vec3(0.19, 0.17, 0.15), h.y*uVariety); alb *= 0.88 + 0.30*vnoise(vec2(u, vv)*26.0)*det; alb *= 1.0 - 0.30*band(row - 0.05, 0.04, foot/0.145); alb *= mix(1.0, 0.82 + 0.34*fbm(p.xz*1.1 + 21.0), uGrime*detM); gloss = mix(0.06, 0.55, uWet); } else if (mat < 7.5){ alb = vec3(0.36, 0.31, 0.28)*(0.9 + 0.2*vnoise(p.xz*30.0)); gloss = 0.1; } else if (mat < 8.5){ alb = vec3(0.045, 0.045, 0.048)*(0.9 + 0.25*vnoise(vec2(p.y*40.0, 0.0))); gloss = mix(0.25, 0.8, uWet); } else if (mat < 9.5){ // ---- the lantern itself ---- float on = 0.0; float lb = floor(p.z/gLS + 0.5); for (int i = 0; i < 3; i++) { float lid = lb + float(i) - 1.0; on = max(on, lampOn(lid)*smoothstep(gLS*0.6, gLS*0.2, abs(p.z - lid*gLS))); } alb = vec3(0.5); emis = gLampCol*on*(9.0 + 16.0*uLampBright); gloss = 0.4; } else if (mat < 10.5){ // ---- foliage ---- // // Two scales of clump, and then a strong VERTICAL GRADIENT, which does // more for the read than the colour does: a canopy is lit from the top // and very nearly black underneath, and a uniformly coloured one looks // like green plastic however good its silhouette is. vec4 tv = hash42(vec2(floor(p.z/gLOT + 0.5)*3.0 + 19.0, side*7.7)); float conif = step(tv.y, uConifer); vec3 grn = mix(vec3(0.085, 0.115, 0.045), vec3(0.030, 0.068, 0.048), conif); vec3 dry = mix(vec3(0.140, 0.120, 0.050), vec3(0.055, 0.078, 0.048), conif); vec3 base = mix(grn, dry, (1.0 - uGrass)*0.75); alb = base*(0.55 + 0.85*n3(p*2.6)); alb *= 0.75 + 0.50*n3(p*11.0 + 4.0)*detT; alb *= 0.48 + 0.62*smoothstep(-0.5, 0.8, n.y); gloss = 0.10; } else if (mat < 11.5){ vec4 ch = hash42(vec2(floor(p.z/gLOT), side*3.0)); alb = mix(vec3(0.020, 0.022, 0.026), vec3(0.10, 0.10, 0.11), ch.x*0.6); gloss = 0.85; // car paint is the one } else if (mat < 12.5){ float j = band(fract(p.z/2.7) - 0.5, 0.010, foot/2.7); alb = vec3(0.38, 0.375, 0.365)*(0.9 + 0.2*vnoise(p.xz*20.0)); alb *= 1.0 - 0.3*j; gloss = mix(0.05, 0.5, uWet); } else if (mat < 13.5){ // ---- the fluorescent deck ---- float sp = uStrip; float sid = floor(p.z/sp + 0.5); float dz = abs(p.z - sid*sp); float on = step(hash11(sid*5.3), 0.94); float lens = fill(dz - 0.13, foot); alb = vec3(0.30, 0.31, 0.31)*(0.85 + 0.3*vnoise(p.xz*3.0)); // the structure between the runs, which is what makes it a deck alb *= 1.0 - 0.45*band(fract(p.x/2.44) - 0.5, 0.03, foot/2.44); emis = vec3(0.95, 0.97, 1.0)*lens*on*(3.0 + 5.0*uCeilBright); alb = mix(alb, vec3(0.7), lens*0.6); gloss = 0.05; } else { // ---- bark ---- // // Ridged ALONG the trunk. Bark runs with the grain, and an isotropic // noise field on a trunk is the fastest way to make a tree look like a // rock - so both octaves are stretched hard in y. float rid = 0.62*vnoise(vec2((p.x + p.z)*26.0, p.y*2.4)) + 0.38*vnoise(vec2((p.x - p.z)*62.0, p.y*5.2)); alb = mix(vec3(0.052, 0.044, 0.038), vec3(0.115, 0.098, 0.078), rid); alb *= 0.72 + 0.48*rid*detT; gloss = 0.08; } // ---- light ---- float ao = mix(1.0, calcAO(p, n), uAO); float sky = 0.5 + 0.5*n.y; // key: sun, or moon, on one direction and one shadow ray float ndl = max(dot(n, gKeyDir), 0.0); float ksh = 1.0; if (uShadow > 0.01 && ndl > 0.001) ksh = mix(1.0, shadowRay(p + n*0.05, gKeyDir, 45.0), uShadow); vec3 col = alb*gKeyCol*ndl*ksh; // lamps vec3 kd; float kdist; vec3 lampD = lampLight(p, n, kd, kdist); float lsh = 1.0; if (uShadow > 0.01 && kdist < 40.0) lsh = mix(1.0, shadowRay(p + n*0.06, kd, kdist - 0.4), uShadow*0.9); col += alb*lampD*lsh*(2.6 + 1.4*uLampBright); // the deck, if there is one col += alb*ceilLight(p, n)*(1.6 + 1.2*uCeilBright); // FOLIAGE IS TRANSLUCENT, and a streetlamp behind a canopy is exactly // where it shows: light coming through a leaf rather than off it. Without // this term a backlit tree is a black cutout, which is the one thing a // real one never is - it is the brightest object on the street. if (mat > 9.5 && mat < 10.5){ col += alb*lampD*pow(max(dot(rd, -kd), 0.0), 3.0)*2.2; col += alb*gKeyCol*pow(max(dot(rd, -gKeyDir), 0.0), 3.0)*0.6; } // ambient: the sky above and a dim bounce off the road below, which at // night is almost the only thing filling the undersides vec3 amb = mix(gSkyHor, gSkyZen, sky)*sky*ao + vec3(0.05, 0.05, 0.055)*(1.0 - sky)*ao*(0.4 + 0.6*gNight); col += alb*amb*(0.55 + 0.45*(1.0 - gNight)); // specular, and the wet road is the reason it exists if (gloss > 0.02){ vec3 kdir = (gNight > 0.5) ? kd : gKeyDir; vec3 hv = normalize(kdir - rd); float sp = pow(max(dot(n, hv), 0.0), mix(12.0, 220.0, gloss)); col += (gNight > 0.5 ? lampD*3.0 : gKeyCol)*sp*gloss*ksh*lsh; // ANALYTIC REFLECTION, and only for the ground. On a horizontal mirror // at grazing incidence what you actually see is the sky and the lamps // and essentially nothing else, so the reflected ray samples only those // two - no second march, no third of the frame time. The lobe is // stretched vertically, which is what turns each lamp into the column // of light on wet asphalt instead of a dot. if (uWet > 0.02 && n.y > 0.5 && mat < 3.6){ vec3 rr = reflect(rd, n); float F = 0.03 + 0.55*pow(1.0 - max(dot(-rd, n), 0.0), 5.0); vec3 refl = skyCol(rr)*0.7; float lb = floor(p.z/gLS + 0.5); for (int i = 0; i < 3; i++){ float lid = lb + float(i) - 1.0; vec3 lp = lampPos(lid); vec3 L = normalize(lp - p); // squash the vertical component of the difference: the streak float dd = length((rr - L)*vec3(1.0, 0.16, 1.0)); refl += gLampCol*lampOn(lid)*exp(-dd*dd*70.0)*3.5*uLampBright; } col = mix(col, refl, clamp(F*uWet*1.5, 0.0, 0.85)); } } return col; } // --------------------------------------------------------------- void main(){ vec2 fragCoord = gl_FragCoord.xy; vec2 R = RENDERSIZE.xy; float T = TIME; float t = T*uSpeed; gLOT = uLot; gRW = 0.5*uRoadW; gSB = 0.5*uRoadW + 2.4 + uSetback; gLS = uLampSpace; gCeil = uCeiling; gTOD = clamp(uTOD, 0.0, 2.0); gNight = smoothstep(1.0, 1.9, gTOD); gDusk = 1.0 - abs(gTOD - 1.0); // TIME OF DAY MOVES FIVE THINGS AT ONCE, which is why it is one control // and not three presets. Elevation and colour of the key, the sky, whether // the lamps have struck, how many windows are lit, and the fog - and the // fog is the one that matters, because a suburban street at night is not a // dark version of the same street, it is a different scene made mostly of // scattered light. float el = pick1(0.95, 0.055, 0.62, gTOD); // sun sinking, moon rising gKeyDir = normalize(vec3(cos(uSunAz)*cos(el), sin(el), sin(uSunAz)*cos(el))); gKeyCol = pick3(vec3(1.00, 0.97, 0.90)*1.35, vec3(1.00, 0.55, 0.26)*0.95, vec3(0.30, 0.38, 0.58)*0.16*uMoon, gTOD); gSkyZen = pick3(vec3(0.42, 0.55, 0.78), vec3(0.10, 0.14, 0.34), vec3(0.006, 0.008, 0.018), gTOD); gSkyHor = pick3(vec3(0.76, 0.80, 0.86), vec3(0.62, 0.32, 0.22), vec3(0.022, 0.026, 0.040), gTOD); gLampCol = mix(vec3(1.00, 0.62, 0.22), vec3(1.00, 0.94, 0.84), uLampWarm); gLampLit = smoothstep(0.55, 1.05, gTOD); gWinLit = uWindows*smoothstep(0.45, 1.25, gTOD); gFogCol = mix(gSkyHor, gLampCol*0.5, gNight*0.35)*mix(1.0, 1.6, step(0.5, gCeil)); // ---- the walk ---- float z = t*1.15; float cz = centre(z); vec3 ro = vec3(cz + uSide*gRW*0.62 + uBob*0.022*sin(t*2.5), uEye + uBob*0.030*sin(t*5.0), z); float dz = 0.6; vec3 fw = normalize(vec3(centre(z + dz) - centre(z - dz), 0.0, 2.0*dz)); float yaw = uYaw + uBob*0.04*sin(t*0.81 + 1.0); float cy = cos(yaw), sy = sin(yaw); fw = normalize(vec3(fw.x*cy + fw.z*sy, 0.0, fw.z*cy - fw.x*sy)); vec3 rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0))); vec3 up = cross(rt, fw); fw = normalize(fw + up*(uPitch + uBob*0.016*sin(t*1.7))); rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0))); up = cross(rt, fw); float roll = uBob*0.011*sin(t*2.1 + 0.5); vec3 rr2 = rt*cos(roll) + up*sin(roll); up = up*cos(roll) - rt*sin(roll); rt = rr2; vec2 uv = (fragCoord - 0.5*R)/R.y; float vhs = uVHS; uv *= 1.0 + vhs*0.030*dot(uv, uv); vec3 rd = normalize(uv.x*rt + uv.y*up + uLens*fw); gPX = 1.0/(uLens*R.y); // ---- march ---- float tt = 0.35, tHit = FAR, mat = 0.0; for (int i = 0; i < 168; i++){ vec2 m = map(ro + rd*tt); // the epsilon has to open with distance or a grazing ray down a // 150 m street runs out of steps, but not too far: stopping short by e // along the ray is a slide of e/cos ALONG a grazing surface, which // bands every lighting term computed from the hit point if (m.x < 0.0016*tt + 0.0012){ tHit = tt; mat = m.y; break; } tt += m.x*0.85; if (tt > FAR) break; } vec3 col; if (mat > 0.5){ vec3 p = ro + rd*tHit; vec3 n = calcNormal(p); // one Newton step onto the true intersection: moving dt along the ray // changes the signed distance by dt*dot(n, rd), so the residual maps // straight onto a correction in t float ndv0 = min(dot(n, rd), -0.05); tHit += clamp(-map(p).x/ndv0, 0.0, 1.2); p = ro + rd*tHit; n = calcNormal(p); float ndv = dot(n, rd); // sqrt(c*c + k) and not max(|c|, k): a hard clamp has an EDGE where it // engages, and on a flat wall that edge is a cone about the normal - a // ring painted across the surface float foot = tHit*gPX/sqrt(ndv*ndv + 0.003); vec3 em; col = shade(p, rd, n, mat, foot, em) + em; } else { col = skyCol(rd); tHit = FAR; } // ---- fog, and the ground mist under it ---- // // Two layers, because they behave differently: the distance haze is // uniform and eats the far end of the street, and the MIST sits below head // height and drifts, which is what makes the road glow and leaves the roofs // clear. One exponential cannot do both. float mistK = uMist*exp(-max(ro.y - 1.1, 0.0)*0.5); float fogAmt = 1.0 - exp(-uFog*tHit*(1.0 + 1.4*mistK)); col = mix(col, gFogCol, clamp(fogAmt, 0.0, 1.0)); // ---- in-scattering: the cones ---- if (uHaze > 0.005){ float tm = min(tHit, FAR); float jt = hash12(fragCoord + floor(T*30.0)); vec3 glow = vec3(0.0); for (int i = 0; i < 20; i++){ // Sample positions depend ONLY on i, never on where the ray landed. // Spacing them tm/20 apart ties the integral to tHit, which jumps // several metres between a ray that reaches the far plane and its // neighbour that clips a roof - a hard edge along every silhouette. // Quadratic in i: fixed in world space, dense near the camera where // the cone needs resolution, coarse where the fog has eaten it. float u = (float(i) + jt)/20.0; float s = 150.0*u*u; if (s > tm) break; float ds = 150.0*2.0*u/20.0; ds *= min(1.0, (tm - s)/max(ds, 1e-3)); vec3 q = ro + rd*s; // the mist layer, drifting, thickest just above the road float dens = 0.10 + mistK*exp(-max(q.y - 0.9, 0.0)*0.55) * (0.55 + 0.9*fbm(q.xz*0.045 + vec2(T*0.02, 0.0))); float lb = floor(q.z/gLS + 0.5); for (int k = 0; k < 3; k++){ float lid = lb + float(k) - 1.0; vec3 lp = lampPos(lid); vec3 L = lp - q; float dl = max(length(L), 0.5); float on = lampOn(lid)*smoothstep(1.8*gLS, 1.05*gLS, abs(q.z - lp.z)); // downward throw again, so the cone is a cone and not a ball float dn = 0.30 + 0.70*clamp(-L.y/dl, 0.0, 1.0); glow += gLampCol*on*dn*dens*ds*exp(-uFog*1.4*s)/(1.0 + 0.10*dl*dl); } if (gCeil > 0.5){ float sp = uStrip; float sd = floor(q.z/sp + 0.5); float dl = max(length(vec2(gCeil - 0.12 - q.y, q.z - sd*sp)), 0.6); glow += vec3(0.95, 0.97, 1.0)*step(hash11(sd*5.3), 0.94) * dens*ds*exp(-uFog*1.4*s)*uCeilBright/(1.2 + 0.5*dl); } } col += glow*uHaze*0.22*uLampBright; } // ---- the lens sees the lamps directly ---- // // A diagonal anamorphic streak, projected from each lamp's WORLD position // rather than found in the frame. It is a lens artefact, not a light, so it // belongs in the optics: this is the single thing present in every phone // photograph of an empty street at night and absent from every render. if (uFlare > 0.005){ float lb = floor((ro.z + 40.0)/gLS + 0.5); for (int i = 0; i < 4; i++){ float lid = lb + float(i) - 2.0; vec3 lp = lampPos(lid); vec3 d = lp - ro; float zc = dot(d, fw); if (zc < 1.0) continue; float on = lampOn(lid); if (on < 0.01) continue; vec2 sp = vec2(dot(d, rt), dot(d, up))*uLens/zc; vec2 q = uv - sp; float ang = 0.42; // the streak angle vec2 ax = vec2(cos(ang), sin(ang)); float al = dot(q, ax), ac = dot(q, vec2(-ax.y, ax.x)); float streak = exp(-abs(ac)/0.0075)*exp(-abs(al)/0.55); float halo = exp(-length(q)/0.035) + 0.35*exp(-length(q)/0.11); float atten = on*exp(-uFog*zc*0.9)/(1.0 + 0.006*zc*zc); col += gLampCol*(streak*0.85 + halo)*atten*uFlare*uLampBright; } } // ---- exposure and tape ---- col *= uExposure*mix(0.75, 1.55, gNight); col = col/(1.0 + col*0.82); vec2 sc = fragCoord/R; float nz = hash12(fragCoord + floor(T*24.0)*vec2(13.7, 91.3)); col += (nz - 0.5)*uGrain*(0.10 + 0.16*gNight); // a phone sensor at night float r2 = dot(uv, uv); col.r *= 1.0 - vhs*0.07*r2; col.b *= 1.0 - vhs*0.12*r2; col *= 1.0 - uVignette*1.15*dot(sc - 0.5, sc - 0.5); col = col*(1.0 - vhs*0.05) + vhs*0.016; // half an LSB before the 8-bit write: a night street is almost entirely // smooth low-amplitude gradient, which is exactly where quantisation shows // up as contour rings around every lamp - and the grain slider cannot be // relied on, because a viewer is entitled to set it to zero col += (hash12(fragCoord + 0.5) - 0.5)/255.0; gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0); }
forking #c3ixdpt7 — 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.