fix(render): close #52 — lifestone visible (alpha-test + cull + uDrawIDOffset)
Three root causes regressed the Holtburg lifestone since the WB rendering migration (Phase N.5 retirement amendment, commitdcae2b6, 2026-05-08). All confirmed via temporary [LIFESTONE-DIAG] instrumentation and visually verified by the user through the +Acdream test character. 1. **Alpha-test discard** in mesh_modern.frag transparent pass killed high-α pixels of dat-flagged transparent surfaces. Native AC transparent surfaces routinely include effectively-opaque pixels — e.g. the lifestone crystal core (surface 0x080011DE) — that compose correctly under (SrcAlpha, 1-SrcAlpha) blending. The original N.5 §2 rationale ("high-α belongs in opaque pass") doesn't hold for surfaces flagged transparent at the dat level: those pixels can't reach the opaque pass at all. Fix: remove `α >= 0.95 discard` from the transparent pass, keep `α < 0.05 discard` as a fragment-cost optimization (skip totally-empty pixels). 2. **Cull state** for the transparent pass was unset by WbDrawDispatcher after the N.5 retirement amendment deleted StaticMeshRenderer.cs (which had the Phase 9.2 setup at commit6f1971a, 2026-04-11). Closed-shell translucents — lifestone crystal, glow gems — need GL_CULL_FACE + GL_BACK + GL_CCW in the transparent pass; otherwise back faces composite over front faces in iteration order under DepthMask(false). Fix: re-establish Phase 9.2's exact GL state setup at the top of Phase 8. 3. **uDrawIDOffset uniform** was missing from mesh_modern.vert. gl_DrawIDARB resets to 0 at the start of each glMultiDrawElementsIndirect call, so the transparent pass — which begins later in the indirect buffer — was fetching Batches[0..transparentCount) instead of its actual section at Batches[opaqueCount..end). The lifestone crystal ended up reading the FIRST OPAQUE batch's TextureHandle every frame; as the camera moved and the front-to-back opaque sort reordered which group landed at BatchData[0], the crystal's apparent texture flickered to whatever sat first — typically the player character's body parts. Fix: add `uniform int uDrawIDOffset` to the vertex shader, change Batches[gl_DrawIDARB] → Batches[uDrawIDOffset + gl_DrawIDARB], and set the uniform per-pass in WbDrawDispatcher (0 for opaque, _opaqueDrawCount for transparent). Mirrors WorldBuilder's BaseObjectRenderManager.cs line 845. Tests: 1688/1696 passing (8 pre-existing physics/input failures unchanged). N.5b conformance sentinel 94/94 clean. Visual: Holtburg lifestone now renders with the spinning blue crystal correctly composed over the pedestal. Other transparent content (glass, particle effects, NPC clothing) is unaffected — the same uniform fix applies globally and is correct for all transparent draws. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c111312e13
commit
e40159f4d6
3 changed files with 69 additions and 3 deletions
|
|
@ -86,8 +86,24 @@ void main() {
|
|||
if (uRenderPass == 0) {
|
||||
if (color.a < 0.05) discard; // opaque pass — kill truly empty only (A2C)
|
||||
} else {
|
||||
if (color.a >= 0.95) discard; // transparent pass
|
||||
if (color.a < 0.05) discard; // skip totally-empty
|
||||
// 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;
|
||||
}
|
||||
|
||||
vec3 N = normalize(vNormal);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue