#version 430 core // Sky mesh vertex shader — computes the per-vertex lighting tint that // gives retail its time-of-day variation: // // tint = clamp(emissive + ambient + max(dot(N, -sunDir), 0) * sunColor, // 0.0, 1.0) // // This is the retail-verbatim AdjustPlanes formula ported from the // decompiled D3D fixed-function lighting. The `emissive` scalar is the // Surface.Luminosity FLOAT field (NOT the SurfaceType.Luminous flag bit) — // for Dereth's sky meshes, the DOME + SUN/MOON have emissive=1.0 // (texture-passthrough regardless of lighting), while CLOUDS have // emissive=0.0 (lit normally, so they pick up the ambient tint that // produces retail's purple-haze night / warm-tan dusk / pale-cool noon). // // See docs/research/2026-04-23-sky-retail-verbatim.md §6 for the full // decompile trail and field citations. layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aNormal; layout(location = 2) in vec2 aTex; uniform mat4 uModel; // per-object arc transform uniform mat4 uSkyView; // camera view with M41..M43 = 0 uniform mat4 uSkyProjection; // near=0.1, far=1e6 uniform vec2 uUvScroll; // cumulative TexVelocityX/Y * time // Per-frame lighting — keyframe-interpolated values pushed before each // Render() call. AmbColor × AmbBright and DirColor × DirBright are // pre-multiplied by SkyDescLoader so the shader feeds them straight in. uniform vec3 uAmbientColor; // AmbColor × AmbBright uniform vec3 uSunColor; // DirColor × DirBright uniform vec3 uSunDir; // unit vector FROM surface TO sun // Per-submesh: Surface.Luminosity (0..1 self-illumination scalar). uniform float uEmissive; out vec2 vTex; out vec3 vTint; void main() { vTex = aTex + uUvScroll; gl_Position = uSkyProjection * uSkyView * uModel * vec4(aPos, 1.0); // uModel for sky is scale * RotZ(-heading) * RotY(-rotation) — pure // orthonormal rotation, so mat3(uModel) correctly transforms normals // without needing transpose(inverse(...)). vec3 worldNormal = normalize(mat3(uModel) * aNormal); // Per-vertex lighting. `emissive` is broadcast scalar → vec3. For // emissive=1.0 the clamp saturates to white regardless of ambient/sun // (the retail "unlit" mesh). For emissive=0.0 only the ambient + sun // term drives `tint` (the retail "lit" mesh, e.g. clouds). float diff = max(dot(worldNormal, uSunDir), 0.0); vec3 lit = vec3(uEmissive) + uAmbientColor + diff * uSunColor; vTint = clamp(lit, 0.0, 1.0); }