From 0980bea48dfcc8b94b524b11f6a1a898b86f9524 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 18 Jun 2026 21:38:30 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20A7=20Fix=20D=20D-3/D-4=20?= =?UTF-8?q?=E2=80=94=20two-path=20lighting=20(objects=20plain-Lambert+sun,?= =?UTF-8?q?=20EnvCell=20wrap+no-sun)=20(#140)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../Rendering/Shaders/mesh_modern.vert | 37 +++++++++++-------- .../Rendering/Wb/EnvCellRenderer.cs | 1 + .../Rendering/Wb/WbDrawDispatcher.cs | 3 ++ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert index 667db74d..78011f66 100644 --- a/src/AcDream.App/Rendering/Shaders/mesh_modern.vert +++ b/src/AcDream.App/Rendering/Shaders/mesh_modern.vert @@ -122,6 +122,7 @@ uniform mat4 uViewProjection; // _opaqueDrawCount before the transparent MDI call, matching WorldBuilder's // uDrawIDOffset pattern in BaseObjectRenderManager.cs line 845. 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 // 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 range = L.dirAndRange.w; // falloff_eff = Falloff × 1.3 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 - // +0.5·d bias lets a face angled AWAY from the torch still catch light (retail's - // soft terminator). wrap≤0 = fully shadowed. TwoLpr=1.5, WrapBias=0.5. - float wrap = (1.0 / 1.5) * (dot(N, toL) + 0.5 * d); - if (wrap <= 0.0) return vec3(0.0); + // A7 Fix D D-3: angular term by lighting path. ENVCELL bake (mode 1) keeps the + // half-Lambert wrap (lights surfaces angled away, retail calc_point_light); OBJECT + // mode (0) uses plain Lambert max(0,N·L) so a torch BEHIND a character contributes + // nothing (retail's hardware path). toL is un-normalised (length d). + 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; // <1 m → just d (dodge the near singularity). "Punchy near, soft far." float norm = (distsq > 1.0) ? (distsq * d) : d; 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) { // Spotlight: hard-edged cos-cone gate layered on the point ramp. vec3 Ldir = toL / max(d, 1e-4); @@ -183,15 +187,18 @@ vec3 pointContribution(vec3 N, vec3 worldPos, GlobalLight L) { vec3 accumulateLights(vec3 N, vec3 worldPos, int instanceIndex) { vec3 lit = uCellAmbient.xyz; - // 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; - float ndl = max(0.0, dot(N, Ldir)); - lit += uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w * ndl; + // SUN / directional — OBJECT path only (mode 0). retail's EnvCell path + // (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); + 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; + float ndl = max(0.0, dot(N, Ldir)); + lit += uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w * ndl; + } } // POINT / SPOT torches: their OWN accumulator (A7 Fix D, D-1). Retail's diff --git a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs index 421890e2..bf80e9e0 100644 --- a/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs +++ b/src/AcDream.App/Rendering/Wb/EnvCellRenderer.cs @@ -877,6 +877,7 @@ public sealed unsafe class EnvCellRenderer : IDisposable // WB EnvCellRenderManager.cs:406-409: uniform state setup. _shader.SetInt("uRenderPass", (int)renderPass); _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 // moving"): upload uViewProjection HERE rather than inheriting it from diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index fa686b3c..fc131abb 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -893,6 +893,9 @@ public sealed unsafe class WbDrawDispatcher : IDisposable _indoorProbeFrameCounter++; var vp = camera.View * camera.Projection; _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. _missRequested.Clear();