#version 450 core #include "common.glsl" // High-resolution 3D scenery (trees, bushes, rocks). Foliage uses wrap // lighting on its rounded custom normals plus sun translucency. in VS { vec3 worldPos; vec3 normal; vec2 uv; vec3 color; float height; } v; layout(binding = 0) uniform sampler2D uTex; uniform vec4 uBaseColor; uniform int uAlphaMask; uniform float uCutoff; uniform int uFoliage; uniform int uOccluderFade; // 1 in the main view: dither away scenery hiding the player uniform vec3 uEmissive; // linear HDR; multiplied by the emissive texture (unit 1) layout(binding = 1) uniform sampler2D uEmissiveTex; layout(location = 0) out vec4 fragColor; void main() { vec4 tex = texture(uTex, v.uv); if (uAlphaMask == 1) { // Sharpen alpha by its screen-space rate of change: keeps leaf coverage // stable through the mip chain instead of thinning out with distance. float a = (tex.a - uCutoff) / max(fwidth(tex.a), 1e-4) + 0.5; if (a < 0.5) discard; } if (uOccluderFade == 1 && uBlob.w > 0.0) { vec4 pc = uViewProj * vec4(uBlob.xyz + vec3(0.0, 0.6, 0.0), 1.0); vec2 ps = pc.xy / pc.w * 0.5 + 0.5; vec2 d = (gl_FragCoord.xy * uScreen.zw - ps) * vec2(uScreen.x * uScreen.w, 1.0); float fragDepth = (uViewProj * vec4(v.worldPos, 1.0)).w; // Only geometry above the sprite's head fades: low walls and trunk bases // stay solid, the player is still readable over them. float aboveHead = smoothstep(uBlob.y + 0.9, uBlob.y + 1.4, v.worldPos.y); float fade = (1.0 - smoothstep(0.07, 0.15, length(d))) * step(fragDepth, pc.w - 1.0) * aboveHead; if (ign(gl_FragCoord.xy) < fade * 0.85) discard; } vec3 wp = v.worldPos; vec3 n = normalize(v.normal); if (uFoliage == 0 && !gl_FrontFacing) n = -n; vec3 L = uSunDir.xyz; vec3 V = normalize(uCamPos.xyz - wp); vec3 albedo = tex.rgb * uBaseColor.rgb * v.color; float gao = groundAO(wp, v.height); float ndl = dot(n, L); float diffuse = uFoliage == 1 ? clamp((ndl + 0.45) / 1.45, 0.0, 1.0) : max(ndl, 0.0); // Offset along the normal and toward the sun to avoid acne on thin cards. float vis = sunShadow(wp + n * 0.06 + L * 0.08) * canopyCookie(wp); if (uFoliage == 1) vis = mix(0.3, 1.0, vis); // light scatters through leaves: soft self-shadowing vec3 sunRad = uSunColor.rgb * uSunDir.w; vec3 toCam = cross(uBillboardRight.xyz, vec3(0.0, 1.0, 0.0)); vec3 amb = hemiAmbient(n) + uSkyHorizon.rgb * uSkyHorizon.w * max(dot(n, toCam), 0.0); vec3 light = sunRad * diffuse * vis * mix(1.0, gao, 0.7) + amb * gao + pointLights(wp, n); if (uFoliage == 1) { // Backlit leaves glow; sunlit rims pick up a warm sheen. float back = pow(max(dot(-V, L), 0.0), 4.0); light += sunRad * back * vis * 0.35 * vec3(1.0, 1.05, 0.7); float rim = pow(1.0 - max(dot(n, V), 0.0), 3.0) * max(ndl, 0.0); light += sunRad * rim * vis * 0.25; } else { // Subtle sheen on rocks and bark. vec3 H = normalize(L + V); light += sunRad * pow(max(dot(n, H), 0.0), 24.0) * 0.06 * vis; } vec3 sheen = vec3(0.0); if (uWeather.y > 0.0) albedo = wetSurface(albedo, n, V, vis, sheen); vec3 col = albedo * light + sheen * gao; col += texture(uEmissiveTex, v.uv).rgb * uEmissive; if (uBlob.w > 0.0) { // player's contact shadow float d = length(wp.xz - uBlob.xz) / uBlob.w; float near = 1.0 - smoothstep(0.0, 0.6, abs(wp.y - uBlob.y)); col *= 1.0 - 0.4 * (1.0 - smoothstep(0.3, 1.0, d)) * near; } fragColor = vec4(col, 1.0); }