Skip to content

Commit

Permalink
Better cascade resolution utilization
Browse files Browse the repository at this point in the history
Move the shadow cascade frustum centers a bit
back, so that the edge of the bounding box is at the
scene camera. Select cascades based on the bounding
sphere.

Should ensure much better resolution utilization.
  • Loading branch information
pr0bability committed Jan 20, 2025
1 parent 78e4fe9 commit c1b645b
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 43 deletions.
23 changes: 20 additions & 3 deletions src/effects/ShadowsExterior.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,13 @@ void ShadowsExteriorEffect::RegisterConstants() {
TheShaderManager->RegisterConstant("TESR_ShadowRadius", &Constants.ShadowMapRadius);
TheShaderManager->RegisterConstant("TESR_ShadowViewProjTransform", (D3DXVECTOR4*)&Constants.ShadowViewProj);
TheShaderManager->RegisterConstant("TESR_ShadowCameraToLightTransform", (D3DXVECTOR4*)&Constants.ShadowCameraToLight);
TheShaderManager->RegisterConstant("TESR_ShadowNearCenter", &ShadowMaps[MapNear].ShadowMapCascadeCenterRadius);
TheShaderManager->RegisterConstant("TESR_ShadowCameraToLightTransformNear", (D3DXVECTOR4*)&ShadowMaps[MapNear].ShadowCameraToLight);
TheShaderManager->RegisterConstant("TESR_ShadowMiddleCenter", &ShadowMaps[MapMiddle].ShadowMapCascadeCenterRadius);
TheShaderManager->RegisterConstant("TESR_ShadowCameraToLightTransformMiddle", (D3DXVECTOR4*)&ShadowMaps[MapMiddle].ShadowCameraToLight);
TheShaderManager->RegisterConstant("TESR_ShadowFarCenter", &ShadowMaps[MapFar].ShadowMapCascadeCenterRadius);
TheShaderManager->RegisterConstant("TESR_ShadowCameraToLightTransformFar", (D3DXVECTOR4*)&ShadowMaps[MapFar].ShadowCameraToLight);
TheShaderManager->RegisterConstant("TESR_ShadowLodCenter", &ShadowMaps[MapLod].ShadowMapCascadeCenterRadius);
TheShaderManager->RegisterConstant("TESR_ShadowCameraToLightTransformLod", (D3DXVECTOR4*)&ShadowMaps[MapLod].ShadowCameraToLight);
TheShaderManager->RegisterConstant("TESR_ShadowCameraToLightTransformOrtho", (D3DXVECTOR4*)&ShadowMaps[MapOrtho].ShadowCameraToLight);
TheShaderManager->RegisterConstant("TESR_ShadowCubeMapLightPosition", &Constants.ShadowCubeMapLightPosition);
Expand Down Expand Up @@ -402,6 +406,7 @@ void Vector4Round(D3DXVECTOR4* out, D3DXVECTOR4* in) {
D3DXMATRIX ShadowsExteriorEffect::GetCascadeViewProj(ShadowMapSettings* ShadowMap, D3DXVECTOR3* SunDir) {
// Get z-range for this cascade.
NiCamera* sceneCamera = WorldSceneGraph->camera;
NiPoint3 cameraPosition = sceneCamera->m_worldTransform.pos;
float depthRange = (sceneCamera->Frustum.Far - sceneCamera->Frustum.Near);
float zNear = ShadowMap->ShadowMapNear / depthRange;
float zFar = ShadowMap->ShadowMapRadius / depthRange;
Expand Down Expand Up @@ -460,20 +465,32 @@ D3DXMATRIX ShadowsExteriorEffect::GetCascadeViewProj(ShadowMapSettings* ShadowMa

D3DXVECTOR3 cascadeExtents = maxExtents - minExtents;

// Create a shadow frustum center by moving the view frustum slice center away from the camera.
// Should make it so we can more easily use the full resolution, which is mostly wasted due to
// stabilization.
D3DXVECTOR3 shadowFrustumCenter;
D3DXVec3Normalize(&shadowFrustumCenter, &frustumCenter); // Get the direction from camera to the frustum center.
shadowFrustumCenter *= sphereRadius; // Move the center so that the length is equal to the sphere radius.

ShadowMap->ShadowMapCascadeCenterRadius.x = shadowFrustumCenter.x;
ShadowMap->ShadowMapCascadeCenterRadius.y = shadowFrustumCenter.y;
ShadowMap->ShadowMapCascadeCenterRadius.z = shadowFrustumCenter.z;
ShadowMap->ShadowMapCascadeCenterRadius.w = sphereRadius;

float nearPlane = 0.0f; // Shadow casters are pancaked to near plane in the vertex shader.
float farPlane = cascadeExtents.z;
D3DXVECTOR3 shadowCameraPos = frustumCenter + D3DXVECTOR3(*SunDir) * -minExtents.z;
D3DXVECTOR3 shadowCameraPos = shadowFrustumCenter + D3DXVECTOR3(*SunDir) * -minExtents.z;

D3DXMATRIX shadowView, shadowProj, shadowViewProj;

D3DXMatrixLookAtRH(&shadowView, &shadowCameraPos, &frustumCenter, &upDir);
D3DXMatrixLookAtRH(&shadowView, &shadowCameraPos, &shadowFrustumCenter, &upDir);
D3DXMatrixOrthoOffCenterRH(&shadowProj, minExtents.x, maxExtents.x, minExtents.y, maxExtents.y, nearPlane, farPlane);
shadowViewProj = shadowView * shadowProj;

// Create the rounding matrix, by projecting the world-space origin and determining
// the fractional offset in texel space.
float sMapSize = Settings.ShadowMaps.CascadeResolution;
NiPoint3 cameraPosition = sceneCamera->m_worldTransform.pos; // We are working in camera relative world space.
// We are working in camera relative world space - camera position is our fixed point for stabilization.
D3DXVECTOR4 shadowOrigin(-cameraPosition.x, -cameraPosition.y, -cameraPosition.z, 1.0f);
D3DXVec4Transform(&shadowOrigin, &shadowOrigin, &shadowViewProj);
D3DXVec4Scale(&shadowOrigin, &shadowOrigin, sMapSize / 2.0f);
Expand Down
1 change: 1 addition & 0 deletions src/effects/ShadowsExterior.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class ShadowsExteriorEffect : public EffectRecord
D3DVIEWPORT9 ShadowMapViewPort;
frustum ShadowMapFrustum;
NiFrustumPlanes ShadowMapFrustumPlanes;
D3DXVECTOR4 ShadowMapCascadeCenterRadius;
FormsStruct Forms;
float ShadowMapInverseResolution;
float ShadowMapRadius;
Expand Down
59 changes: 19 additions & 40 deletions src/hlsl/NewVegas/Effects/SunShadows.fx.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ float4 TESR_ShadowScreenSpaceData; // x: Enabled, y: blurRadius, z: renderDistan
float4 TESR_ShadowRadius; // radius of the 4 cascades
float4 TESR_SunAmbient;
float4 TESR_ShadowFade; // x: sunset attenuation, y: shadows maps active, z: point lights shadows active
float4 TESR_ShadowNearCenter; // x,y,z: center (world space), w: radius
float4 TESR_ShadowMiddleCenter; // x,y,z: center (world space), w: radius
float4 TESR_ShadowFarCenter; // x,y,z: center (world space), w: radius
float4 TESR_ShadowLodCenter; // x,y,z: center (world space), w: radius

sampler2D TESR_DepthBuffer : register(s0) = sampler_state { ADDRESSU = CLAMP; ADDRESSV = CLAMP; MAGFILTER = LINEAR; MINFILTER = LINEAR; MIPFILTER = LINEAR; };
sampler2D TESR_ShadowAtlas : register(s1) = sampler_state { ADDRESSU = CLAMP; ADDRESSV = CLAMP; MAGFILTER = LINEAR; MINFILTER = LINEAR; MIPFILTER = LINEAR; };
Expand Down Expand Up @@ -83,46 +87,21 @@ float GetLightAmountValue(float4x4 lightTransform, float4 coord, float offsetX,

float GetLightAmount(float4 coord, float depth)
{
float blendArea = 0.9f; // 20 % of each cascade to overlap
float shadow;

// getting all shadow values from cascades as negative (to be able to use the dot product to chose the correct one)
// shadows are inverted to mean 1 if in shadow to allow dot product filtering
float4 shadows = {
1 - GetLightAmountValue(TESR_ShadowCameraToLightTransformNear, coord, 0.0f, 0.0f, Bias.x, BleedReduction.x),
1 - GetLightAmountValue(TESR_ShadowCameraToLightTransformMiddle, coord, 0.5f, 0.0f, Bias.y, BleedReduction.y),
1 - GetLightAmountValue(TESR_ShadowCameraToLightTransformFar, coord, 0.0f, 0.5f, Bias.z, BleedReduction.z),
1 - GetLightAmountValue(TESR_ShadowCameraToLightTransformLod, coord, 0.5f, 0.5f, Bias.w, BleedReduction.w),
};

float4 cascade = {
depth < TESR_ShadowRadius.x,
depth >= TESR_ShadowRadius.x * blendArea && depth < TESR_ShadowRadius.y,
depth >= TESR_ShadowRadius.y * blendArea && depth < TESR_ShadowRadius.z,
depth >= TESR_ShadowRadius.z * blendArea && depth < TESR_ShadowRadius.w,
};

// calculate blending areas coefficients between cascades
float4 fadeIn = {
1.0,
clamp(0.0, 1.0, invlerp(blendArea * TESR_ShadowRadius.x, TESR_ShadowRadius.x, depth)),
clamp(0.0, 1.0, invlerp(blendArea * TESR_ShadowRadius.y, TESR_ShadowRadius.y, depth)),
clamp(0.0, 1.0, invlerp(blendArea * TESR_ShadowRadius.z, TESR_ShadowRadius.z, depth)),
};

float4 fadeOut = {
clamp(0.0, 1.0, 1 - invlerp(blendArea * TESR_ShadowRadius.x, TESR_ShadowRadius.x, depth)),
clamp(0.0, 1.0, 1 - invlerp(blendArea * TESR_ShadowRadius.y, TESR_ShadowRadius.y, depth)),
clamp(0.0, 1.0, 1 - invlerp(blendArea * TESR_ShadowRadius.z, TESR_ShadowRadius.z, depth)),
// clamp(0.0, 1.0, 1 - smoothstep(TESR_ShadowRadius.z, TESR_ShadowRadius.w, depth)),
clamp(0.0, 1.0, 1),
};

// apply blending to each cascade shadow
shadows *= fadeIn * fadeOut;

// filter the shadow based on the current valid cascades
return 1 - dot(shadows, cascade);
if (length(coord.xyz - TESR_ShadowNearCenter.xyz) < TESR_ShadowNearCenter.w) {
return GetLightAmountValue(TESR_ShadowCameraToLightTransformNear, coord, 0.0, 0.0, Bias.x, BleedReduction.x);
}
else if (length(coord.xyz - TESR_ShadowMiddleCenter.xyz) < TESR_ShadowMiddleCenter.w) {
return GetLightAmountValue(TESR_ShadowCameraToLightTransformMiddle, coord, 0.5, 0.0, Bias.y, BleedReduction.y);
}
else if (length(coord.xyz - TESR_ShadowFarCenter.xyz) < TESR_ShadowFarCenter.w) {
return GetLightAmountValue(TESR_ShadowCameraToLightTransformFar, coord, 0.0, 0.5, Bias.z, BleedReduction.z);
}
else if (length(coord.xyz - TESR_ShadowFarCenter.xyz) < TESR_ShadowFarCenter.w) {
return GetLightAmountValue(TESR_ShadowCameraToLightTransformFar, coord, 0.5, 0.5, Bias.w, BleedReduction.w);
}
else {
return 1.0f;
}
}

// returns a semi random float3 between 0 and 1 based on the given seed. (blue noise)
Expand Down

0 comments on commit c1b645b

Please sign in to comment.