1212#include < glm/gtc/type_ptr.hpp>
1313
1414#include < algorithm>
15+ #include < cmath>
1516#include < cstring>
1617#include < limits>
1718
@@ -221,7 +222,7 @@ namespace Lux {
221222 { ShaderDataType::Float2, " a_TexCoord" }
222223 };
223224
224- // ── Shadow map (single directional, ortho projection) ─────────────────
225+ // ── Directional shadow maps ────────────────────────── ─────────────────
225226 {
226227 ImageSpecification shadowMapSpec;
227228 shadowMapSpec.DebugName = " ShadowMapArray" ;
@@ -234,35 +235,40 @@ namespace Lux {
234235 m_ShadowMapImage = Image2D::Create (shadowMapSpec);
235236 m_ShadowMapImage->RT_Invalidate ();
236237
237- FramebufferSpecification fbSpec;
238- fbSpec.Width = 4096 ;
239- fbSpec.Height = 4096 ;
240- fbSpec.Attachments = { ImageFormat::Depth };
241- fbSpec.DepthClearValue = 1 .0f ;
242- fbSpec.DebugName = " ShadowMap" ;
243- fbSpec.ExistingImage = m_ShadowMapImage;
244- fbSpec.ExistingImageLayer = 0 ;
245-
246- PipelineSpecification pipelineSpec;
247- pipelineSpec.DebugName = " DirShadowMap" ;
248- pipelineSpec.Shader = Renderer::GetShaderLibrary ()->Get (" DirShadowMap" );
249- pipelineSpec.TargetFramebuffer = Framebuffer::Create (fbSpec);
250- pipelineSpec.Layout = vertexLayout;
251- pipelineSpec.DepthOperator = DepthCompareOperator::LessOrEqual;
252- pipelineSpec.BackfaceCulling = false ; // avoid peter-panning
238+ Ref<Shader> shadowPassShader = Renderer::GetShaderLibrary ()->Get (" DirShadowMap" );
239+ for (uint32_t cascade = 0 ; cascade < ShadowCascadeCount; cascade++)
240+ {
241+ FramebufferSpecification fbSpec;
242+ fbSpec.Width = 4096 ;
243+ fbSpec.Height = 4096 ;
244+ fbSpec.Attachments = { ImageFormat::Depth };
245+ fbSpec.DepthClearValue = 1 .0f ;
246+ fbSpec.DebugName = " ShadowMap-Cascade" + std::to_string (cascade);
247+ fbSpec.ExistingImage = m_ShadowMapImage;
248+ fbSpec.ExistingImageLayer = cascade;
249+
250+ PipelineSpecification pipelineSpec;
251+ pipelineSpec.DebugName = " DirShadowMap-Cascade" + std::to_string (cascade);
252+ pipelineSpec.Shader = shadowPassShader;
253+ pipelineSpec.TargetFramebuffer = Framebuffer::Create (fbSpec);
254+ pipelineSpec.Layout = vertexLayout;
255+ pipelineSpec.DepthOperator = DepthCompareOperator::LessOrEqual;
256+ pipelineSpec.BackfaceCulling = false ; // avoid peter-panning
253257
254- RenderPassSpecification rpSpec;
255- rpSpec.DebugName = " ShadowMapPass" ;
256- rpSpec.Pipeline = Pipeline::Create (pipelineSpec);
258+ RenderPassSpecification rpSpec;
259+ rpSpec.DebugName = " ShadowMapPass-Cascade " + std::to_string (cascade) ;
260+ rpSpec.Pipeline = Pipeline::Create (pipelineSpec);
257261
258- m_ShadowMapPass = RenderPass::Create (rpSpec);
259- m_ShadowMapPass->SetInput (" ShadowData" , m_UBSShadow);
260- m_ShadowMapPass->SetInput (" InstanceTransforms" , m_SBSInstanceTransforms);
261- m_ShadowMapPass->SetInput (" ObjectIndexes" , m_SBSObjectIndexes);
262- LUX_CORE_VERIFY (m_ShadowMapPass->Validate ());
263- m_ShadowMapPass->Bake ();
262+ m_ShadowMapPasses[cascade] = RenderPass::Create (rpSpec);
263+ m_ShadowMapPasses[cascade]->SetInput (" ShadowData" , m_UBSShadow);
264+ m_ShadowMapPasses[cascade]->SetInput (" InstanceTransforms" , m_SBSInstanceTransforms);
265+ m_ShadowMapPasses[cascade]->SetInput (" ObjectIndexes" , m_SBSObjectIndexes);
266+ LUX_CORE_VERIFY (m_ShadowMapPasses[cascade]->Validate ());
267+ m_ShadowMapPasses[cascade]->Bake ();
268+ }
264269
265- m_ShadowPassMaterial = Material::Create (pipelineSpec.Shader , " ShadowPass" );
270+ m_ShadowMapPass = m_ShadowMapPasses[0 ];
271+ m_ShadowPassMaterial = Material::Create (shadowPassShader, " ShadowPass" );
266272 }
267273
268274 // ── Spot shadow atlas (single depth atlas) ───────────────────────────
@@ -1392,6 +1398,98 @@ namespace Lux {
13921398 m_SceneData.SkyboxLod = skyboxLod;
13931399 }
13941400
1401+ void SceneRenderer::CalculateCascades (CascadeData* cascades, const SceneRendererCamera& sceneCamera, const glm::vec3& lightDirection) const
1402+ {
1403+ const float nearClip = glm::max (sceneCamera.Near , 0 .001f );
1404+ const float cameraFar = glm::max (sceneCamera.Far , nearClip + 0 .001f );
1405+ const float shadowFar = glm::clamp (m_Options.MaxShadowDistance , nearClip + 0 .001f , cameraFar);
1406+ const float cameraClipRange = cameraFar - nearClip;
1407+ const float shadowRange = shadowFar - nearClip;
1408+ const float ratio = shadowFar / nearClip;
1409+
1410+ float cascadeSplits[ShadowCascadeCount]{};
1411+ for (uint32_t cascade = 0 ; cascade < ShadowCascadeCount; cascade++)
1412+ {
1413+ const float p = (cascade + 1 .0f ) / static_cast <float >(ShadowCascadeCount);
1414+ const float logSplit = nearClip * std::pow (ratio, p);
1415+ const float uniformSplit = nearClip + shadowRange * p;
1416+ const float splitDistance = glm::mix (uniformSplit, logSplit, glm::clamp (m_Options.ShadowCascadeSplitLambda , 0 .0f , 1 .0f ));
1417+ cascadeSplits[cascade] = (splitDistance - nearClip) / cameraClipRange;
1418+ }
1419+ cascadeSplits[ShadowCascadeCount - 1 ] = (shadowFar - nearClip) / cameraClipRange;
1420+
1421+ const glm::mat4 viewProjection = sceneCamera.Camera .GetUnReversedProjectionMatrix () * sceneCamera.ViewMatrix ;
1422+ const glm::mat4 inverseViewProjection = glm::inverse (viewProjection);
1423+ const float shadowMapResolution = m_ShadowMapPass ? static_cast <float >(m_ShadowMapPass->GetTargetFramebuffer ()->GetWidth ()) : 4096 .0f ;
1424+ const glm::vec3 normalizedLightDirection = glm::normalize (lightDirection);
1425+ const glm::vec3 up = glm::abs (glm::dot (normalizedLightDirection, glm::vec3 (0 .0f , 1 .0f , 0 .0f ))) < 0 .99f
1426+ ? glm::vec3 (0 .0f , 1 .0f , 0 .0f )
1427+ : glm::vec3 (1 .0f , 0 .0f , 0 .0f );
1428+
1429+ float lastSplitDist = 0 .0f ;
1430+ for (uint32_t cascade = 0 ; cascade < ShadowCascadeCount; cascade++)
1431+ {
1432+ const float splitDist = cascadeSplits[cascade];
1433+
1434+ glm::vec3 frustumCorners[8 ] =
1435+ {
1436+ { -1 .0f , 1 .0f , 0 .0f },
1437+ { 1 .0f , 1 .0f , 0 .0f },
1438+ { 1 .0f , -1 .0f , 0 .0f },
1439+ { -1 .0f , -1 .0f , 0 .0f },
1440+ { -1 .0f , 1 .0f , 1 .0f },
1441+ { 1 .0f , 1 .0f , 1 .0f },
1442+ { 1 .0f , -1 .0f , 1 .0f },
1443+ { -1 .0f , -1 .0f , 1 .0f },
1444+ };
1445+
1446+ for (glm::vec3& corner : frustumCorners)
1447+ {
1448+ const glm::vec4 worldCorner = inverseViewProjection * glm::vec4 (corner, 1 .0f );
1449+ corner = glm::vec3 (worldCorner) / worldCorner.w ;
1450+ }
1451+
1452+ for (uint32_t i = 0 ; i < 4 ; i++)
1453+ {
1454+ const glm::vec3 cornerRay = frustumCorners[i + 4 ] - frustumCorners[i];
1455+ frustumCorners[i + 4 ] = frustumCorners[i] + cornerRay * splitDist;
1456+ frustumCorners[i] = frustumCorners[i] + cornerRay * lastSplitDist;
1457+ }
1458+
1459+ glm::vec3 frustumCenter (0 .0f );
1460+ for (const glm::vec3& corner : frustumCorners)
1461+ frustumCenter += corner;
1462+ frustumCenter /= 8 .0f ;
1463+
1464+ float radius = 0 .0f ;
1465+ for (const glm::vec3& corner : frustumCorners)
1466+ radius = glm::max (radius, glm::length (corner - frustumCenter));
1467+ radius = glm::max (std::ceil (radius * 16 .0f ) / 16 .0f , 0 .01f );
1468+
1469+ const glm::vec3 maxExtents (radius);
1470+ const glm::vec3 minExtents = -maxExtents;
1471+ glm::mat4 lightView = glm::lookAt (frustumCenter - normalizedLightDirection * radius, frustumCenter, up);
1472+ glm::mat4 lightProjection = glm::ortho (
1473+ minExtents.x , maxExtents.x ,
1474+ minExtents.y , maxExtents.y ,
1475+ glm::max (0 .0f , m_Options.ShadowCascadeNearPlaneOffset ),
1476+ maxExtents.z - minExtents.z + glm::max (0 .0f , m_Options.ShadowCascadeFarPlaneOffset ));
1477+
1478+ glm::mat4 shadowMatrix = lightProjection * lightView;
1479+ glm::vec4 shadowOrigin = (shadowMatrix * glm::vec4 (0 .0f , 0 .0f , 0 .0f , 1 .0f )) * shadowMapResolution * 0 .5f ;
1480+ glm::vec4 roundedOrigin = glm::round (shadowOrigin);
1481+ glm::vec4 roundOffset = (roundedOrigin - shadowOrigin) * (2 .0f / shadowMapResolution);
1482+ roundOffset.z = 0 .0f ;
1483+ roundOffset.w = 0 .0f ;
1484+ lightProjection[3 ] += roundOffset;
1485+
1486+ cascades[cascade].SplitDepth = -(nearClip + splitDist * cameraClipRange);
1487+ cascades[cascade].ViewProj = lightProjection * lightView;
1488+
1489+ lastSplitDist = splitDist;
1490+ }
1491+ }
1492+
13951493 // ─────────────────────────────────────────────────────────────────────────
13961494 // BeginScene
13971495 // ─────────────────────────────────────────────────────────────────────────
@@ -1618,36 +1716,29 @@ namespace Lux {
16181716 });
16191717 }
16201718
1621- // ── Directional shadow matrix ─────────────────────────────────────────
1622- // Single ortho shadow map centred on the camera position.
1719+ // ── Directional shadow matrices ───────────────────────────────────────
16231720 {
16241721 const auto & dirLight = m_SceneData.SceneLightEnvironment .DirectionalLights [0 ];
16251722
16261723 if (dirLight.Intensity > 0 .0f && dirLight.CastShadows )
16271724 {
1628- const glm::vec3 lightDir = glm::normalize (dirLight.Direction );
1629- const glm::vec3 camPos = glm::vec3 (glm::inverse (camera.ViewMatrix )[3 ]);
1630-
1631- // Pick an up vector that is not collinear with the light direction
1632- const glm::vec3 up = glm::abs (glm::dot (lightDir, glm::vec3 (0 , 1 , 0 ))) < 0 .99f
1633- ? glm::vec3 (0 , 1 , 0 ) : glm::vec3 (1 , 0 , 0 );
1634-
1635- const glm::mat4 lightView = glm::lookAt (
1636- camPos - lightDir * 150 .0f ,
1637- camPos,
1638- up);
1639-
1640- const float halfSize = m_Options.MaxShadowDistance * 0 .5f ;
1641- const glm::mat4 lightProj = glm::ortho (
1642- -halfSize, halfSize,
1643- -halfSize, halfSize,
1644- -500 .0f , 500 .0f );
1645-
1646- m_ShadowUB.ViewProjection [0 ] = lightProj * lightView;
1725+ CascadeData cascades[ShadowCascadeCount];
1726+ CalculateCascades (cascades, camera, dirLight.Direction );
1727+ for (uint32_t cascade = 0 ; cascade < ShadowCascadeCount; cascade++)
1728+ m_ShadowUB.ViewProjection [cascade] = cascades[cascade].ViewProj ;
1729+
1730+ m_RendererDataUB.CascadeSplits = {
1731+ cascades[0 ].SplitDepth ,
1732+ cascades[1 ].SplitDepth ,
1733+ cascades[2 ].SplitDepth ,
1734+ cascades[3 ].SplitDepth
1735+ };
16471736 }
16481737 else
16491738 {
1650- m_ShadowUB.ViewProjection [0 ] = glm::mat4 (1 .0f );
1739+ for (uint32_t cascade = 0 ; cascade < ShadowCascadeCount; cascade++)
1740+ m_ShadowUB.ViewProjection [cascade] = glm::mat4 (1 .0f );
1741+ m_RendererDataUB.CascadeSplits = glm::vec4 (-1000000 .0f );
16511742 }
16521743
16531744 auto shadowData = m_ShadowUB;
@@ -1665,7 +1756,10 @@ namespace Lux {
16651756 m_RendererDataUB.LightSize = dirLight.LightSize ;
16661757 m_RendererDataUB.MaxShadowDistance = m_Options.MaxShadowDistance ;
16671758 m_RendererDataUB.ShadowFade = m_Options.ShadowFade ;
1668- m_RendererDataUB.CascadeSplits = glm::vec4 (-1000000 .0f );
1759+ m_RendererDataUB.CascadeFading = true ;
1760+ m_RendererDataUB.CascadeTransitionFade = m_Options.ShadowCascadeTransitionFade ;
1761+ m_RendererDataUB.ShowCascades = m_Options.ShowShadowCascades ;
1762+ m_RendererDataUB.ShowLightComplexity = m_Options.ShowLightComplexity ;
16691763 m_RendererDataUB.TilesCountX = m_LightTilesCountX;
16701764
16711765 auto rdData = m_RendererDataUB;
@@ -2129,35 +2223,41 @@ namespace Lux {
21292223 const auto & dirLight = m_SceneData.SceneLightEnvironment .DirectionalLights [0 ];
21302224 if (dirLight.Intensity <= 0 .0f || !dirLight.CastShadows )
21312225 {
2132- // Clear the shadow map so geometry doesn't sample stale data
2133- Renderer::BeginRenderPass (m_CommandBuffer, m_ShadowMapPass, /* explicitClear=*/ true );
2134- Renderer::EndRenderPass (m_CommandBuffer);
2226+ // Clear every cascade so geometry doesn't sample stale data.
2227+ for (auto & shadowMapPass : m_ShadowMapPasses)
2228+ {
2229+ Renderer::BeginRenderPass (m_CommandBuffer, shadowMapPass, /* explicitClear=*/ true );
2230+ Renderer::EndRenderPass (m_CommandBuffer);
2231+ }
21352232 return ;
21362233 }
21372234
21382235 Renderer::BeginGPUPerfMarker (m_CommandBuffer, " ShadowMapPass" );
2139- Renderer::BeginRenderPass (m_CommandBuffer, m_ShadowMapPass, /* explicitClear=*/ true );
2140-
2141- for (auto & [key, dc] : m_StaticMeshShadowPassDrawList)
2236+ for (uint32_t cascade = 0 ; cascade < ShadowCascadeCount; cascade++)
21422237 {
2143- auto it = m_ShadowMeshTransformMap.find (key);
2144- if (it == m_ShadowMeshTransformMap.end ()) continue ;
2238+ Renderer::BeginRenderPass (m_CommandBuffer, m_ShadowMapPasses[cascade], /* explicitClear=*/ true );
21452239
2146- const auto & cascadeTmd = it->second .Cascade ;
2147- const uint32_t instCount = (uint32_t )cascadeTmd.ObjectIndices .size ();
2148- if (instCount == 0 ) continue ;
2240+ for (auto & [key, dc] : m_StaticMeshShadowPassDrawList)
2241+ {
2242+ auto it = m_ShadowMeshTransformMap.find (key);
2243+ if (it == m_ShadowMeshTransformMap.end ()) continue ;
21492244
2150- StaticDrawCommand drawCmd = dc;
2151- drawCmd.InstanceCount = instCount;
2245+ const auto & cascadeTmd = it->second .Cascade ;
2246+ const uint32_t instCount = (uint32_t )cascadeTmd.ObjectIndices .size ();
2247+ if (instCount == 0 ) continue ;
21522248
2153- Ref<SceneRenderer> instance = this ;
2154- Renderer::Submit ([instance, drawCmd, cascadeTmd]() mutable {
2155- instance->RT_DrawStaticMesh (
2156- instance->m_CommandBuffer , drawCmd, cascadeTmd, /* bindMaterial=*/ false );
2157- });
2158- }
2249+ StaticDrawCommand drawCmd = dc;
2250+ drawCmd.InstanceCount = instCount;
21592251
2160- Renderer::EndRenderPass (m_CommandBuffer);
2252+ Ref<SceneRenderer> instance = this ;
2253+ Renderer::Submit ([instance, drawCmd, cascadeTmd, cascade]() mutable {
2254+ instance->RT_DrawStaticMesh (
2255+ instance->m_CommandBuffer , drawCmd, cascadeTmd, /* bindMaterial=*/ false , cascade);
2256+ });
2257+ }
2258+
2259+ Renderer::EndRenderPass (m_CommandBuffer);
2260+ }
21612261 Renderer::EndGPUPerfMarker (m_CommandBuffer);
21622262 }
21632263
0 commit comments