Lands the fading-secret-door feature and fixes the door "flip-back" that surfaced while testing it. #188 — fading-wall doors (e.g. "Pedestal Weak Spot") fade their wall part out via TransparentPartHook instead of swinging: - TranslucencyHookSink consumes TransparentPartHook -> TranslucencyFadeManager (per-(entity,part) linear translucency ramp; holds at End frame). - WbDrawDispatcher: new per-instance alpha SSBO (binding 7); ClassifyBatches takes opacityMultiplier (1 - translucency, per CMaterial::SetTranslucencySimple 0x005396f0) forcing AlphaBlend; fully-invisible parts skipped. - mesh_modern.vert/.frag: binding-7 InstanceAlphaBuf -> vOpacityMultiplier -> FragColor.a *= vOpacityMultiplier. - Register AP-89: the fade multiplies sampled texture alpha, not a separate D3D9 material alpha channel (observably identical for texture-alpha==1 surfaces). Door flip-back fix (affected BOTH #188 fading walls AND #187 sliding doors): a door/wall that finished opening holds a single unchanging frame, so the uncommitted IsEntityCurrentlyMoving cache-bypass narrowing dropped it onto the Tier-1 static cache -- which only remembers the REST pose + opacity 1.0 -- snapping it visually shut/opaque while physics stayed open. Reverted that narrowing: every Sequencer entity stays on the per-frame path (live pose + live fade opacity), the known-good pre-optimization behavior. The per-frame CPU cost that narrowing chased was a Debug-build artifact -- Release is GPU-bound (~200 fps in Sawato, measured), so the unconditional add is free where it matters. Left a code comment barring re-introduction. Tests: full Core suite green (2649 passed, 2 skipped). Live visual gate PASSED -- both fading-wall and sliding doors hold open. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
111 lines
4.8 KiB
GLSL
111 lines
4.8 KiB
GLSL
#version 430 core
|
||
#extension GL_ARB_bindless_texture : require
|
||
|
||
in vec3 vNormal;
|
||
in vec2 vTexCoord;
|
||
in vec3 vWorldPos;
|
||
in vec3 vLit; // A7: per-vertex Gouraud lighting (ambient + capped lights), from mesh_modern.vert
|
||
in flat uvec2 vTextureHandle;
|
||
in flat uint vTextureLayer;
|
||
in flat float vOpacityMultiplier; // #188
|
||
|
||
// uRenderPass values (Phase N.5 Decision 2 — two-pass alpha-test):
|
||
// 0 = opaque pass — discard fragments with alpha < 0.95
|
||
// (lets the depth write succeed for solid pixels)
|
||
// 1 = translucent pass — covers AlphaBlend / Additive / InvAlpha;
|
||
// discard alpha >= 0.95 (already drawn opaque) and
|
||
// alpha < 0.05 (skip empty fragments — large
|
||
// transparent overdraw cost otherwise)
|
||
uniform int uRenderPass;
|
||
uniform int uLightDebug; // #176 stripe hunt (see mesh_modern.vert) — mode 3 handled here
|
||
|
||
// SceneLighting UBO — IDENTICAL layout to mesh_instanced.frag binding=1.
|
||
struct Light {
|
||
vec4 posAndKind;
|
||
vec4 dirAndRange;
|
||
vec4 colorAndIntensity;
|
||
vec4 coneAngleEtc;
|
||
};
|
||
layout(std140, binding = 1) uniform SceneLighting {
|
||
Light uLights[8];
|
||
vec4 uCellAmbient;
|
||
vec4 uFogParams;
|
||
vec4 uFogColor;
|
||
vec4 uCameraAndTime;
|
||
};
|
||
|
||
// A7 (2026-06-15): per-vertex lighting moved to mesh_modern.vert (Gouraud) to match
|
||
// retail's fixed-function per-vertex T&L — a per-pixel evaluation made a hard "spotlight"
|
||
// pool. The SceneLighting UBO above is still declared here for fog (uFogParams/uFogColor/
|
||
// uCameraAndTime) + the lightning-flash bump; its uLights[]/uCellAmbient are now consumed
|
||
// in the vertex shader. The std140 layout must stay identical to the vert + the CPU upload.
|
||
|
||
vec3 applyFog(vec3 lit, vec3 worldPos) {
|
||
int mode = int(uFogParams.w);
|
||
if (mode == 0) return lit;
|
||
float d = length(worldPos - uCameraAndTime.xyz);
|
||
float fogStart = uFogParams.x;
|
||
float fogEnd = uFogParams.y;
|
||
float span = max(1e-3, fogEnd - fogStart);
|
||
float fog = clamp((d - fogStart) / span, 0.0, 1.0);
|
||
return mix(lit, uFogColor.xyz, fog);
|
||
}
|
||
|
||
out vec4 FragColor;
|
||
|
||
void main() {
|
||
sampler2DArray tex = sampler2DArray(vTextureHandle);
|
||
vec4 color = texture(tex, vec3(vTexCoord, float(vTextureLayer)));
|
||
|
||
// Two-pass alpha-test (N.5 Decision 2).
|
||
// A.5 T20: opaque pass writes alpha as-sampled so GL_SAMPLE_ALPHA_TO_COVERAGE
|
||
// derives the MSAA sample mask from it — ClipMap foliage edges become smooth.
|
||
// Discard only fully-transparent (α < 0.05); the GPU handles coverage masking.
|
||
if (uRenderPass == 0) {
|
||
if (color.a < 0.05) discard; // opaque pass — kill truly empty only (A2C)
|
||
} else {
|
||
// Transparent pass.
|
||
//
|
||
// Phase Post-A.5 (ISSUE #52, 2026-05-10): do NOT discard α≥0.95 here.
|
||
// Native AC transparent-flagged surfaces routinely include
|
||
// effectively-opaque pixels — e.g. the Holtburg lifestone crystal core
|
||
// (surface 0x080011DE) which the spawn manifest classifies as
|
||
// transparent (batch.IsTransparent=True) but whose decoded texture
|
||
// alpha lands ≥0.95 across the visible surface. Those pixels still
|
||
// compose correctly under (SrcAlpha, 1-SrcAlpha) alpha-blending, so
|
||
// discarding them here threw away the whole crystal. The original
|
||
// N.5 §2 rationale (high-α fragments belong in the opaque pass) does
|
||
// not apply when the SURFACE is dat-flagged transparent — those
|
||
// pixels can't reach the opaque pass at all.
|
||
//
|
||
// Keep the α<0.05 short-circuit as a fragment-cost optimization
|
||
// (skip fully-empty pixels — saves blend bandwidth on alpha-keyed
|
||
// sprites with large transparent margins).
|
||
if (color.a < 0.05) discard;
|
||
}
|
||
|
||
// Per-vertex Gouraud lighting from the vertex shader (ambient + capped lights).
|
||
vec3 lit = vLit;
|
||
|
||
// #176 stripe-hunt mode 3: show the raw per-vertex light field (texture
|
||
// ignored). Stripes visible HERE = a vertex-lighting artifact; absent =
|
||
// the pattern comes from texture/per-pixel machinery. Throwaway diagnostic.
|
||
if (uLightDebug == 3) {
|
||
FragColor = vec4(min(lit, vec3(1.0)), 1.0);
|
||
return;
|
||
}
|
||
|
||
// Lightning flash — additive scene bump (matches mesh_instanced.frag).
|
||
lit += uFogParams.z * vec3(0.6, 0.6, 0.75);
|
||
|
||
// Retail clamp per-channel to 1.0 (r13 §13.1).
|
||
lit = min(lit, vec3(1.0));
|
||
|
||
vec3 rgb = color.rgb * lit;
|
||
rgb = applyFog(rgb, vWorldPos);
|
||
// #188: multiply the FINAL alpha only — the discard thresholds above stay
|
||
// keyed on the raw sampled color.a, so the last few frames of a fade
|
||
// (multiplier crossing under 0.05) still ramp smoothly toward zero rather
|
||
// than popping invisible early against the discard cutoff.
|
||
FragColor = vec4(rgb, color.a * vOpacityMultiplier);
|
||
}
|