fix(render): A7 Fix D D-1 — clamp the point-light sum on its own (#140)

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) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-18 17:29:45 +02:00
parent 39c70f00aa
commit cf62793304

View file

@ -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;