in vec3 WorldPos; in vec3 Normal; in vec3 ViewNormal; in vec3 Albedo; in vec4 LightSpacePos; in float ViewDepth; in float Wear; in vec4 Wet; layout (location = 0) out vec4 OutColor; layout (location = 1) out vec4 OutNormalDepth; // read by the ink pass uniform vec3 viewPos; uniform vec3 lightDirection; uniform vec3 lightColor; uniform vec3 shadowColor; uniform vec3 skyTop; uniform vec3 skyHorizon; uniform float shadowStrength; uniform float boilTime; uniform float terrainSize; uniform float decalSize; uniform float grass; uniform float rainWet; // 0..1 everything wet from rain: darker, the ground damp uniform sampler2D shadowMap; uniform sampler2D waterMap; uniform sampler2D surfaceMap; // asphalt, concrete, gravel, bare earth (Site_surface) uniform vec4 holes[8]; // holes in the ground (basements): middle xz, x axis (cos, sin) ... uniform vec2 holeSize[8]; // ... half sizes uniform int holeCount; uniform vec4 tint; // rgb: darkens (charred wood), a: glows like embers uniform float puff; // > 0: smoke - soft and see-through, this thick (edges fading out) const vec3 PATH_EARTH = vec3(0.58, 0.44, 0.28); const vec3 MUD = vec3(0.31, 0.23, 0.15); const vec3 TAR = vec3(0.27, 0.27, 0.30); const vec3 CONCRETE = vec3(0.72, 0.71, 0.68); const vec3 STONES_DARK = vec3(0.48, 0.46, 0.42); const vec3 STONES_PALE = vec3(0.76, 0.71, 0.61); // What the ground is made of, painted: tar with grit and patches, concrete, // gravel of mixed stones, soil. Part weights (a grassy middle strip, frayed // edges) become patches with ragged borders, not a blend. vec3 surfaceColor(vec3 albedo, vec4 s, vec3 p, float patches) { float sum = dot(s, vec4(1.0)); if (sum < 0.004) return albedo; float mottle = valueNoise(vec3(p.xz * 0.35, 2.0)); float grit = valueNoise(vec3(p.xz * 13.0, 4.0)); float pebble = valueNoise(vec3(p.xz * 17.0, 5.0)) * 0.6 + valueNoise(vec3(p.xz * 41.0, 6.0)) * 0.4; vec3 tar = TAR * (0.82 + 0.3 * mottle) + (grit - 0.5) * 0.06; vec3 concrete = CONCRETE * (0.86 + 0.22 * mottle) + (grit - 0.5) * 0.05; vec3 gravel = mix(STONES_DARK, STONES_PALE, 0.25 + 0.5 * smoothstep(0.25, 0.75, pebble)) * (0.88 + 0.2 * mottle); vec3 earth = PATH_EARTH * (0.78 + 0.32 * mottle) * (0.92 + 0.16 * grit); vec3 ground = (tar * s.r + concrete * s.g + gravel * s.b + earth * s.a) / sum; float edge = valueNoise(vec3(p.xz * 2.5, 7.0)) * 0.75 + grit * 0.25; // ragged, painted borders float cover = smoothstep(edge - 0.12, edge + 0.12, sum * 1.25 - 0.1); return mix(albedo, ground * (0.9 + 0.2 * patches), cover); } const float WATER_SHOWN = 25.0; // mm of standing water at a full texture value (wetness.c) void main() { if (terrainSize > 0.0) { for (int i = 0; i < holeCount; i++) { vec2 d = WorldPos.xz - holes[i].xy; vec2 uv = vec2(dot(d, holes[i].zw), dot(d, vec2(-holes[i].w, holes[i].z))); if (all(lessThanEqual(abs(uv), holeSize[i]))) discard; } } vec3 n = normalize(Normal); vec3 l = normalize(lightDirection); // Glowing colors (flames) are stored 2 higher: no light, no shade if (Albedo.r > 1.5) { vec3 glow = (Albedo - 2.0) * (0.85 + 0.3 * valueNoise(WorldPos * 6.0 + boilTime * 3.0)); OutColor = vec4(applyFog(glow, length(viewPos - WorldPos), skyHorizon), 1.0); OutNormalDepth = vec4(normalize(ViewNormal), ViewDepth); return; } vec4 paint = paintAlbedo(Albedo, WorldPos, boilTime); vec3 albedo = paint.rgb * tint.rgb; // Meadow wear: grass bends pale, then gives way to a bare earth path if (terrainSize > 0.0) { float bent = smoothstep(0.08, 0.40, Wear + (paint.w - 0.5) * 0.1); float bare = smoothstep(0.45, 0.85, Wear + (paint.w - 0.5) * 0.15); albedo = mix(albedo, albedo * vec3(1.12, 1.05, 0.70) + vec3(0.07, 0.05, 0.0), bent); albedo = mix(albedo, PATH_EARTH * (0.85 + 0.3 * paint.w), bare); vec4 surface = texture(surfaceMap, WorldPos.xz / terrainSize + 0.5); albedo = surfaceColor(albedo, surface, WorldPos, paint.w); n = normalize(mix(n, vec3(0.0, 1.0, 0.0), 0.8 * min(surface.r + surface.g, 1.0))); // tar and concrete lie smooth } // Wet ground: damp soil darkens, pee stains, mud, and standing water on top. // The cells are blurred and pushed around by noise: ragged, painted edges. vec4 wet = vec4(0.0); float edge = 0.0; if (terrainSize > 0.0 || (decalSize > 0.0 && n.y > 0.7)) { float size = max(terrainSize, decalSize); wet = texture(waterMap, WorldPos.xz / size + 0.5); if (dot(wet, vec4(1.0)) > 0.0) { // (dry almost everywhere: no noise there) vec2 bend = (vec2(valueNoise(vec3(WorldPos.xz * 2.2, 3.1)), valueNoise(vec3(WorldPos.zx * 2.2, 8.7))) - 0.5) * 0.3; wet = texture(waterMap, (WorldPos.xz + bend) / size + 0.5); edge = valueNoise(vec3(WorldPos.xz * 7.0, 1.7)) - 0.5; } } else if (grass > 0.0) { wet = Wet; } float depth = wet.g * wet.g * WATER_SHOWN; vec4 some = step(vec4(0.004), wet); // dry ground stays exactly as it was float puddle = smoothstep(0.9, 1.4, depth + edge * 1.2 * some.g); float damp = smoothstep(0.06, 0.3, wet.r + edge * 0.15 * some.r) * (0.55 + 0.45 * wet.r); bool ground = terrainSize > 0.0 || grass > 0.0 || (decalSize > 0.0 && n.y > 0.7); damp = max(damp, rainWet * (ground ? 0.8 : 0.0)); if (!ground) albedo *= 1.0 - 0.18 * rainWet; // (walls, roofs, people: a little darker wet) float mud = smoothstep(0.15, 0.35, wet.a + edge * 0.25 * some.a) * (0.6 + 0.4 * wet.a); float stain = smoothstep(0.1, 0.4, wet.b + edge * 0.2 * some.b); albedo *= mix(1.0, 0.58, damp); albedo = mix(albedo, albedo * vec3(1.25, 1.08, 0.45), stain * 0.6); albedo = mix(albedo, MUD * (0.8 + 0.45 * paint.w) * mix(1.0, 0.75, wet.r), mud); albedo *= mix(1.0, 0.55, puddle); // the bottom under the water float visible = mix(1.0, shadowVisibility(shadowMap, LightSpacePos, n, l), shadowStrength); vec3 color = paintLight(albedo, n, l, visible, WorldPos, boilTime, lightColor, shadowColor, skyTop, 0.0); color += fireGlow(albedo, n, WorldPos); if (tint.a > 0.0) { // embers: glowing cracks that flicker float crack = valueNoise(WorldPos * 14.0 + boilTime * 0.7); vec3 ember = vec3(1.0, 0.32, 0.06) * (0.6 + 0.8 * crack); color = mix(color, ember, tint.a * smoothstep(0.25, 0.65, crack + tint.a * 0.3)); } if (puddle > 0.0 || mud > 0.0) { // Water mirrors the sky in flat painted tones, with lighter streaks and a // darker wet rim; the sun glints. Wet mud shines a little. vec3 v = normalize(viewPos - WorldPos); vec2 ripple = vec2(valueNoise(vec3(WorldPos.xz * 3.0, boilTime * 0.6)), valueNoise(vec3(WorldPos.zx * 3.0 + 7.0, boilTime * 0.6))) - 0.5; vec3 wn = normalize(vec3(ripple.x * 0.12, 1.0, ripple.y * 0.12)); vec3 r = reflect(-v, wn); float fres = pow(1.0 - max(dot(wn, v), 0.0), 4.0); vec3 sky = mix(skyHorizon, skyTop, smoothstep(0.0, 0.6, r.y)); float streak = smoothstep(0.62, 0.7, valueNoise(vec3(WorldPos.x * 1.5 + WorldPos.z * 4.0, WorldPos.z * 1.5, boilTime * 0.3))); float glint = smoothstep(0.985, 0.995, dot(r, l)) * visible; float mirror = clamp(0.25 + 0.6 * fres + 0.3 * streak, 0.0, 0.9); vec3 water = mix(color * vec3(0.75, 0.85, 0.95), mix(sky, skyHorizon, 0.35) * 0.9, mirror); water = mix(water, water * vec3(1.12, 1.0, 0.55), stain * 0.45); // pee water *= 1.0 - 0.35 * smoothstep(0.0, 0.5, puddle) * (1.0 - smoothstep(0.5, 1.0, puddle)); // wet rim color = mix(color, water + lightColor * glint, puddle); color += lightColor * glint * mud * wet.r * 0.4; } color = applyFog(color, length(viewPos - WorldPos), skyHorizon); float alpha = 1.0; if (puff > 0.0) { // smoke: thickest where it faces you, fading to nothing at the rim float facing = abs(normalize(ViewNormal).z); alpha = puff * smoothstep(0.05, 0.85, facing); color = mix(color, skyHorizon, 0.15); } OutColor = vec4(color, alpha); OutNormalDepth = vec4(normalize(ViewNormal), ViewDepth); }