sky(phase-4b): clamp sky vTint at vertex + 1.0 fragment cap for retail parity
After Phase 4 landed the per-vertex lighting formula, user observed acdream was still "a bit too bright" vs retail. Root cause: - My Phase 4 shader deliberately left vTint unclamped so D3D-style overbright contributions to emissive meshes (dome has Emissive=1 → lit could reach 2.0 with ambient + sun) would clamp naturally at the framebuffer. - But the frag cap was 1.2 (leaving "headroom for lightning flash"), letting dome vertices run 20% hotter than retail's per-channel 1.0. Retail's D3D fixed-function pipeline clamps vertex lit colour at D3DRS_COLORCLAMP=1 (default) BEFORE texture modulation. We now match: - Clamp `vTint = clamp(lit, 0, 1)` in sky.vert so the saturate happens at the vertex stage, exactly like D3D. - Drop normal-frame frag cap from 1.2 → 1.0 (the 3.0 flash relaxation stays so lightning strobes still visibly blow out). Expected visual: - Dome: identical appearance (was clamping to framebuffer 1.0 anyway), but pure retail-spec rendering so no sneaky 20% headroom. - Clouds: unchanged (already < 1.0 at morning Rainy keyframe). - Fragment flash during storm: unchanged — cap relaxes to 3.0 on flash. Build + 733 tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3a117bd91a
commit
2802fb2151
2 changed files with 14 additions and 6 deletions
|
|
@ -50,10 +50,11 @@ void main() {
|
||||||
float flash = uFogParams.z;
|
float flash = uFogParams.z;
|
||||||
rgb += flash * vec3(1.5, 1.5, 1.8);
|
rgb += flash * vec3(1.5, 1.5, 1.8);
|
||||||
|
|
||||||
// Soft clamp. Normal frame caps at 1.2 so the D3D-style overbright
|
// Normal-frame cap at 1.0 (retail D3D framebuffer clamps at 1.0
|
||||||
// from Emissive+Ambient+Diffuse at day-time saturates cleanly; during
|
// per channel for RGBA8 output; vTint is already vertex-clamped so
|
||||||
|
// the only path above 1 is lightning flash additive bump). During
|
||||||
// a flash the ceiling relaxes so the strobe blows out visibly.
|
// a flash the ceiling relaxes so the strobe blows out visibly.
|
||||||
float cap = mix(1.2, 3.0, clamp(flash, 0.0, 1.0));
|
float cap = mix(1.0, 3.0, clamp(flash, 0.0, 1.0));
|
||||||
rgb = min(rgb, vec3(cap));
|
rgb = min(rgb, vec3(cap));
|
||||||
|
|
||||||
float a = sampled.a * (1.0 - uTransparency);
|
float a = sampled.a * (1.0 - uTransparency);
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,15 @@ void main() {
|
||||||
|
|
||||||
// Retail per-vertex fixed-function lighting (AMBIENT=0 globally,
|
// Retail per-vertex fixed-function lighting (AMBIENT=0 globally,
|
||||||
// so the global ambient term drops; only light.Ambient contributes).
|
// so the global ambient term drops; only light.Ambient contributes).
|
||||||
|
// Clamp to [0,1] at the vertex — retail's D3DRS_COLORCLAMP defaults
|
||||||
|
// to clamping lit vertex colours to 1.0 BEFORE texture modulate.
|
||||||
|
// Without this, a dome vertex (uEmissive=1) picks up ambient+diff
|
||||||
|
// on top of already-saturated emissive, producing > 1.5 lit values
|
||||||
|
// that our framebuffer cap (1.2) lets through as 20% overbright
|
||||||
|
// vs retail's 1.0-clamped reference. User-observed 2026-04-23.
|
||||||
float diff = max(dot(worldNormal, uSunDir), 0.0);
|
float diff = max(dot(worldNormal, uSunDir), 0.0);
|
||||||
vTint = vec3(uEmissive) // material.Emissive
|
vec3 lit = vec3(uEmissive) // material.Emissive
|
||||||
+ uAmbientColor // material.Ambient(1) × light.Ambient
|
+ uAmbientColor // material.Ambient(1) × light.Ambient
|
||||||
+ uSunColor * diff; // material.Diffuse(1) × light.Diffuse × N·L
|
+ uSunColor * diff; // material.Diffuse(1) × light.Diffuse × N·L
|
||||||
|
vTint = clamp(lit, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue