diff --git a/Engine/source/lighting/shadowMap/pssmLightShadowMap.cpp b/Engine/source/lighting/shadowMap/pssmLightShadowMap.cpp index 54e4986672..d0ba5bd3c5 100644 --- a/Engine/source/lighting/shadowMap/pssmLightShadowMap.cpp +++ b/Engine/source/lighting/shadowMap/pssmLightShadowMap.cpp @@ -191,6 +191,16 @@ void PSSMLightShadowMap::_roundProjection(const MatrixF& lightMat, const MatrixF } void PSSMLightShadowMap::_adjustScaleAndOffset(Box3F& clipAABB, Point3F& scale, Point3F& offset) { + + const ShadowMapParams* params = mLight->getExtended(); + + 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.y = 2.0f / (clipAABB.maxExtents.y - clipAABB.minExtents.y); scale.z = 1.0f; @@ -469,7 +479,7 @@ void PSSMLightShadowMap::setShaderParameters(GFXShaderConstBuffer* params, Light params->setSafe( lsc->mOverDarkFactorPSSM, p->overDarkFactor); // 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 > &out, const Frustum &viewFrustum, const Point3F &_ligthDir) diff --git a/Engine/source/lighting/shadowMap/shadowMatHook.cpp b/Engine/source/lighting/shadowMap/shadowMatHook.cpp index c6b8bab017..cbbea17e74 100644 --- a/Engine/source/lighting/shadowMap/shadowMatHook.cpp +++ b/Engine/source/lighting/shadowMap/shadowMatHook.cpp @@ -112,6 +112,18 @@ void ShadowMaterialHook::init( BaseMatInstance *inMat ) 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->setUserObject( inMat->getUserObject() ); newMat->getFeaturesDelegate().bind( &ShadowMaterialHook::_overrideFeatures ); @@ -162,12 +174,6 @@ BaseMatInstance* ShadowMaterialHook::getShadowMat( ShadowType type ) const { 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. return mShadowMat[type]; } diff --git a/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/softShadow.hlsl b/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/softShadow.hlsl index ccc90f0096..463e074bc5 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/softShadow.hlsl +++ b/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/softShadow.hlsl @@ -72,6 +72,37 @@ static float2 sNonUniformTaps[NUM_PRE_TAPS] = /// rotations of the filter taps. TORQUE_UNIFORM_SAMPLER2D(gTapRotationTex, 2); +float shadowCompare(float occluderDepth, float receiverDepth) +{ + return receiverDepth > occluderDepth ? 0.0 : 1.0; +} + +float pcf_sampleTaps( + TORQUE_SAMPLER2D(shadowMap), + float2 sinCos, + float2 shadowPos, + float filterRadius, + float receiverDepth, + float factor_bias, + int startTap, + int endTap ) +{ + float result = 0; + + float2 tap; + for(int t = startTap; t < endTap; t++) + { + tap.x = (sNonUniformTaps[t].x * sinCos.y - sNonUniformTaps[t].y * sinCos.x) * filterRadius; + tap.y = (sNonUniformTaps[t].y * sinCos.y + sNonUniformTaps[t].x * sinCos.x) * filterRadius; + + float occluder = TORQUE_TEX2DLOD(shadowMap, float4(shadowPos + tap,0,0)).r; + + result += shadowCompare(occluder, receiverDepth); + } + + return result / float(endTap - startTap); +} + float softShadow_sampleTaps( TORQUE_SAMPLER2D(shadowMap1), float2 sinCos, float2 shadowPos, @@ -81,6 +112,7 @@ float softShadow_sampleTaps( TORQUE_SAMPLER2D(shadowMap1), int startTap, int endTap ) { + float shadow = 0; float2 tap = 0; @@ -88,9 +120,11 @@ float softShadow_sampleTaps( TORQUE_SAMPLER2D(shadowMap1), { tap.x = ( sNonUniformTaps[t].x * sinCos.y - sNonUniformTaps[t].y * sinCos.x ) * filterRadius; tap.y = ( sNonUniformTaps[t].y * sinCos.y + sNonUniformTaps[t].x * sinCos.x ) * filterRadius; + float occluder = TORQUE_TEX2DLOD( shadowMap1, float4( shadowPos + tap, 0, 0 ) ).r; - float esm = saturate( exp( esmFactor * ( occluder - distToLight ) ) ); + float esm = exp( clamp(esmFactor * (occluder - distToLight), -80.0, 0.0) ); + esm = saturate(esm); shadow += esm / float( endTap - startTap ); } @@ -98,61 +132,45 @@ float softShadow_sampleTaps( TORQUE_SAMPLER2D(shadowMap1), } -float softShadow_filter( TORQUE_SAMPLER2D(shadowMap), - float2 vpos, - float2 shadowPos, - float filterRadius, - float distToLight, - float dotNL, - float esmFactor ) +float softShadow_filter( + TORQUE_SAMPLER2D(shadowMap), + float2 vpos, + float2 shadowPos, + float filterRadius, + float distToLight, + float dotNL, + float esmFactor) { - #ifndef SOFTSHADOW - - // If softshadow is undefined then we skip any complex - // filtering... just do a single sample ESM. - - float occluder = TORQUE_TEX2DLOD(shadowMap, float4(shadowPos, 0, 0)).r; - float shadow = saturate( exp( esmFactor * ( occluder - distToLight ) ) ); - - #else - // Lookup the random rotation for this screen pixel. - float2 sinCos = ( TORQUE_TEX2DLOD(gTapRotationTex, float4(vpos * 16, 0, 0)).rg - 0.5) * 2; - - // Do the prediction taps first. - float shadow = softShadow_sampleTaps( TORQUE_SAMPLER2D_MAKEARG(shadowMap), - sinCos, - shadowPos, - filterRadius, - distToLight, - esmFactor, - 0, - NUM_PRE_TAPS ); - - // We live with only the pretap results if we don't - // have high quality shadow filtering enabled. - #ifdef SOFTSHADOW_HIGH_QUALITY - - // Only do the expensive filtering if we're really - // in a partially shadowed area. - if ( shadow * ( 1.0 - shadow ) * max( dotNL, 0 ) > 0.06 ) - { - shadow += softShadow_sampleTaps( TORQUE_SAMPLER2D_MAKEARG(shadowMap), - sinCos, - shadowPos, - filterRadius, - distToLight, - esmFactor, - NUM_PRE_TAPS, - NUM_TAPS ); - - // This averages the taps above with the results - // of the prediction samples. - shadow *= 0.5; - } - - #endif // SOFTSHADOW_HIGH_QUALITY - - #endif // SOFTSHADOW + float2 sinCos = (TORQUE_TEX2DLOD(gTapRotationTex, float4(vpos * 16,0,0)).rg - 0.5) * 2; + + float shadow = pcf_sampleTaps( + TORQUE_SAMPLER2D_MAKEARG(shadowMap), + sinCos, + shadowPos, + filterRadius, + distToLight, + esmFactor, + 0, + NUM_PRE_TAPS); + +#ifdef SOFTSHADOW_HIGH_QUALITY + + if(shadow * (1.0-shadow) * max(dotNL,0) > 0.06) + { + shadow += pcf_sampleTaps( + TORQUE_SAMPLER2D_MAKEARG(shadowMap), + sinCos, + shadowPos, + filterRadius, + distToLight, + esmFactor, + NUM_PRE_TAPS, + NUM_TAPS); + + shadow *= 0.5; + } - return shadow; +#endif + + return shadow; } \ No newline at end of file diff --git a/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/vectorLightP.hlsl b/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/vectorLightP.hlsl index 77414a81a2..ac2e39cb2c 100644 --- a/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/vectorLightP.hlsl +++ b/Templates/BaseGame/game/core/rendering/shaders/lighting/advanced/vectorLightP.hlsl @@ -64,109 +64,138 @@ uniform float4 scaleY; uniform float4 offsetX; uniform float4 offsetY; -float4 AL_VectorLightShadowCast( TORQUE_SAMPLER2D(sourceShadowMap), - float2 texCoord, - float4x4 worldToLightProj, - float3 worldPos, - float4 scaleX, - float4 scaleY, - float4 offsetX, - float4 offsetY, - float4 farPlaneScalePSSM, - float dotNL) +float ComputeESMFactor(float cascadeNear, float cascadeFar, int shadowMapResolution, float targetShadow = 0.1) { - // Compute shadow map coordinate - float4 pxlPosLightProj = mul(worldToLightProj, float4(worldPos,1)); - float2 baseShadowCoord = pxlPosLightProj.xy / pxlPosLightProj.w; - - // Distance to light, in shadowmap space - float distToLight = pxlPosLightProj.z / pxlPosLightProj.w; - - // Figure out which split to sample from. Basically, we compute the shadowmap sample coord - // for all of the splits and then check if its valid. - float4 shadowCoordX = baseShadowCoord.xxxx; - float4 shadowCoordY = baseShadowCoord.yyyy; - float4 farPlaneDists = distToLight.xxxx; - shadowCoordX *= scaleX; - shadowCoordY *= scaleY; - shadowCoordX += offsetX; - shadowCoordY += offsetY; - farPlaneDists *= farPlaneScalePSSM; - - // If the shadow sample is within -1..1 and the distance - // to the light for this pixel is less than the far plane - // of the split, use it. - float4 finalMask; - 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 && - 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 && - shadowCoordY.z > -0.99 && shadowCoordY.z < 0.99 && - farPlaneDists.z < 1.0 ) - finalMask = float4(0, 0, 1, 0); - - else - finalMask = float4(0, 0, 0, 1); - - float3 debugColor = float3(0,0,0); - - #ifdef NO_SHADOW - debugColor = float3(1.0,1.0,1.0); - #endif - - #ifdef PSSM_DEBUG_RENDER - if ( finalMask.x > 0 ) - debugColor += float3( 1, 0, 0 ); - else if ( finalMask.y > 0 ) - debugColor += float3( 0, 1, 0 ); - else if ( finalMask.z > 0 ) - debugColor += float3( 0, 0, 1 ); - else if ( finalMask.w > 0 ) - debugColor += float3( 1, 1, 0 ); - #endif + float delta = (cascadeFar - cascadeNear) / shadowMapResolution; + float esmFactor = -log(targetShadow) / delta; + return esmFactor; +} - // Here we know what split we're sampling from, so recompute the texcoord location - // Yes, we could just use the result from above, but doing it this way actually saves - // shader instructions. - float2 finalScale; - finalScale.x = dot(finalMask, scaleX); - finalScale.y = dot(finalMask, scaleY); - - float2 finalOffset; - finalOffset.x = dot(finalMask, offsetX); - finalOffset.y = dot(finalMask, offsetY); - - float2 shadowCoord; - shadowCoord = baseShadowCoord * finalScale; - shadowCoord += finalOffset; - - // Convert to texcoord space - shadowCoord = 0.5 * shadowCoord + float2(0.5, 0.5); - shadowCoord.y = 1.0f - shadowCoord.y; - - // Move around inside of atlas - float2 aOffset; - aOffset.x = dot(finalMask, atlasXOffset); - aOffset.y = dot(finalMask, atlasYOffset); - - shadowCoord *= atlasScale; - shadowCoord += aOffset; - - // 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, - distToLight, dotNL, dot( finalMask, overDarkPSSM ) ) ); -}; +float4 AL_VectorLightShadowCast( + 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; + + // PSSM split handling + float4 shadowCoordX = baseShadowCoord.xxxx; + float4 shadowCoordY = baseShadowCoord.yyyy; + float4 farPlaneDists = distToLight.xxxx; + shadowCoordX *= scaleX; + shadowCoordY *= scaleY; + shadowCoordX += offsetX; + shadowCoordY += offsetY; + farPlaneDists *= farPlaneScalePSSM; + + const float cascadeBorder = 0.02; + float4 insideX = step(-1.0 + cascadeBorder, shadowCoordX) * step(shadowCoordX, 1.0 - cascadeBorder); + float4 insideY = step(-1.0 + cascadeBorder, shadowCoordY) * step(shadowCoordY, 1.0 - cascadeBorder); + float4 insideZ = step(farPlaneDists, 1.0); + + float4 cascadeValid = insideX * insideY * insideZ; + + float4 finalMask; + + finalMask.x = cascadeValid.x; + 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); + +#ifdef NO_SHADOW + debugColor = float3(1.0,1.0,1.0); +#endif + +#ifdef PSSM_DEBUG_RENDER + if ( finalMask.x > 0 ) + debugColor += float3( 1, 0, 0 ); + else if ( finalMask.y > 0 ) + debugColor += float3( 0, 1, 0 ); + else if ( finalMask.z > 0 ) + debugColor += float3( 0, 0, 1 ); + else if ( finalMask.w > 0 ) + debugColor += float3( 1, 1, 0 ); +#endif + + // Compute final scale & offset for PSSM atlas + float2 finalScale; + finalScale.x = dot(finalMask, scaleX); + finalScale.y = dot(finalMask, scaleY); + float2 finalOffset; + finalOffset.x = dot(finalMask, offsetX); + finalOffset.y = dot(finalMask, offsetY); + + float2 shadowCoord = baseShadowCoord * finalScale + finalOffset; + + // Convert to texcoord space and atlas + shadowCoord = 0.5 *shadowCoord + 0.5; + shadowCoord.y = 1.0 - shadowCoord.y; + float2 aOffset; + aOffset.x = dot(finalMask, atlasXOffset); + aOffset.y = dot(finalMask, atlasYOffset); + shadowCoord = shadowCoord * atlasScale + aOffset; + + // Compute atlas tile bounds + float2 tileMin = aOffset; + float2 tileMax = aOffset + atlasScale; + + // Convert filter radius to atlas UV space + float2 filterRadiusUV = shadowSoftness * atlasScale; + + // Adjust for PSSM far plane + float farPlaneScale = dot(farPlaneScalePSSM, finalMask); + distToLight *= farPlaneScale; + + + // Shadow map resolution per cascade + 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 {