#version 430 core #extension GL_ARB_bindless_texture : require in vec3 vNormal; in vec2 vTexCoord; in vec3 vWorldPos; in flat uvec2 vTextureHandle; in flat uint vTextureLayer; // uRenderPass values (Phase N.5 Decision 2 — two-pass alpha-test): // 0 = opaque pass — discard fragments with alpha < 0.95 // (lets the depth write succeed for solid pixels) // 1 = translucent pass — covers AlphaBlend / Additive / InvAlpha; // discard alpha >= 0.95 (already drawn opaque) and // alpha < 0.05 (skip empty fragments — large // transparent overdraw cost otherwise) uniform int uRenderPass; // SceneLighting UBO — IDENTICAL layout to mesh_instanced.frag binding=1. struct Light { vec4 posAndKind; vec4 dirAndRange; vec4 colorAndIntensity; vec4 coneAngleEtc; }; layout(std140, binding = 1) uniform SceneLighting { Light uLights[8]; vec4 uCellAmbient; vec4 uFogParams; vec4 uFogColor; vec4 uCameraAndTime; }; vec3 accumulateLights(vec3 N, vec3 worldPos) { vec3 lit = uCellAmbient.xyz; int activeLights = int(uCellAmbient.w); for (int i = 0; i < 8; ++i) { if (i >= activeLights) break; int kind = int(uLights[i].posAndKind.w); vec3 Lcol = uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w; if (kind == 0) { vec3 Ldir = -uLights[i].dirAndRange.xyz; float ndl = max(0.0, dot(N, Ldir)); lit += Lcol * ndl; } else { vec3 toL = uLights[i].posAndKind.xyz - worldPos; float d = length(toL); float range = uLights[i].dirAndRange.w; if (d < range && range > 1e-3) { vec3 Ldir = toL / max(d, 1e-4); float ndl = max(0.0, dot(N, Ldir)); float atten = 1.0; if (kind == 2) { float cos_edge = cos(uLights[i].coneAngleEtc.x * 0.5); float cos_l = dot(-Ldir, uLights[i].dirAndRange.xyz); atten *= (cos_l > cos_edge) ? 1.0 : 0.0; } lit += Lcol * ndl * atten; } } } return lit; } vec3 applyFog(vec3 lit, vec3 worldPos) { int mode = int(uFogParams.w); if (mode == 0) return lit; float d = length(worldPos - uCameraAndTime.xyz); float fogStart = uFogParams.x; float fogEnd = uFogParams.y; float span = max(1e-3, fogEnd - fogStart); float fog = clamp((d - fogStart) / span, 0.0, 1.0); return mix(lit, uFogColor.xyz, fog); } out vec4 FragColor; void main() { sampler2DArray tex = sampler2DArray(vTextureHandle); vec4 color = texture(tex, vec3(vTexCoord, float(vTextureLayer))); // Two-pass alpha-test (N.5 Decision 2). if (uRenderPass == 0) { if (color.a < 0.95) discard; // opaque pass } else { if (color.a >= 0.95) discard; // transparent pass if (color.a < 0.05) discard; // skip totally-empty } vec3 N = normalize(vNormal); vec3 lit = accumulateLights(N, vWorldPos); // Lightning flash — additive scene bump (matches mesh_instanced.frag). lit += uFogParams.z * vec3(0.6, 0.6, 0.75); // Retail clamp per-channel to 1.0 (r13 §13.1). lit = min(lit, vec3(1.0)); vec3 rgb = color.rgb * lit; rgb = applyFog(rgb, vWorldPos); FragColor = vec4(rgb, color.a); }