From cf62793304e58846df44cbf7486efe98a9738949 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 18 Jun 2026 17:29:45 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20A7=20Fix=20D=20D-1=20=E2=80=94?= =?UTF-8?q?=20clamp=20the=20point-light=20sum=20on=20its=20own=20(#140)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit accumulateLights folded ambient+sun+torches into one accumulator clamped only in the frag, so a few warm intensity-100 torches blew walls/objects to white. Mirror retail SetStaticLightingVertexColors: sum point/spot into pointAcc, clamp to [0,1] (the baked emissive), THEN add ambient+sun, frag final-clamps. Matches LightBake.ComputeVertexColor (LightBakeConformanceTests). Per-light cap unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Rendering/Shaders/mesh_modern.vert | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert index 2efd4a96..667db74d 100644 --- a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert +++ b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert @@ -183,29 +183,33 @@ vec3 pointContribution(vec3 N, vec3 worldPos, GlobalLight L) { vec3 accumulateLights(vec3 N, vec3 worldPos, int instanceIndex) { vec3 lit = uCellAmbient.xyz; - // SUN / directional — from the SceneLighting UBO (global; the audit cleared - // the ambient + sun chain as already faithful). Any point/spot entries still - // present in the UBO from LightManager.Tick are IGNORED here — point lights - // now come per-object from the SSBO below, so there's no double-count. + // SUN / directional — material-lit term (added with ambient, NOT into the + // torch sum), unchanged. int activeLights = int(uCellAmbient.w); for (int i = 0; i < 8; ++i) { if (i >= activeLights) break; if (int(uLights[i].posAndKind.w) != 0) continue; // directional only - vec3 Ldir = -uLights[i].dirAndRange.xyz; // forward points INTO the scene + vec3 Ldir = -uLights[i].dirAndRange.xyz; float ndl = max(0.0, dot(N, Ldir)); lit += uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w * ndl; } - // POINT / SPOT — THIS object's selected set (minimize_object_lighting): 8 int - // slots per instance into the global light buffer, -1 = unused. Camera- - // independent, so a wall's torches light it the same regardless of viewer pos. + // POINT / SPOT torches: their OWN accumulator (A7 Fix D, D-1). Retail's + // SetStaticLightingVertexColors sums the static point lights from BLACK and + // clamps the SUM to [0,1] before anything else (a baked emissive term), so a + // few warm intensity-100 torches can't push the whole pixel to white the way + // folding them into ambient+sun did. Mirrors LightBake.ComputeVertexColor + // (LightBakeConformanceTests). Per-light cap inside pointContribution is unchanged. + vec3 pointAcc = vec3(0.0); int base = instanceIndex * 8; for (int k = 0; k < 8; ++k) { int gi = instanceLightIdx[base + k]; if (gi < 0) continue; - lit += pointContribution(N, worldPos, gLights[gi]); + pointAcc += pointContribution(N, worldPos, gLights[gi]); } - return lit; + lit += min(pointAcc, vec3(1.0)); // clamp the torch sum on its own (retail baked emissive) + + return lit; // frag still does the final min(lit, 1.0) } out vec3 vNormal;