diff --git a/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h b/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h index d1f3e559..6ad89878 100644 --- a/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h +++ b/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h @@ -12,8 +12,24 @@ class Scene; namespace Rendering { +struct DirectionalShadowPlanningSettings { + uint32_t mapDimension = 1024u; + float minFocusDistance = 5.0f; + float maxFocusDistance = 64.0f; + float perspectiveFocusFactor = 1.0f; + float orthographicFocusFactor = 2.0f; + float minDepthRange = 20.0f; + float boundsPadding = 1.0f; + float minDepthPadding = 2.0f; +}; + class SceneRenderRequestPlanner { public: + SceneRenderRequestPlanner() = default; + + void SetDirectionalShadowPlanningSettings(const DirectionalShadowPlanningSettings& settings); + const DirectionalShadowPlanningSettings& GetDirectionalShadowPlanningSettings() const; + std::vector CollectCameras( const Components::Scene& scene, Components::CameraComponent* overrideCamera) const; @@ -23,6 +39,9 @@ public: Components::CameraComponent* overrideCamera, const RenderContext& context, const RenderSurface& surface) const; + +private: + DirectionalShadowPlanningSettings m_directionalShadowPlanningSettings = {}; }; } // namespace Rendering diff --git a/engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp b/engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp index 1571ea61..5b970e31 100644 --- a/engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp +++ b/engine/src/Rendering/Planning/SceneRenderRequestPlanner.cpp @@ -20,14 +20,41 @@ namespace Rendering { namespace { -constexpr uint32_t kDirectionalShadowMapDimension = 1024; -constexpr float kMinShadowFocusDistance = 5.0f; -constexpr float kMaxShadowFocusDistance = 64.0f; -constexpr float kPerspectiveShadowFocusFactor = 1.0f; -constexpr float kOrthographicShadowFocusFactor = 2.0f; -constexpr float kMinShadowDepthRange = 20.0f; -constexpr float kShadowBoundsPadding = 1.0f; -constexpr float kMinShadowDepthPadding = 2.0f; +DirectionalShadowPlanningSettings SanitizeDirectionalShadowPlanningSettings( + const DirectionalShadowPlanningSettings& settings) { + DirectionalShadowPlanningSettings sanitized = settings; + const DirectionalShadowPlanningSettings defaults = {}; + + if (sanitized.mapDimension == 0u) { + sanitized.mapDimension = defaults.mapDimension; + } + if (sanitized.minFocusDistance <= Math::EPSILON) { + sanitized.minFocusDistance = defaults.minFocusDistance; + } + if (sanitized.maxFocusDistance < sanitized.minFocusDistance) { + sanitized.maxFocusDistance = (settings.maxFocusDistance >= defaults.minFocusDistance) + ? settings.maxFocusDistance + : defaults.maxFocusDistance; + sanitized.maxFocusDistance = std::max(sanitized.maxFocusDistance, sanitized.minFocusDistance); + } + if (sanitized.perspectiveFocusFactor <= Math::EPSILON) { + sanitized.perspectiveFocusFactor = defaults.perspectiveFocusFactor; + } + if (sanitized.orthographicFocusFactor <= Math::EPSILON) { + sanitized.orthographicFocusFactor = defaults.orthographicFocusFactor; + } + if (sanitized.minDepthRange <= Math::EPSILON) { + sanitized.minDepthRange = defaults.minDepthRange; + } + if (sanitized.boundsPadding < 0.0f) { + sanitized.boundsPadding = defaults.boundsPadding; + } + if (sanitized.minDepthPadding < 0.0f) { + sanitized.minDepthPadding = defaults.minDepthPadding; + } + + return sanitized; +} bool ShouldPlanDirectionalShadowForCamera( const Components::CameraComponent& camera, @@ -102,6 +129,7 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( const Components::Scene& scene, const Components::CameraComponent& camera, const Components::LightComponent& light, + const DirectionalShadowPlanningSettings& shadowSettings, float viewportAspect) { DirectionalShadowRenderPlan plan = {}; if (!light.GetCastsShadows()) { @@ -129,13 +157,13 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( const float shadowDistance = camera.GetProjectionType() == Components::CameraProjectionType::Perspective ? std::clamp( - camera.GetFarClipPlane() * kPerspectiveShadowFocusFactor, - kMinShadowFocusDistance, - kMaxShadowFocusDistance) + camera.GetFarClipPlane() * shadowSettings.perspectiveFocusFactor, + shadowSettings.minFocusDistance, + shadowSettings.maxFocusDistance) : std::clamp( - camera.GetOrthographicSize() * kOrthographicShadowFocusFactor, - kMinShadowFocusDistance, - kMaxShadowFocusDistance); + camera.GetOrthographicSize() * shadowSettings.orthographicFocusFactor, + shadowSettings.minFocusDistance, + shadowSettings.maxFocusDistance); const float sliceNear = std::max(camera.GetNearClipPlane(), 0.1f); const float sliceFar = std::max(sliceNear + 0.1f, shadowDistance); @@ -185,7 +213,7 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( frustumWorldBounds.Encapsulate(frustumCorners[index]); } - const float shadowViewDistance = std::max(sliceFar, kMinShadowDepthRange * 0.5f); + const float shadowViewDistance = std::max(sliceFar, shadowSettings.minDepthRange * 0.5f); const Math::Vector3 shadowWorldPosition = focusPoint + lightDirection * shadowViewDistance; @@ -256,17 +284,24 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( ExpandLightSpaceBounds(view, worldCorners, minX, maxX, minY, maxY, minZ, maxZ); } - minX -= kShadowBoundsPadding; - maxX += kShadowBoundsPadding; - minY -= kShadowBoundsPadding; - maxY += kShadowBoundsPadding; - minZ -= kMinShadowDepthPadding; - maxZ += kMinShadowDepthPadding; + minX -= shadowSettings.boundsPadding; + maxX += shadowSettings.boundsPadding; + minY -= shadowSettings.boundsPadding; + maxY += shadowSettings.boundsPadding; + minZ -= shadowSettings.minDepthPadding; + maxZ += shadowSettings.minDepthPadding; const float shadowHalfExtent = std::max( std::max(std::abs(minX), std::abs(maxX)), std::max(std::abs(minY), std::abs(maxY))); - const float shadowDepthRange = std::max(maxZ - minZ, kMinShadowDepthRange); + const float unclampedShadowDepthRange = maxZ - minZ; + if (unclampedShadowDepthRange < shadowSettings.minDepthRange) { + const float centerZ = (minZ + maxZ) * 0.5f; + const float halfDepthRange = shadowSettings.minDepthRange * 0.5f; + minZ = centerZ - halfDepthRange; + maxZ = centerZ + halfDepthRange; + } + const Math::Matrix4x4 projection = Math::Matrix4x4::Orthographic( minX, maxX, @@ -281,8 +316,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( plan.orthographicHalfExtent = shadowHalfExtent; plan.nearClipPlane = minZ; plan.farClipPlane = maxZ; - plan.mapWidth = kDirectionalShadowMapDimension; - plan.mapHeight = kDirectionalShadowMapDimension; + plan.mapWidth = shadowSettings.mapDimension; + plan.mapHeight = shadowSettings.mapDimension; plan.cameraData.view = view.Transpose(); plan.cameraData.projection = projection.Transpose(); plan.cameraData.viewProjection = (projection * view).Transpose(); @@ -296,6 +331,15 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( } // namespace +void SceneRenderRequestPlanner::SetDirectionalShadowPlanningSettings( + const DirectionalShadowPlanningSettings& settings) { + m_directionalShadowPlanningSettings = SanitizeDirectionalShadowPlanningSettings(settings); +} + +const DirectionalShadowPlanningSettings& SceneRenderRequestPlanner::GetDirectionalShadowPlanningSettings() const { + return m_directionalShadowPlanningSettings; +} + std::vector SceneRenderRequestPlanner::CollectCameras( const Components::Scene& scene, Components::CameraComponent* overrideCamera) const { @@ -355,8 +399,12 @@ std::vector SceneRenderRequestPlanner::BuildRequests( ? static_cast(surface.GetRenderAreaWidth()) / static_cast(surface.GetRenderAreaHeight()) : 1.0f; - request.directionalShadow = - BuildDirectionalShadowRenderPlan(scene, *camera, *mainDirectionalLight, viewportAspect); + request.directionalShadow = BuildDirectionalShadowRenderPlan( + scene, + *camera, + *mainDirectionalLight, + m_directionalShadowPlanningSettings, + viewportAspect); if (request.directionalShadow.IsValid()) { request.shadowCaster.clearFlags = RenderClearFlags::Depth; request.shadowCaster.hasCameraDataOverride = true; diff --git a/tests/Rendering/unit/test_scene_render_request_planner.cpp b/tests/Rendering/unit/test_scene_render_request_planner.cpp index 4e2df8aa..1470b923 100644 --- a/tests/Rendering/unit/test_scene_render_request_planner.cpp +++ b/tests/Rendering/unit/test_scene_render_request_planner.cpp @@ -200,3 +200,74 @@ TEST(SceneRenderRequestPlanner_Test, SkipsDirectionalShadowPlanForOverlayCameraW EXPECT_FALSE(requests[1].directionalShadow.enabled); EXPECT_FALSE(requests[1].shadowCaster.hasCameraDataOverride); } + +TEST(SceneRenderRequestPlanner_Test, AppliesConfiguredDirectionalShadowPlanningSettings) { + Scene scene("SceneRenderRequestPlannerCustomDirectionalShadow"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetStackType(CameraStackType::Base); + camera->SetDepth(1.0f); + camera->SetProjectionType(CameraProjectionType::Perspective); + camera->SetNearClipPlane(0.3f); + camera->SetFarClipPlane(100.0f); + + GameObject* shadowLightObject = scene.CreateGameObject("ShadowLight"); + auto* shadowLight = shadowLightObject->AddComponent(); + shadowLight->SetLightType(LightType::Directional); + shadowLight->SetCastsShadows(true); + + SceneRenderRequestPlanner planner; + DirectionalShadowPlanningSettings settings = {}; + settings.mapDimension = 2048u; + settings.minFocusDistance = 12.0f; + settings.maxFocusDistance = 12.0f; + settings.perspectiveFocusFactor = 0.01f; + settings.minDepthRange = 80.0f; + planner.SetDirectionalShadowPlanningSettings(settings); + + const std::vector requests = planner.BuildRequests( + scene, + nullptr, + CreateValidContext(), + RenderSurface(640, 360)); + + ASSERT_EQ(requests.size(), 1u); + const CameraRenderRequest& request = requests[0]; + ASSERT_TRUE(request.directionalShadow.IsValid()); + EXPECT_EQ(request.directionalShadow.mapWidth, 2048u); + EXPECT_EQ(request.directionalShadow.mapHeight, 2048u); + EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportWidth, 2048u); + EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 2048u); + EXPECT_GE(request.directionalShadow.focusPoint.z, 6.0f); + EXPECT_LE(request.directionalShadow.focusPoint.z, 6.3f); + EXPECT_GE( + request.directionalShadow.farClipPlane - request.directionalShadow.nearClipPlane, + 80.0f); +} + +TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSettings) { + SceneRenderRequestPlanner planner; + + DirectionalShadowPlanningSettings invalidSettings = {}; + invalidSettings.mapDimension = 0u; + invalidSettings.minFocusDistance = -1.0f; + invalidSettings.maxFocusDistance = 0.0f; + invalidSettings.perspectiveFocusFactor = 0.0f; + invalidSettings.orthographicFocusFactor = -2.0f; + invalidSettings.minDepthRange = 0.0f; + invalidSettings.boundsPadding = -1.0f; + invalidSettings.minDepthPadding = -3.0f; + planner.SetDirectionalShadowPlanningSettings(invalidSettings); + + const DirectionalShadowPlanningSettings& settings = + planner.GetDirectionalShadowPlanningSettings(); + EXPECT_EQ(settings.mapDimension, 1024u); + EXPECT_FLOAT_EQ(settings.minFocusDistance, 5.0f); + EXPECT_FLOAT_EQ(settings.maxFocusDistance, 64.0f); + EXPECT_FLOAT_EQ(settings.perspectiveFocusFactor, 1.0f); + EXPECT_FLOAT_EQ(settings.orthographicFocusFactor, 2.0f); + EXPECT_FLOAT_EQ(settings.minDepthRange, 20.0f); + EXPECT_FLOAT_EQ(settings.boundsPadding, 1.0f); + EXPECT_FLOAT_EQ(settings.minDepthPadding, 2.0f); +}