sky(phase-5b): port retail vertex fog onto sky meshes
Retail applies linear vertex fog with 3D range distance
(D3DRS_FOGVERTEXMODE=3=LINEAR, D3DRS_RANGEFOGENABLE=1,
D3DRS_FOGTABLEMODE=0=NONE) to ALL mesh draws including sky. Only
FOGCOLOR / FOGSTART / FOGEND are lerped per keyframe; the mode flags
are init-only.
Verified in `docs/research/2026-04-23-sky-fog.md`:
- chunk_005A0000.c:3361-3389 device-init sets the modes.
- Sky meshes render at world origin (translation zeroed, rotation-
only) with intrinsic mesh radii in the thousands of meters
(WorldBuilder's SkyboxRenderManager.cs:247 comment confirms).
- With keyframe MaxWorldFog = 2400m, the dome saturates to
WorldFogColor at its horizon band. THAT is retail's dusk/dawn
horizon-glow mechanism.
Port:
`sky.vert` now computes the vertex fog factor:
worldPos = uModel × aPos (camera-centered since view translation=0)
dist = length(worldPos.xyz)
fogFactor = clamp((fogEnd - dist) / (fogEnd - fogStart), 0, 1)
— outputs as varying vFogFactor. 1.0 means no fog contribution,
0.0 means full fog color.
`sky.frag` applies the mix BEFORE the lightning-flash bump:
rgb = mix(uFogColor.rgb, rgb, vFogFactor)
Uses the existing SceneLighting UBO's uFogParams (x=start, y=end,
z=flash, w=mode) and uFogColor — no new uniforms, no C# change.
Expected visual:
- Dome at dawn/dusk: horizon band blends toward keyframe fogColor
(warm orange at sunset, cool blue at dawn), matching retail's
sky/fog coupling.
- Close sky objects (sun disk at typical mesh radius): unaffected
since dist < fogStart.
- Clouds at intermediate distance: partial fog blend, subtly
muting their saturation with distance.
Note on lightning: the flash channel (uFogParams.z) stays wired but
is currently always 0 because no code drives it. Agent #5 is
researching retail's real lightning mechanism (PlayScript / SetLight
PhysicsScript / other). This commit does not attempt to port it.
Build + 733 tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
53608e77e3
commit
8a42750459
2 changed files with 61 additions and 9 deletions
|
|
@ -16,16 +16,18 @@
|
||||||
//
|
//
|
||||||
// See `docs/research/2026-04-23-sky-material-state.md`.
|
// See `docs/research/2026-04-23-sky-material-state.md`.
|
||||||
|
|
||||||
in vec2 vTex;
|
in vec2 vTex;
|
||||||
in vec3 vTint;
|
in vec3 vTint;
|
||||||
|
in float vFogFactor; // 1 = no fog (near), 0 = full fog color (far)
|
||||||
out vec4 fragColor;
|
out vec4 fragColor;
|
||||||
|
|
||||||
uniform sampler2D uDiffuse;
|
uniform sampler2D uDiffuse;
|
||||||
uniform float uTransparency; // 0 = fully visible, 1 = fully transparent
|
uniform float uTransparency; // 0 = fully visible, 1 = fully transparent
|
||||||
uniform float uLuminosity; // SkyObjectReplace.Luminosity override (0..1)
|
uniform float uLuminosity; // SkyObjectReplace.Luminosity override (0..1)
|
||||||
|
|
||||||
// Shared SceneLighting UBO — only need the fog-flash channel for
|
// Shared SceneLighting UBO — fog params drive the mix, flash channel
|
||||||
// client-driven lightning strobes; sun/ambient already baked into vTint.
|
// bumps sky brightness during lightning strikes. Matches sky.vert's
|
||||||
|
// declaration exactly.
|
||||||
struct Light {
|
struct Light {
|
||||||
vec4 posAndKind;
|
vec4 posAndKind;
|
||||||
vec4 dirAndRange;
|
vec4 dirAndRange;
|
||||||
|
|
@ -46,14 +48,24 @@ void main() {
|
||||||
// Composite: texture × per-vertex lit × per-keyframe dim.
|
// Composite: texture × per-vertex lit × per-keyframe dim.
|
||||||
vec3 rgb = sampled.rgb * vTint * uLuminosity;
|
vec3 rgb = sampled.rgb * vTint * uLuminosity;
|
||||||
|
|
||||||
// Lightning additive bump (client-driven during storm keyframes).
|
// Retail vertex fog: lerp(fogColor, scene, fogFactor). At distant
|
||||||
|
// horizon dome vertices (distance > FOGEND) the sky saturates to
|
||||||
|
// the keyframe's WorldFogColor — that's retail's horizon-glow
|
||||||
|
// mechanism at dusk/dawn. See docs/research/2026-04-23-sky-fog.md.
|
||||||
|
rgb = mix(uFogColor.rgb, rgb, vFogFactor);
|
||||||
|
|
||||||
|
// Lightning additive bump — client-driven during storm flashes.
|
||||||
|
// NOTE: the exact retail mechanism for lightning visual is still
|
||||||
|
// under research (agent #5, 2026-04-23). Keeping the uFogParams.z
|
||||||
|
// channel wired so if it ends up being a per-frame flash uniform
|
||||||
|
// that's what it becomes; if lightning turns out to be a particle
|
||||||
|
// system effect instead, this bump becomes a no-op (flash stays 0).
|
||||||
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);
|
||||||
|
|
||||||
// Normal-frame cap at 1.0 (retail D3D framebuffer clamps at 1.0
|
// Normal-frame cap at 1.0 (retail D3D framebuffer clamps per-channel
|
||||||
// per channel for RGBA8 output; vTint is already vertex-clamped so
|
// on output). Flash relaxes ceiling to 3.0 so storm strobes blow
|
||||||
// the only path above 1 is lightning flash additive bump). During
|
// out visibly.
|
||||||
// a flash the ceiling relaxes so the strobe blows out visibly.
|
|
||||||
float cap = mix(1.0, 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));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,25 @@ uniform vec3 uSunDir; // unit vector FROM surface TO sun
|
||||||
// Per-submesh (from Surface.Luminosity float):
|
// Per-submesh (from Surface.Luminosity float):
|
||||||
uniform float uEmissive;
|
uniform float uEmissive;
|
||||||
|
|
||||||
|
// Shared SceneLighting UBO — we need uFogParams.xy (fog start/end) to
|
||||||
|
// compute the vertex fog factor. Must match sky.frag's declaration.
|
||||||
|
struct Light {
|
||||||
|
vec4 posAndKind;
|
||||||
|
vec4 dirAndRange;
|
||||||
|
vec4 colorAndIntensity;
|
||||||
|
vec4 coneAngleEtc;
|
||||||
|
};
|
||||||
|
layout(std140, binding = 1) uniform SceneLighting {
|
||||||
|
Light uLights[8];
|
||||||
|
vec4 uCellAmbient;
|
||||||
|
vec4 uFogParams; // x=fogStart, y=fogEnd, z=flash, w=fogMode
|
||||||
|
vec4 uFogColor;
|
||||||
|
vec4 uCameraAndTime;
|
||||||
|
};
|
||||||
|
|
||||||
out vec2 vTex;
|
out vec2 vTex;
|
||||||
out vec3 vTint;
|
out vec3 vTint;
|
||||||
|
out float vFogFactor; // 1 = no fog (close), 0 = full fog (far)
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vTex = aTex + uUvScroll;
|
vTex = aTex + uUvScroll;
|
||||||
|
|
@ -72,4 +89,27 @@ void main() {
|
||||||
+ 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);
|
vTint = clamp(lit, 0.0, 1.0);
|
||||||
|
|
||||||
|
// Retail vertex-fog in 3D-range mode (FOGVERTEXMODE=LINEAR,
|
||||||
|
// RANGEFOGENABLE=1, FOGTABLEMODE=NONE per device init — never
|
||||||
|
// toggled per frame). Distance = `|worldPos - cameraPos|`. Since
|
||||||
|
// our sky view matrix has translation zeroed (sky is camera-
|
||||||
|
// centered), the post-uModel position IS the camera-relative
|
||||||
|
// world-space vector, so its length is the 3D range distance.
|
||||||
|
// See docs/research/2026-04-23-sky-fog.md.
|
||||||
|
//
|
||||||
|
// Formula: fogFactor = clamp((fogEnd - dist) / (fogEnd - fogStart), 0, 1)
|
||||||
|
// 1.0 → no fog contribution (scene color wins)
|
||||||
|
// 0.0 → full fog color (sky color fades to fog)
|
||||||
|
//
|
||||||
|
// Sky meshes have intrinsic radii in the thousands of meters (dome
|
||||||
|
// / stars / moon are authored at large distances in the dat); at
|
||||||
|
// typical keyframe FOGEND=2400m, the dome saturates to fogColor at
|
||||||
|
// its horizon band. THAT is how retail colors the horizon at dusk.
|
||||||
|
vec4 worldPos = uModel * vec4(aPos, 1.0);
|
||||||
|
float dist = length(worldPos.xyz);
|
||||||
|
float fogStart = uFogParams.x;
|
||||||
|
float fogEnd = uFogParams.y;
|
||||||
|
float span = max(fogEnd - fogStart, 1e-3);
|
||||||
|
vFogFactor = clamp((fogEnd - dist) / span, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue