fix(A7): port retail calc_point_light (1-dist/falloff) ramp — kill the "spotlight" hard edge (#133)
The dungeon/house/outdoor lights read as hard-edged blown discs ("spotlights")
because our point/spot shader used `atten = 1.0` flat inside a hard `d < range`
cutoff. The mesh.frag comment claimed this was retail-faithful ("no attenuation
inside Range... the bubble-of-light look relies on crisp boundaries", citing
r13 10.2) — that was a misread and the literal cause of the symptom.
Verified against the decomp (not guessed): calc_point_light (0x0059c8b0, the
PER-VERTEX point-light path that lights static walls) scales each light's
contribution by (1 - dist/falloff_eff) — a LINEAR ramp that fades to exactly 0
at the edge, eliminating the hard disc. falloff_eff = Falloff * static_light_factor,
and static_light_factor = 1.3 (0x00820e24), NOT the 1.5 config_hardware_light
rangeAdjust (that 1.5 is the D3D-dynamic path for moving objects, a different
path). The Ghidra port (acclient.c:808639) is more garbled — BN pseudo-C is the
oracle here; the exact normalization factor + a half-Lambert wrap (0.5*dist+N*L)
are x87-obscured (same artifact class as GetPowerBarLevel) and left unported.
Changes:
- mesh_modern.frag + mesh.frag: replace flat atten with clamp(1 - d/range, 0, 1);
Range now carries falloff_eff so the ramp fades to 0 at the cutoff. Fix the
false "no attenuation / crisp bubble" comment in mesh.frag.
- LightInfoLoader: Range = Falloff * 1.3 (static_light_factor), was * 1.5.
- LightManager: correct the stale class doc comment (Tick is now nearest-8
allocation-free partial-select with NO viewer-range slack filter).
- divergence register: AP-16 updated (slack filter removed), AP-35 added
(per-pixel vs per-vertex Gouraud; dropped half-Lambert wrap + normalization).
- test: LightingHookSinkTests Range 8*1.3 = 10.4.
Build + 20 lighting tests green. Visual gate pending (game-wide lighting change:
dungeon torches, house candles, outdoor braziers).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5872bcf075
commit
007e287309
6 changed files with 44 additions and 26 deletions
|
|
@ -46,10 +46,12 @@ layout(std140, binding = 1) uniform SceneLighting {
|
|||
vec4 uCameraAndTime;
|
||||
};
|
||||
|
||||
// Retail hard-cutoff lighting equation (r13 §10.2). No distance
|
||||
// attenuation inside Range; hard edge at Range; spotlights use a
|
||||
// binary cos-cone test. This is deliberate — the retail "bubble of
|
||||
// light" look relies on crisp boundaries.
|
||||
// Retail per-vertex point-light ramp (calc_point_light 0x0059c8b0): the
|
||||
// contribution scales by (1 - dist/falloff_eff) — a LINEAR fade to exactly
|
||||
// 0 at the edge, NOT a hard-cutoff bubble. (The prior "no attenuation inside
|
||||
// Range / crisp boundaries" note was a misread; it is the literal cause of
|
||||
// the #133 "spotlight" look. falloff_eff = Falloff * static_light_factor 1.3
|
||||
// is folded into Range by LightInfoLoader.) Spots add a binary cos-cone test.
|
||||
vec3 accumulateLights(vec3 N, vec3 worldPos) {
|
||||
vec3 lit = uCellAmbient.xyz;
|
||||
int activeLights = int(uCellAmbient.w);
|
||||
|
|
@ -73,7 +75,9 @@ vec3 accumulateLights(vec3 N, vec3 worldPos) {
|
|||
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; // retail: no attenuation inside Range
|
||||
// calc_point_light (1 - dist/falloff_eff) linear ramp; Range already
|
||||
// carries falloff_eff (Falloff * 1.3), so it fades to 0 at the cutoff.
|
||||
float atten = clamp(1.0 - d / max(range, 1e-3), 0.0, 1.0);
|
||||
if (kind == 2) {
|
||||
// Spotlight: hard-edged cos-cone test.
|
||||
float cos_edge = cos(uLights[i].coneAngleEtc.x * 0.5);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,15 @@ vec3 accumulateLights(vec3 N, vec3 worldPos) {
|
|||
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;
|
||||
// Retail per-vertex point-light ramp (calc_point_light 0x0059c8b0,
|
||||
// line 0x0059c9a2): contribution scales by (1 - dist/falloff_eff), a
|
||||
// LINEAR fade to exactly 0 at the edge. That is what makes a torch a
|
||||
// smooth glow that blends into the ambient instead of a flat disc with
|
||||
// a hard edge — the dungeon/house/outdoor "spotlight" look (#133 A7).
|
||||
// falloff_eff = Falloff * static_light_factor (1.3, 0x00820e24) is folded
|
||||
// into the shader Range (dirAndRange.w) by LightInfoLoader, so the ramp
|
||||
// denominator is just Range and fades to 0 exactly at the cutoff.
|
||||
float atten = clamp(1.0 - d / max(range, 1e-3), 0.0, 1.0);
|
||||
if (kind == 2) {
|
||||
float cos_edge = cos(uLights[i].coneAngleEtc.x * 0.5);
|
||||
float cos_l = dot(-Ldir, uLights[i].dirAndRange.xyz);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue