Shadow: authentic terrain tilt + decal draw order (feet layer correctly)
User report: the ground shadow painted OVER the mech's feet. Root cause: the
flat quad + big depth-bias (-0.004 ~= 2-4 world units) workaround for slope
burial -- a bias big enough to beat the TERRAIN also beat the FEET in the
depth test, and the shadow drew in the late alpha-blend pass (after the mech),
so z-arbitration was the only layering control.
Fix, four co-dependent parts (user-verified live: feet on top, opaque,
survives inclines/declines):
- TILT (mech.cpp UpdateShadowJoint): apply the SKL's own contract for
jointshadow ("apply terrain angle to pitch and roll") -- quad up aligned to
the surface normal via an orthonormal basis fed to the ENGINE's own
LinearMatrix->EulerAngles conversion. Hand-derived Euler signs are exactly
how the prior tilt attempts "dug into the hillside"; the engine conversion
is convention-proof. ~35 deg cliff-guard cap.
- SAMPLER (mech4.cpp): surface gradient from the collision probes PLUS
BTVisualGroundLift per probe -- the quad hugs the VISIBLE terrain, whose
lift varies across a slope; world->local rotation by the TRUE yaw from
localToWorld, not the drift-prone gDriveHeading mirror (the task-#48 bug
class). BT_SHADOW_LOG traces the normals.
- DRAW ORDER (new PASS_SHADOW, l4d3d.h/L4VIDRND/L4VIDEO): shadows draw
between the STATIC terrain and the DYNAMIC opaque bodies -- the classic
decal order. The mech, drawn after, z-passes over the shadow: the bias
can never paint the shadow over the feet, structurally.
- BIAS pairing (L4D3D.cpp): tilt on (default) -> decal epsilon -0.0008;
BT_SHADOW_TILT=0 flat fallback -> the old -0.004. BT_SHADOW_BIAS overrides.
KB (locomotion.md): the four-part arrangement recorded as co-dependent, with
the gait-desync-symptom diagnostic hint retained.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
099cad998d
commit
b6fe686b22
@@ -948,10 +948,23 @@ void d3d_OBJECT::DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time target
|
||||
if (s_bias == -1e9f)
|
||||
{
|
||||
const char *bv = getenv("BT_SHADOW_BIAS");
|
||||
s_bias = bv ? (float)atof(bv) : -0.004f;
|
||||
if (bv)
|
||||
s_bias = (float)atof(bv);
|
||||
else
|
||||
{
|
||||
// Default pairs with the tilt (task #49b): with the quad TILTED
|
||||
// onto the slope (BT_SHADOW_TILT on, the default) only a decal
|
||||
// epsilon is needed -- the old big bias (2-4 world units) also
|
||||
// beat the mech's FEET in the depth test, painting the shadow
|
||||
// OVER them. The flat-quad A/B fallback (BT_SHADOW_TILT=0)
|
||||
// still needs the big bias to survive slope burial.
|
||||
const char *tv = getenv("BT_SHADOW_TILT");
|
||||
const int tiltOn = (tv == 0 || tv[0] != '0');
|
||||
s_bias = tiltOn ? -0.0008f : -0.004f;
|
||||
}
|
||||
}
|
||||
mDevice->SetRenderState(D3DRS_DEPTHBIAS, *(DWORD *)&s_bias);
|
||||
float slope = s_bias == 0.0f ? 0.0f : -1.0f; // steep-slope pixels get extra pull
|
||||
float slope = s_bias == 0.0f ? 0.0f : -1.0f; // coplanar decal term
|
||||
mDevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, *(DWORD *)&slope);
|
||||
}
|
||||
mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
|
||||
|
||||
@@ -7583,6 +7583,16 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
||||
}
|
||||
}
|
||||
|
||||
// GROUND SHADOWS (task #49b): the classic decal order -- terrain (statics,
|
||||
// above) -> shadow decals (here) -> dynamic opaque bodies (below). The
|
||||
// shadow's depth-bias only has to beat the TERRAIN already in the z-buffer;
|
||||
// the mech drawn AFTER simply z-passes over the shadow, so the bias can
|
||||
// never paint the shadow over the feet. Drawn with pass id PASS_ALPHABLEND
|
||||
// so DrawMesh's mIsShadow state-block branch (blend + bias + restore) runs
|
||||
// unchanged.
|
||||
for (d3d_OBJECT *obj = mRenderLists[PASS_SHADOW]; obj != NULL; obj = obj->GetNext(PASS_SHADOW))
|
||||
obj->Draw(PASS_ALPHABLEND, &viewTransform, mTargetRenderTime);
|
||||
|
||||
for (d3d_OBJECT *obj = mRenderLists[PASS_OPAQUE]; obj != NULL; obj = obj->GetNext(PASS_OPAQUE))
|
||||
obj->Draw(PASS_OPAQUE, &viewTransform, mTargetRenderTime);
|
||||
|
||||
|
||||
@@ -143,6 +143,18 @@ void HierarchicalDrawComponent::Execute()
|
||||
{
|
||||
if (isDeathDraw || !l4_application->IsDead())
|
||||
{
|
||||
// GROUND SHADOW (task #49b): tshd proxies go to the dedicated
|
||||
// PASS_SHADOW list ONLY -- drawn after the static terrain but
|
||||
// before the dynamic opaque bodies, so the mech's feet z-pass
|
||||
// over the shadow instead of the biased shadow painting over
|
||||
// the feet. (Their drawOps carry alphaTest=1, which would
|
||||
// otherwise route them to the late alpha-blend pass.)
|
||||
if (graphicalObject->GetIsShadow())
|
||||
{
|
||||
myRenderer->AddToPassList(graphicalObject, PASS_SHADOW);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool addedToOpaqueList = false, addedToAlphaList = false, addedToDecalList = false, addedToSphereList = false, addedToSkyList = false;
|
||||
for (int i=0; i<graphicalObject->GetDrawOpCount(); i++)
|
||||
{
|
||||
@@ -187,6 +199,7 @@ void HierarchicalDrawComponent::Execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
} // close the non-shadow classification branch (task #49b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,14 @@
|
||||
#define PASS_ALPHABLEND 2
|
||||
#define PASS_SPHERE 3
|
||||
#define PASS_SKY 4
|
||||
#define PASS_TOTAL_COUNT 5
|
||||
// GROUND-SHADOW list (task #49b): tshd proxies draw between the STATIC opaque
|
||||
// geometry (terrain) and the DYNAMIC opaque list (mech bodies) -- the classic
|
||||
// decal order. The feet then z-pass over the already-drawn shadow, so the
|
||||
// shadow's big terrain depth-bias can never paint OVER the mech. (The list is
|
||||
// drawn with pass id PASS_ALPHABLEND so d3d_OBJECT::DrawMesh's mIsShadow
|
||||
// state-block branch runs unchanged.)
|
||||
#define PASS_SHADOW 5
|
||||
#define PASS_TOTAL_COUNT 6
|
||||
|
||||
struct L4VERTEX
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user