From 95edf0435fb56d704221a454dbaece13b86efa0c Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 13 Apr 2026 03:13:30 +0800 Subject: [PATCH] rendering: stabilize single-map directional shadow fitting --- .../Planning/SceneRenderRequestPlanner.h | 6 +- .../Internal/DirectionalShadowPlanning.cpp | 2 +- .../test_scene_render_request_planner.cpp | 100 ++++++++++++++++-- 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h b/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h index adac0985..f8464a2f 100644 --- a/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h +++ b/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h @@ -16,9 +16,11 @@ namespace Rendering { // fitting and the default sampling / caster bias values emitted into the // runtime shadow contract. struct DirectionalShadowPlanningSettings { - uint32_t mapDimension = 1024u; + // Keep the single-map MainLight shadow at a higher baseline so the current + // non-cascaded implementation has enough texel density to be usable. + uint32_t mapDimension = 2048u; float minFocusDistance = 5.0f; - float maxFocusDistance = 64.0f; + float maxFocusDistance = 32.0f; float perspectiveFocusFactor = 1.0f; float orthographicFocusFactor = 2.0f; float minDepthRange = 20.0f; diff --git a/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp b/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp index 7019ce8f..e357c90b 100644 --- a/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp +++ b/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp @@ -286,7 +286,7 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( gameObject->GetComponent(); if (meshRenderer == nullptr || !meshRenderer->IsEnabled() || - (!meshRenderer->GetCastShadows() && !meshRenderer->GetReceiveShadows())) { + !meshRenderer->GetCastShadows()) { continue; } diff --git a/tests/Rendering/unit/test_scene_render_request_planner.cpp b/tests/Rendering/unit/test_scene_render_request_planner.cpp index 20b02979..d5229411 100644 --- a/tests/Rendering/unit/test_scene_render_request_planner.cpp +++ b/tests/Rendering/unit/test_scene_render_request_planner.cpp @@ -2,10 +2,16 @@ #include #include +#include +#include +#include +#include #include #include using namespace XCEngine::Components; +using namespace XCEngine::Core; +using namespace XCEngine::Resources; using namespace XCEngine::Rendering; namespace { @@ -18,6 +24,17 @@ RenderContext CreateValidContext() { return context; } +Mesh* CreatePlannerTestMesh(const char* path, const XCEngine::Math::Bounds& bounds) { + auto* mesh = new Mesh(); + IResource::ConstructParams params = {}; + params.name = "PlannerTestMesh"; + params.path = path; + params.guid = ResourceGUID::Generate(path); + mesh->Initialize(params); + mesh->SetBounds(bounds); + return mesh; +} + } // namespace TEST(SceneRenderRequestPlanner_Test, FallsBackToSceneCamerasWhenOverrideCameraIsNotUsable) { @@ -154,14 +171,14 @@ TEST(SceneRenderRequestPlanner_Test, BuildsDirectionalShadowPlanForBaseCameraWhe EXPECT_EQ(request.camera, camera); ASSERT_TRUE(request.directionalShadow.IsValid()); EXPECT_TRUE(request.directionalShadow.enabled); - EXPECT_EQ(request.directionalShadow.mapWidth, 1024u); - EXPECT_EQ(request.directionalShadow.mapHeight, 1024u); + EXPECT_EQ(request.directionalShadow.mapWidth, 2048u); + EXPECT_EQ(request.directionalShadow.mapHeight, 2048u); EXPECT_EQ(request.directionalShadow.lightDirection, XCEngine::Math::Vector3::Back()); EXPECT_GT(request.directionalShadow.focusPoint.z, 0.0f); EXPECT_TRUE(request.shadowCaster.hasCameraDataOverride); EXPECT_EQ(request.shadowCaster.clearFlags, RenderClearFlags::Depth); - EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportWidth, 1024u); - EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 1024u); + EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportWidth, 2048u); + EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 2048u); EXPECT_EQ(request.shadowCaster.cameraDataOverride.clearFlags, RenderClearFlags::Depth); } @@ -277,9 +294,9 @@ TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSe const DirectionalShadowPlanningSettings& settings = planner.GetDirectionalShadowPlanningSettings(); - EXPECT_EQ(settings.mapDimension, 1024u); + EXPECT_EQ(settings.mapDimension, 2048u); EXPECT_FLOAT_EQ(settings.minFocusDistance, 5.0f); - EXPECT_FLOAT_EQ(settings.maxFocusDistance, 64.0f); + EXPECT_FLOAT_EQ(settings.maxFocusDistance, 32.0f); EXPECT_FLOAT_EQ(settings.perspectiveFocusFactor, 1.0f); EXPECT_FLOAT_EQ(settings.orthographicFocusFactor, 2.0f); EXPECT_FLOAT_EQ(settings.minDepthRange, 20.0f); @@ -291,3 +308,74 @@ TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSe EXPECT_FLOAT_EQ(settings.casterBias.depthBiasFactor, 2.5f); EXPECT_EQ(settings.casterBias.depthBiasUnits, 4); } + +TEST(SceneRenderRequestPlanner_Test, IgnoresReceiveOnlyMeshesWhenFittingDirectionalShadowBounds) { + Scene scene("SceneRenderRequestPlannerReceiveOnlyShadowBounds"); + + 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); + + GameObject* casterObject = scene.CreateGameObject("Caster"); + casterObject->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(0.0f, 0.0f, 6.0f)); + auto* casterFilter = casterObject->AddComponent(); + auto* casterRenderer = casterObject->AddComponent(); + casterRenderer->SetCastShadows(true); + casterRenderer->SetReceiveShadows(true); + Mesh* casterMesh = CreatePlannerTestMesh( + "Meshes/planner_caster.mesh", + XCEngine::Math::Bounds( + XCEngine::Math::Vector3(-0.5f, -0.5f, -0.5f), + XCEngine::Math::Vector3(1.0f, 1.0f, 1.0f))); + casterFilter->SetMesh(casterMesh); + + SceneRenderRequestPlanner planner; + const std::vector baselineRequests = planner.BuildRequests( + scene, + nullptr, + CreateValidContext(), + RenderSurface(640, 360)); + + ASSERT_EQ(baselineRequests.size(), 1u); + ASSERT_TRUE(baselineRequests[0].directionalShadow.IsValid()); + const float baselineHalfExtent = baselineRequests[0].directionalShadow.orthographicHalfExtent; + const float baselineTexelWorldSize = baselineRequests[0].directionalShadow.texelWorldSize; + + GameObject* receiverObject = scene.CreateGameObject("ReceiverOnly"); + receiverObject->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(0.0f, 0.0f, 16.0f)); + auto* receiverFilter = receiverObject->AddComponent(); + auto* receiverRenderer = receiverObject->AddComponent(); + receiverRenderer->SetCastShadows(false); + receiverRenderer->SetReceiveShadows(true); + Mesh* receiverMesh = CreatePlannerTestMesh( + "Meshes/planner_receiver_only.mesh", + XCEngine::Math::Bounds( + XCEngine::Math::Vector3(-20.0f, -20.0f, -0.5f), + XCEngine::Math::Vector3(40.0f, 40.0f, 1.0f))); + receiverFilter->SetMesh(receiverMesh); + + const std::vector updatedRequests = planner.BuildRequests( + scene, + nullptr, + CreateValidContext(), + RenderSurface(640, 360)); + + ASSERT_EQ(updatedRequests.size(), 1u); + ASSERT_TRUE(updatedRequests[0].directionalShadow.IsValid()); + EXPECT_FLOAT_EQ(updatedRequests[0].directionalShadow.orthographicHalfExtent, baselineHalfExtent); + EXPECT_FLOAT_EQ(updatedRequests[0].directionalShadow.texelWorldSize, baselineTexelWorldSize); + + casterFilter->ClearMesh(); + receiverFilter->ClearMesh(); + delete casterMesh; + delete receiverMesh; +}