// Shared frame data and helpers. Mirrors FrameUBO in Renderer.cpp (std140). layout(std140, binding = 0) uniform Frame { mat4 uView; mat4 uProj; mat4 uViewProj; mat4 uInvViewProj; mat4 uLightViewProj; vec4 uCamPos; // xyz, w = time (seconds) vec4 uCamParams; // near, far, focus distance, unused vec4 uScreen; // target width, height, 1/width, 1/height vec4 uSunDir; // xyz toward sun, w = intensity vec4 uSunColor; // rgb vec4 uSkyZenith; // rgb, w = ambient intensity vec4 uSkyHorizon; // rgb, w = camera-side fill light intensity vec4 uSkyGround; // rgb vec4 uFogColor; // rgb, w = density vec4 uFogParams; // height falloff, base height, noise amount, anisotropy g vec4 uFogParams2; // god ray intensity, max distance, steps, unused vec4 uWind; // dir.xy, strength, gust speed vec4 uInteractor; // xyz, radius vec4 uBlob; // xyz, radius vec4 uClipPlane; // world-space plane for gl_ClipDistance[0] vec4 uBillboardRight;// camera-facing billboard right vector vec4 uShadowRight; // sun-facing billboard right vector (shadow cards) vec4 uCookie; // strength, scale, speed, coverage vec4 uShadowParams; // texel size, softness, unused, unused vec4 uShaft; // god ray beam strength, coverage, pattern scale, unused vec4 uWaterParams; // level, caustics intensity, has water, unused vec4 uGroundAO; // world -> uv: minX, minZ, 1/sizeX, 1/sizeZ (z = 0: disabled) vec4 uWeather; // rain intensity, surface wetness, rain fall speed, unused vec4 uSkyParams; // night (stars + moon disc at uSunDir), extra cloud cover, unused, unused ivec4 uLightCount; vec4 uLightPos[16]; // xyz, w = radius vec4 uLightColor[16]; }; #define uTime uCamPos.w #define PI 3.14159265359 layout(binding = 8) uniform sampler2DShadow uShadowMap; layout(binding = 9) uniform sampler2D uGroundAOTex; // ---------------------------------------------------------------- noise ---- float hash12(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * 0.1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); } float hash13(vec3 p3) { p3 = fract(p3 * 0.1031); p3 += dot(p3, p3.zyx + 31.32); return fract((p3.x + p3.y) * p3.z); } float vnoise(vec2 p) { vec2 i = floor(p), f = fract(p); vec2 u = f * f * (3.0 - 2.0 * f); return mix(mix(hash12(i), hash12(i + vec2(1, 0)), u.x), mix(hash12(i + vec2(0, 1)), hash12(i + vec2(1, 1)), u.x), u.y); } float vnoise3(vec3 p) { vec3 i = floor(p), f = fract(p); vec3 u = f * f * (3.0 - 2.0 * f); float a = mix(mix(hash13(i), hash13(i + vec3(1, 0, 0)), u.x), mix(hash13(i + vec3(0, 1, 0)), hash13(i + vec3(1, 1, 0)), u.x), u.y); float b = mix(mix(hash13(i + vec3(0, 0, 1)), hash13(i + vec3(1, 0, 1)), u.x), mix(hash13(i + vec3(0, 1, 1)), hash13(i + vec3(1, 1, 1)), u.x), u.y); return mix(a, b, u.z); } float fbm2(vec2 p) { float a = 0.5, s = 0.0; for (int i = 0; i < 4; ++i) { s += a * vnoise(p); p = p * 2.03 + 17.1; a *= 0.5; } return s / 0.9375; } // Interleaved gradient noise: cheap per-pixel dither for ray marching. float ign(vec2 p) { return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); } // ---------------------------------------------------------------- depth ---- float linearDepth(float d) { float n = uCamParams.x, f = uCamParams.y; float z = d * 2.0 - 1.0; return 2.0 * n * f / (f + n - z * (f - n)); } vec3 worldFromDepth(vec2 uv, float d) { vec4 p = uInvViewProj * vec4(uv * 2.0 - 1.0, d * 2.0 - 1.0, 1.0); return p.xyz / p.w; } // ------------------------------------------------------------- lighting ---- vec3 shadowCoord(vec3 wp) { vec4 lc = uLightViewProj * vec4(wp, 1.0); return lc.xyz / lc.w * 0.5 + 0.5; } float sunShadow(vec3 wp) { vec3 p = shadowCoord(wp); if (any(lessThan(p.xy, vec2(0.0))) || any(greaterThan(p.xyz, vec3(1.0)))) return 1.0; vec2 o = vec2(uShadowParams.x * uShadowParams.y); float s = 0.0; for (int y = -1; y <= 1; ++y) for (int x = -1; x <= 1; ++x) s += texture(uShadowMap, vec3(p.xy + vec2(x, y) * o, p.z)); return s / 9.0; } float sunShadowFast(vec3 wp) { vec3 p = shadowCoord(wp); if (any(lessThan(p.xy, vec2(0.0))) || any(greaterThan(p.xyz, vec3(1.0)))) return 1.0; return texture(uShadowMap, p); } float canopyPattern(vec3 wp, float scale, float coverage, float octaves) { const float canopyHeight = 14.0; vec3 L = uSunDir.xyz; vec2 p = wp.xz + L.xz / max(L.y, 0.15) * (canopyHeight - wp.y); p = p * scale + uTime * uCookie.z * vec2(1.0, 0.6); float n = octaves > 1.5 ? fbm2(p) : vnoise(p) * 0.75 + vnoise(p * 2.7 + 9.1) * 0.25; return smoothstep(coverage - 0.06, coverage + 0.06, n); } // Dappled light from an implied tree canopy high above the scene, projected // along the sun direction onto surfaces. float canopyCookie(vec3 wp) { if (uCookie.x <= 0.0) return 1.0; return mix(1.0, canopyPattern(wp, uCookie.y, uCookie.w, 4.0), uCookie.x); } // Sparse beams through the same implied canopy, used inside fog so sunlight // gathers into distinct god ray shafts instead of a uniform glow. float shaftCookie(vec3 wp) { if (uShaft.x <= 0.0) return 1.0; return mix(1.0, canopyPattern(wp, uShaft.z, uShaft.y, 1.0), uShaft.x); } float sunVisibility(vec3 wp) { return sunShadow(wp) * canopyCookie(wp); } vec3 hemiAmbient(vec3 n) { vec3 sky = mix(uSkyHorizon.rgb, uSkyZenith.rgb, 0.45); return mix(uSkyGround.rgb, sky, n.y * 0.5 + 0.5) * uSkyZenith.w; } vec3 pointLights(vec3 wp, vec3 n) { vec3 sum = vec3(0.0); for (int i = 0; i < uLightCount.x; ++i) { vec3 d = uLightPos[i].xyz - wp; float dist = length(d); float r = uLightPos[i].w; if (dist >= r) continue; float att = 1.0 - (dist * dist) / (r * r); att *= att; float ndl = clamp((dot(n, d / max(dist, 1e-4)) + 0.4) / 1.4, 0.0, 1.0); sum += uLightColor[i].rgb * att * ndl; } return sum; } // Baked contact occlusion under scenery (tree bases, bushes, rocks). `height` // is the shaded point's height above the ground it stands on: the darkening // only applies near the ground. float groundAO(vec3 wp, float height) { if (uGroundAO.z <= 0.0) return 1.0; float ao = texture(uGroundAOTex, (wp.xz - uGroundAO.xy) * uGroundAO.zw).r; return mix(ao, 1.0, smoothstep(0.0, 1.2, height)); } // Rain-soaked surfaces: darker albedo plus a sky/sun sheen on upward faces. vec3 wetSurface(vec3 albedo, vec3 n, vec3 V, float vis, out vec3 sheen) { float wet = uWeather.y * smoothstep(0.2, 0.8, n.y); vec3 R = reflect(-V, n); float F = 0.04 + 0.96 * pow(1.0 - max(dot(n, V), 0.0), 5.0); sheen = wet * (uSkyHorizon.rgb * uSkyZenith.w * F * 0.6 + uSunColor.rgb * uSunDir.w * pow(max(dot(R, uSunDir.xyz), 0.0), 60.0) * 0.5 * vis); return albedo * mix(1.0, 0.68, uWeather.y); } // Cloud layer opacity toward `dir` (0 at and below the horizon). Rain and // uSkyParams.y (overcast) thicken it. float skyClouds(vec3 dir) { if (dir.y <= 0.0) return 0.0; float cover = max(uWeather.x, uSkyParams.y); vec2 cp = dir.xz / (dir.y + 0.12) * 1.3 + uTime * vec2(0.012, 0.004); return smoothstep(0.5 - 0.45 * cover, 0.85 - 0.3 * cover, fbm2(cp)) * 0.55 * smoothstep(0.0, 0.2, dir.y); } vec3 skyColor(vec3 dir) { float h = dir.y; vec3 c = mix(uSkyHorizon.rgb, uSkyZenith.rgb, pow(clamp(h, 0.0, 1.0), 0.55)); if (h < 0.0) c = mix(uSkyHorizon.rgb, uSkyGround.rgb, clamp(-h * 3.0, 0.0, 1.0)); float sd = max(dot(dir, uSunDir.xyz), 0.0); float night = uSkyParams.x; if (h > 0.0) { // Clouds catch the sun by day; at night they are faintly moonlit. vec3 lit = mix(uSunColor.rgb * (0.75 + 0.6 * sd), uSkyHorizon.rgb * 1.6 + uSunColor.rgb * uSunDir.w * (0.02 + 0.3 * pow(sd, 8.0)), night); c = mix(c, lit, skyClouds(dir)); } // Sun disc + halo (the moon replaces it at night, overcast hides it). float sunVis = (1.0 - night) * (1.0 - uSkyParams.y) * clamp(uSunDir.w * 2.0, 0.0, 1.0); c += uSunColor.rgb * (pow(sd, 900.0) * 40.0 + pow(sd, 10.0) * 0.3) * sunVis; return c; } // Animated caustic web (after joltz0r's "water turbulence"). float caustics(vec2 uv, float time) { vec2 p = mod(uv * 6.28318, 6.28318) - 250.0; vec2 i = p; float c = 1.0; const float inten = 0.005; for (int n = 0; n < 4; ++n) { float t = time * (1.0 - (3.5 / float(n + 1))); i = p + vec2(cos(t - i.x) + sin(t + i.y), sin(t - i.y) + cos(t + i.x)); c += 1.0 / length(vec2(p.x / (sin(i.x + t) / inten), p.y / (cos(i.y + t) / inten))); } c /= 4.0; c = 1.17 - pow(c, 1.4); return pow(abs(c), 8.0); }