PSSM Shadow Fix

fixes the issue with pssm shadows were the softness value makes the shadow cast on the entire cascade.
This commit is contained in:
marauder2k7 2026-03-12 20:47:19 +00:00
parent 058983aa42
commit 6a8d832bd6
3 changed files with 143 additions and 98 deletions

View file

@ -191,6 +191,16 @@ void PSSMLightShadowMap::_roundProjection(const MatrixF& lightMat, const MatrixF
} }
void PSSMLightShadowMap::_adjustScaleAndOffset(Box3F& clipAABB, Point3F& scale, Point3F& offset) { void PSSMLightShadowMap::_adjustScaleAndOffset(Box3F& clipAABB, Point3F& scale, Point3F& offset) {
const ShadowMapParams* params = mLight->getExtended<ShadowMapParams>();
F32 padding = params->shadowSoftness * (2.0f / (F32)mTexSize);
clipAABB.minExtents.x -= padding;
clipAABB.minExtents.y -= padding;
clipAABB.maxExtents.x += padding;
clipAABB.maxExtents.y += padding;
scale.x = 2.0f / (clipAABB.maxExtents.x - clipAABB.minExtents.x); scale.x = 2.0f / (clipAABB.maxExtents.x - clipAABB.minExtents.x);
scale.y = 2.0f / (clipAABB.maxExtents.y - clipAABB.minExtents.y); scale.y = 2.0f / (clipAABB.maxExtents.y - clipAABB.minExtents.y);
scale.z = 1.0f; scale.z = 1.0f;
@ -469,7 +479,7 @@ void PSSMLightShadowMap::setShaderParameters(GFXShaderConstBuffer* params, Light
params->setSafe( lsc->mOverDarkFactorPSSM, p->overDarkFactor); params->setSafe( lsc->mOverDarkFactorPSSM, p->overDarkFactor);
// The softness is a factor of the texel size. // The softness is a factor of the texel size.
params->setSafe( lsc->mShadowSoftnessConst, p->shadowSoftness * ( 1.0f / mTexSize ) ); params->setSafe( lsc->mShadowSoftnessConst, p->shadowSoftness * ( 2.0f / mTexSize ) );
} }
void PSSMLightShadowMap::_calcPlanesCullForShadowCasters(Vector< Vector<PlaneF> > &out, const Frustum &viewFrustum, const Point3F &_ligthDir) void PSSMLightShadowMap::_calcPlanesCullForShadowCasters(Vector< Vector<PlaneF> > &out, const Frustum &viewFrustum, const Point3F &_ligthDir)

View file

@ -112,6 +112,18 @@ void ShadowMaterialHook::init( BaseMatInstance *inMat )
mShadowMat[ShadowType_Spot] = newMat; mShadowMat[ShadowType_Spot] = newMat;
newMat = new ShadowMatInstance(shadowMat);
newMat->setUserObject(inMat->getUserObject());
newMat->getFeaturesDelegate().bind(&ShadowMaterialHook::_overrideFeatures);
forced.setCullMode(GFXCullCW);
forced.zBias = 1000.0f;
forced.zSlopeBias = 1.0f;
forced.setFillModeSolid();
newMat->addStateBlockDesc(forced);
forced.cullDefined = true;
newMat->init(features, inMat->getVertexFormat());
mShadowMat[ShadowType_PSSM] = newMat;
newMat = new ShadowMatInstance( shadowMat ); newMat = new ShadowMatInstance( shadowMat );
newMat->setUserObject( inMat->getUserObject() ); newMat->setUserObject( inMat->getUserObject() );
newMat->getFeaturesDelegate().bind( &ShadowMaterialHook::_overrideFeatures ); newMat->getFeaturesDelegate().bind( &ShadowMaterialHook::_overrideFeatures );
@ -162,12 +174,6 @@ BaseMatInstance* ShadowMaterialHook::getShadowMat( ShadowType type ) const
{ {
AssertFatal( type < ShadowType_Count, "ShadowMaterialHook::getShadowMat() - Bad light type!" ); AssertFatal( type < ShadowType_Count, "ShadowMaterialHook::getShadowMat() - Bad light type!" );
// The cubemap and pssm shadows use the same
// spotlight material for shadows.
if ( type == ShadowType_Spot ||
type == ShadowType_PSSM )
return mShadowMat[ShadowType_Spot];
// Get the specialized shadow material. // Get the specialized shadow material.
return mShadowMat[type]; return mShadowMat[type];
} }

View file

@ -64,109 +64,138 @@ uniform float4 scaleY;
uniform float4 offsetX; uniform float4 offsetX;
uniform float4 offsetY; uniform float4 offsetY;
float4 AL_VectorLightShadowCast( TORQUE_SAMPLER2D(sourceShadowMap), float ComputeESMFactor(float cascadeNear, float cascadeFar, int shadowMapResolution, float targetShadow = 0.1)
float2 texCoord,
float4x4 worldToLightProj,
float3 worldPos,
float4 scaleX,
float4 scaleY,
float4 offsetX,
float4 offsetY,
float4 farPlaneScalePSSM,
float dotNL)
{ {
// Compute shadow map coordinate float delta = (cascadeFar - cascadeNear) / shadowMapResolution;
float4 pxlPosLightProj = mul(worldToLightProj, float4(worldPos,1)); float esmFactor = -log(targetShadow) / delta;
float2 baseShadowCoord = pxlPosLightProj.xy / pxlPosLightProj.w; return esmFactor;
}
// Distance to light, in shadowmap space float4 AL_VectorLightShadowCast(
float distToLight = pxlPosLightProj.z / pxlPosLightProj.w; TORQUE_SAMPLER2D(sourceShadowMap),
float2 texCoord,
float4x4 worldToLightProj,
float3 worldPos,
float4 scaleX,
float4 scaleY,
float4 offsetX,
float4 offsetY,
float4 farPlaneScalePSSM,
float dotNL)
{
// Compute shadow map coordinate
float4 pxlPosLightProj = mul(worldToLightProj, float4(worldPos,1));
float2 baseShadowCoord = pxlPosLightProj.xy / pxlPosLightProj.w;
float distToLight = pxlPosLightProj.z / pxlPosLightProj.w;
// Figure out which split to sample from. Basically, we compute the shadowmap sample coord // PSSM split handling
// for all of the splits and then check if its valid. float4 shadowCoordX = baseShadowCoord.xxxx;
float4 shadowCoordX = baseShadowCoord.xxxx; float4 shadowCoordY = baseShadowCoord.yyyy;
float4 shadowCoordY = baseShadowCoord.yyyy; float4 farPlaneDists = distToLight.xxxx;
float4 farPlaneDists = distToLight.xxxx; shadowCoordX *= scaleX;
shadowCoordX *= scaleX; shadowCoordY *= scaleY;
shadowCoordY *= scaleY; shadowCoordX += offsetX;
shadowCoordX += offsetX; shadowCoordY += offsetY;
shadowCoordY += offsetY; farPlaneDists *= farPlaneScalePSSM;
farPlaneDists *= farPlaneScalePSSM;
// If the shadow sample is within -1..1 and the distance const float cascadeBorder = 0.02;
// to the light for this pixel is less than the far plane float4 insideX = step(-1.0 + cascadeBorder, shadowCoordX) * step(shadowCoordX, 1.0 - cascadeBorder);
// of the split, use it. float4 insideY = step(-1.0 + cascadeBorder, shadowCoordY) * step(shadowCoordY, 1.0 - cascadeBorder);
float4 finalMask; float4 insideZ = step(farPlaneDists, 1.0);
if ( shadowCoordX.x > -0.99 && shadowCoordX.x < 0.99 &&
shadowCoordY.x > -0.99 && shadowCoordY.x < 0.99 &&
farPlaneDists.x < 1.0 )
finalMask = float4(1, 0, 0, 0);
else if ( shadowCoordX.y > -0.99 && shadowCoordX.y < 0.99 && float4 cascadeValid = insideX * insideY * insideZ;
shadowCoordY.y > -0.99 && shadowCoordY.y < 0.99 &&
farPlaneDists.y < 1.0 )
finalMask = float4(0, 1, 0, 0);
else if ( shadowCoordX.z > -0.99 && shadowCoordX.z < 0.99 && float4 finalMask;
shadowCoordY.z > -0.99 && shadowCoordY.z < 0.99 &&
farPlaneDists.z < 1.0 )
finalMask = float4(0, 0, 1, 0);
else finalMask.x = cascadeValid.x;
finalMask = float4(0, 0, 0, 1); finalMask.y = (1 - finalMask.x) * cascadeValid.y;
finalMask.z = (1 - finalMask.x - finalMask.y) * cascadeValid.z;
finalMask.w = 1 - finalMask.x - finalMask.y - finalMask.z;
float3 debugColor = float3(0,0,0); float3 debugColor = float3(0,0,0);
#ifdef NO_SHADOW #ifdef NO_SHADOW
debugColor = float3(1.0,1.0,1.0); debugColor = float3(1.0,1.0,1.0);
#endif #endif
#ifdef PSSM_DEBUG_RENDER #ifdef PSSM_DEBUG_RENDER
if ( finalMask.x > 0 ) if ( finalMask.x > 0 )
debugColor += float3( 1, 0, 0 ); debugColor += float3( 1, 0, 0 );
else if ( finalMask.y > 0 ) else if ( finalMask.y > 0 )
debugColor += float3( 0, 1, 0 ); debugColor += float3( 0, 1, 0 );
else if ( finalMask.z > 0 ) else if ( finalMask.z > 0 )
debugColor += float3( 0, 0, 1 ); debugColor += float3( 0, 0, 1 );
else if ( finalMask.w > 0 ) else if ( finalMask.w > 0 )
debugColor += float3( 1, 1, 0 ); debugColor += float3( 1, 1, 0 );
#endif #endif
// Here we know what split we're sampling from, so recompute the texcoord location // Compute final scale & offset for PSSM atlas
// Yes, we could just use the result from above, but doing it this way actually saves float2 finalScale;
// shader instructions. finalScale.x = dot(finalMask, scaleX);
float2 finalScale; finalScale.y = dot(finalMask, scaleY);
finalScale.x = dot(finalMask, scaleX); float2 finalOffset;
finalScale.y = dot(finalMask, scaleY); finalOffset.x = dot(finalMask, offsetX);
finalOffset.y = dot(finalMask, offsetY);
float2 finalOffset; float2 shadowCoord = baseShadowCoord * finalScale + finalOffset;
finalOffset.x = dot(finalMask, offsetX);
finalOffset.y = dot(finalMask, offsetY);
float2 shadowCoord; // Convert to texcoord space and atlas
shadowCoord = baseShadowCoord * finalScale; shadowCoord = 0.5 *shadowCoord + 0.5;
shadowCoord += finalOffset; shadowCoord.y = 1.0 - shadowCoord.y;
float2 aOffset;
aOffset.x = dot(finalMask, atlasXOffset);
aOffset.y = dot(finalMask, atlasYOffset);
shadowCoord = shadowCoord * atlasScale + aOffset;
// Convert to texcoord space // Compute atlas tile bounds
shadowCoord = 0.5 * shadowCoord + float2(0.5, 0.5); float2 tileMin = aOffset;
shadowCoord.y = 1.0f - shadowCoord.y; float2 tileMax = aOffset + atlasScale;
// Move around inside of atlas // Convert filter radius to atlas UV space
float2 aOffset; float2 filterRadiusUV = shadowSoftness * atlasScale;
aOffset.x = dot(finalMask, atlasXOffset);
aOffset.y = dot(finalMask, atlasYOffset);
shadowCoord *= atlasScale; // Adjust for PSSM far plane
shadowCoord += aOffset; float farPlaneScale = dot(farPlaneScalePSSM, finalMask);
distToLight *= farPlaneScale;
// Each split has a different far plane, take this into account.
float farPlaneScale = dot( farPlaneScalePSSM, finalMask );
distToLight *= farPlaneScale;
return float4(debugColor, softShadow_filter( TORQUE_SAMPLER2D_MAKEARG(sourceShadowMap), texCoord, shadowCoord, farPlaneScale * shadowSoftness, // Shadow map resolution per cascade
distToLight, dotNL, dot( finalMask, overDarkPSSM ) ) ); int shadowRes = 1024;
}; float cascadeTexel = 1.0 / shadowRes;
float4 depthBiasPSSM = float4(
0.2 * cascadeTexel,
0.3 * cascadeTexel,
0.7 * cascadeTexel,
1.5 * cascadeTexel
);
float shadow_bias = dot(finalMask, depthBiasPSSM);
distToLight += shadow_bias;
distToLight = saturate(distToLight);
// Example cascade ranges
float cascadeNear[4] = { 0.0, 0.2, 0.5, 0.75 };
float cascadeFar[4] = { 0.2, 0.5, 0.75, 1.0 };
float4 overDarkPSSM;
for(int i=0;i<4;i++)
{
overDarkPSSM[i] = ComputeESMFactor(cascadeNear[i], cascadeFar[i], shadowRes, 0.1);
}
return float4(
debugColor,
softShadow_filter(
TORQUE_SAMPLER2D_MAKEARG(sourceShadowMap),
texCoord,
shadowCoord,
shadowSoftness,
distToLight,
dotNL,
dot(finalMask, overDarkPSSM) // replace this with shadowBias for pcf.
)
);
}
float4 main(FarFrustumQuadConnectP IN) : SV_TARGET float4 main(FarFrustumQuadConnectP IN) : SV_TARGET
{ {