sky(phase-2): retail-verbatim per-vertex lighting via Surface.Luminosity
Phase 2 of the sky port. Empirically confirmed from the Phase 1 dump
(ACDREAM_DUMP_SKY=1 on the live Dereth region): retail distinguishes
self-illuminated sky meshes from lit ones by the `Surface.Luminosity`
FLOAT field (0..1), NOT by the `SurfaceType.Luminous` flag bit (none of
Dereth's sky meshes have the flag set).
Observed values on the 4 currently-visible sky GfxObjs:
GfxObj 0x010015EE (dome, 4 surfaces) Luminosity = 1.0
GfxObj 0x010015EF (upper cloud) Luminosity = 0.0
GfxObj 0x01004C36 (lower drift cloud) Luminosity = 0.0
GfxObj 0x01001348 (sun/moon additive) Luminosity = 1.0
Retail uses this as an emissive coefficient in the per-vertex lighting
formula (decompiled chunk_00500000.c:7535 FUN_00508010 + chunk_00530000.c
AdjustPlanes per-vertex math):
tint = clamp(vec3(Luminosity) + AmbColor*AmbBright
+ max(dot(N, -sunDir), 0) * DirColor*DirBright,
0.0, 1.0)
fragment = texture * tint
When Luminosity=1.0 the clamp saturates → full texture brightness
regardless of time of day (dome gradient preserved; sun/moon always
bright). When Luminosity=0.0 only the ambient + diffuse term drives the
tint, so clouds pick up the time-of-day ambient (purple at midnight
per AmbColor=(200,100,255)×AmbBright=0.4 ≈ (0.31,0.16,0.40); warm tan
at dusk; pale-cool at noon).
Also empirically confirmed: raw SkyObjectReplace Transparent/Luminosity
/MaxBright are in 0..100 percent range (observed 11, 15, 22, 66, 100,
and -1 sentinel). The `/100` divide in SkyDescLoader (eeae83a) is
retail-correct; `_DAT_007a1870` in the decompile must be 0.01f.
Code changes:
- src/AcDream.Core/Meshing/GfxObjSubMesh.cs: new `Luminosity` field on
the per-submesh record (0..1, defaults to 0 for non-sky meshes).
- src/AcDream.Core/Meshing/GfxObjMesh.cs: pull Surface.Luminosity when
building submeshes (alongside existing Translucency capture).
- src/AcDream.App/Rendering/Sky/SkyRenderer.cs:
- SubMeshGpu gains SurfLuminosity, propagated from GfxObjSubMesh.
- Render() pushes uAmbientColor/uSunColor/uSunDir once per frame from
the interpolated keyframe; uEmissive once per submesh.
- uTint uniform removed (replaced by the vTint varying computed in
the vertex shader).
- src/AcDream.App/Rendering/Shaders/sky.vert: computes vTint per-vertex
using the retail AdjustPlanes formula.
- src/AcDream.App/Rendering/Shaders/sky.frag: consumes vTint, drops
uTint uniform. uLuminosity (the per-keyframe SkyObjectReplace
override) still applied as a final scalar multiply.
Expected visual difference from Phase 1 baseline:
- Dome gradient: IDENTICAL (Luminosity=1 saturates).
- Sun / moon: IDENTICAL (Luminosity=1 saturates, additive blend).
- Clouds: now tinted by time of day. Midnight → purple haze. Noon →
pale cool. Dusk → warm tan.
Open questions (unchanged from Phase 1 doc):
- Does the 15s LightTickSize throttling need porting? Phase 3.
- Does FUN_00532440 (AdjustPlanes per-cell terrain relight) need
porting for non-sky geometry to follow the sky? Phase 3.
Build + 717 tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
58afd4850f
commit
aa2e20a42e
5 changed files with 152 additions and 78 deletions
|
|
@ -1,24 +1,28 @@
|
|||
#version 430 core
|
||||
// Sky mesh fragment shader — sample the object's diffuse texture with
|
||||
// the scrolled UVs from the vertex stage. Unlit: sky meshes ARE the
|
||||
// gradient (r12 §2.2), not a surface lit by the sun.
|
||||
// Sky mesh fragment shader — retail-verbatim composite:
|
||||
//
|
||||
// The per-keyframe replace override can dim the mesh (Transparent) or
|
||||
// brighten it (Luminosity); those two floats arrive as uTransparency /
|
||||
// uLuminosity uniforms.
|
||||
// fragment.rgb = texture.rgb * vTint * uLuminosity + lightning_flash
|
||||
// fragment.a = texture.a * (1 - uTransparency)
|
||||
//
|
||||
// vTint arrives from the vertex shader with retail's per-vertex lighting
|
||||
// baked in (emissive + ambient + diffuse × sun, clamped to [0,1]).
|
||||
// uLuminosity is the per-keyframe SkyObjectReplace override (0..1
|
||||
// fraction after the /100 scale in SkyDescLoader) — NOT to be confused
|
||||
// with the Surface.Luminosity that feeds uEmissive in the vertex shader.
|
||||
// uTransparency is the per-keyframe SkyObjectReplace alpha-fade.
|
||||
//
|
||||
// See docs/research/2026-04-23-sky-retail-verbatim.md §6 + §7.
|
||||
|
||||
in vec2 vTex;
|
||||
in vec3 vTint;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D uDiffuse;
|
||||
uniform float uTransparency; // 0 = fully visible, 1 = invisible
|
||||
uniform float uLuminosity; // 1 = normal, >1 makes the mesh glow
|
||||
uniform vec4 uTint; // per-object color tint (default white)
|
||||
uniform float uTransparency; // 0 = fully visible, 1 = fully transparent
|
||||
uniform float uLuminosity; // 1.0 = normal; <1 dims per SkyObjectReplace
|
||||
|
||||
// Shared SceneLighting UBO — we only need the fog parameters to let the
|
||||
// horizon band of the sky blend smoothly into the scene's fog color at
|
||||
// the far edge, and the lightning flash to give storms their signature
|
||||
// strobe.
|
||||
// Shared SceneLighting UBO — only need fog/flash channel for the
|
||||
// client-driven lightning strobe. Sun/ambient already baked into vTint.
|
||||
struct Light {
|
||||
vec4 posAndKind;
|
||||
vec4 dirAndRange;
|
||||
|
|
@ -36,24 +40,20 @@ layout(std140, binding = 1) uniform SceneLighting {
|
|||
void main() {
|
||||
vec4 sampled = texture(uDiffuse, vTex);
|
||||
|
||||
// Apply tint + luminosity. Retail's SkyObjReplace.Luminosity can push
|
||||
// above 1 to make the sun mesh brighter than its texture; r12 §2.3.
|
||||
vec3 rgb = sampled.rgb * uTint.rgb * uLuminosity;
|
||||
// Composite: texture × per-vertex lighting × per-keyframe dim.
|
||||
vec3 rgb = sampled.rgb * vTint * uLuminosity;
|
||||
|
||||
// Lightning additive bump — makes the sky itself flash during storms.
|
||||
// Retail's lightning is a near-white strobe; a dim grey bump doesn't
|
||||
// read as lightning. Keep a faint blue tint so it still feels electric
|
||||
// rather than pure-white daylight.
|
||||
// Lightning additive bump (client-side during storm keyframes).
|
||||
float flash = uFogParams.z;
|
||||
rgb += flash * vec3(1.5, 1.5, 1.8);
|
||||
|
||||
// Soft clamp to let Luminosity/flash slightly over-bright. During a
|
||||
// lightning flash, raise the ceiling so the strobe actually blows out
|
||||
// instead of getting capped mid-rise.
|
||||
// Soft clamp. Normal frame: cap at 1.2 so emissive meshes have a
|
||||
// small highlight margin. During a flash the ceiling relaxes so the
|
||||
// strobe actually blows out instead of getting pinned mid-rise.
|
||||
float cap = mix(1.2, 3.0, clamp(flash, 0.0, 1.0));
|
||||
rgb = min(rgb, vec3(cap));
|
||||
|
||||
float a = sampled.a * (1.0 - uTransparency) * uTint.a;
|
||||
float a = sampled.a * (1.0 - uTransparency);
|
||||
if (a < 0.01) discard;
|
||||
fragColor = vec4(rgb, a);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue