fix(render): A7 Fix D D-3/D-4 — two-path lighting (objects plain-Lambert+sun, EnvCell wrap+no-sun) (#140)

mesh_modern unified all meshes into one calc_point_light path: it applied the
bake's half-Lambert wrap to objects (lighting character backs from a torch behind
them) and added the sun to EnvCell building shells (warm facade wash). Retail
splits these: objects = hardware plain Lambert max(0,N.L) + sun; EnvCell walls =
baked wrap, dynamics only, NO sun (minimize_envcell_lighting). Add a per-draw
uLightingMode (WbDrawDispatcher=0 object, EnvCellRenderer=1 envcell) selecting the
angular term (wrap vs plain Lambert) and gating the sun. Per-light cap + D-1 clamp
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-18 21:38:30 +02:00
parent 156dc453c9
commit 0980bea48d
3 changed files with 26 additions and 15 deletions

View file

@ -122,6 +122,7 @@ uniform mat4 uViewProjection;
// _opaqueDrawCount before the transparent MDI call, matching WorldBuilder's // _opaqueDrawCount before the transparent MDI call, matching WorldBuilder's
// uDrawIDOffset pattern in BaseObjectRenderManager.cs line 845. // uDrawIDOffset pattern in BaseObjectRenderManager.cs line 845.
uniform int uDrawIDOffset; uniform int uDrawIDOffset;
uniform int uLightingMode; // A7 Fix D: 0 = OBJECT (plain Lambert + sun), 1 = ENVCELL (half-Lambert wrap, no sun)
// SceneLighting UBO — binding=1 in the UBO namespace (GL keeps the SSBO and UBO // SceneLighting UBO — binding=1 in the UBO namespace (GL keeps the SSBO and UBO
// binding tables separate, so this coexists with the binding=1 BatchBuffer SSBO // binding tables separate, so this coexists with the binding=1 BatchBuffer SSBO
@ -157,16 +158,19 @@ vec3 pointContribution(vec3 N, vec3 worldPos, GlobalLight L) {
float d = sqrt(distsq); float d = sqrt(distsq);
float range = L.dirAndRange.w; // falloff_eff = Falloff × 1.3 float range = L.dirAndRange.w; // falloff_eff = Falloff × 1.3
if (d >= range || range <= 1e-4) return vec3(0.0); if (d >= range || range <= 1e-4) return vec3(0.0);
// Half-Lambert WRAP: (1/1.5)·(N·D + 0.5·d). N·D = d·cosθ (D un-normalised); the // A7 Fix D D-3: angular term by lighting path. ENVCELL bake (mode 1) keeps the
// +0.5·d bias lets a face angled AWAY from the torch still catch light (retail's // half-Lambert wrap (lights surfaces angled away, retail calc_point_light); OBJECT
// soft terminator). wrap≤0 = fully shadowed. TwoLpr=1.5, WrapBias=0.5. // mode (0) uses plain Lambert max(0,N·L) so a torch BEHIND a character contributes
float wrap = (1.0 / 1.5) * (dot(N, toL) + 0.5 * d); // nothing (retail's hardware path). toL is un-normalised (length d).
if (wrap <= 0.0) return vec3(0.0); float angular = (uLightingMode == 1)
? (1.0 / 1.5) * (dot(N, toL) + 0.5 * d) // half-Lambert wrap (EnvCell bake)
: max(0.0, dot(N, toL)); // plain Lambert (object/hardware)
if (angular <= 0.0) return vec3(0.0);
// NORM branch (distance-cube): >1 m → distsq·d ≈ inverse-square soft far halo; // NORM branch (distance-cube): >1 m → distsq·d ≈ inverse-square soft far halo;
// <1 m → just d (dodge the near singularity). "Punchy near, soft far." // <1 m → just d (dodge the near singularity). "Punchy near, soft far."
float norm = (distsq > 1.0) ? (distsq * d) : d; float norm = (distsq > 1.0) ? (distsq * d) : d;
float intensity = L.colorAndIntensity.w; float intensity = L.colorAndIntensity.w;
float scale = (1.0 - d / range) * intensity * (wrap / norm); float scale = (1.0 - d / range) * intensity * (angular / norm);
if (kind == 2) { if (kind == 2) {
// Spotlight: hard-edged cos-cone gate layered on the point ramp. // Spotlight: hard-edged cos-cone gate layered on the point ramp.
vec3 Ldir = toL / max(d, 1e-4); vec3 Ldir = toL / max(d, 1e-4);
@ -183,8 +187,10 @@ vec3 pointContribution(vec3 N, vec3 worldPos, GlobalLight L) {
vec3 accumulateLights(vec3 N, vec3 worldPos, int instanceIndex) { vec3 accumulateLights(vec3 N, vec3 worldPos, int instanceIndex) {
vec3 lit = uCellAmbient.xyz; vec3 lit = uCellAmbient.xyz;
// SUN / directional — material-lit term (added with ambient, NOT into the // SUN / directional — OBJECT path only (mode 0). retail's EnvCell path
// torch sum), unchanged. // (minimize_envcell_lighting) enables only dynamic lights, NEVER the sun, so
// EnvCell walls (mode 1) get no directional sun wash (A7 Fix D D-4).
if (uLightingMode == 0) {
int activeLights = int(uCellAmbient.w); int activeLights = int(uCellAmbient.w);
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
if (i >= activeLights) break; if (i >= activeLights) break;
@ -193,6 +199,7 @@ vec3 accumulateLights(vec3 N, vec3 worldPos, int instanceIndex) {
float ndl = max(0.0, dot(N, Ldir)); float ndl = max(0.0, dot(N, Ldir));
lit += uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w * ndl; lit += uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w * ndl;
} }
}
// POINT / SPOT torches: their OWN accumulator (A7 Fix D, D-1). Retail's // POINT / SPOT torches: their OWN accumulator (A7 Fix D, D-1). Retail's
// SetStaticLightingVertexColors sums the static point lights from BLACK and // SetStaticLightingVertexColors sums the static point lights from BLACK and

View file

@ -877,6 +877,7 @@ public sealed unsafe class EnvCellRenderer : IDisposable
// WB EnvCellRenderManager.cs:406-409: uniform state setup. // WB EnvCellRenderManager.cs:406-409: uniform state setup.
_shader.SetInt("uRenderPass", (int)renderPass); _shader.SetInt("uRenderPass", (int)renderPass);
_shader.SetInt("uFilterByCell", 0); _shader.SetInt("uFilterByCell", 0);
_shader.SetInt("uLightingMode", 1); // A7 Fix D D-3/D-4: EnvCell bake (wrap points, no sun)
// Phase U.4 ROOT-CAUSE FIX (cell-shell flicker / "transparent walls when // Phase U.4 ROOT-CAUSE FIX (cell-shell flicker / "transparent walls when
// moving"): upload uViewProjection HERE rather than inheriting it from // moving"): upload uViewProjection HERE rather than inheriting it from

View file

@ -893,6 +893,9 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
_indoorProbeFrameCounter++; _indoorProbeFrameCounter++;
var vp = camera.View * camera.Projection; var vp = camera.View * camera.Projection;
_shader.SetMatrix4("uViewProjection", vp); _shader.SetMatrix4("uViewProjection", vp);
// A7 Fix D D-3/D-4: object path — plain Lambert points + sun. MUST set
// explicitly (shared GL uniform; EnvCellRenderer sets it to 1).
_shader.SetInt("uLightingMode", 0);
// #128 self-heal: fresh re-request dedup per Draw pass. // #128 self-heal: fresh re-request dedup per Draw pass.
_missRequested.Clear(); _missRequested.Clear();