From 1d6f2e290d4b7b50028c9a48462a6e0ee36533f9 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 13 Apr 2026 01:40:29 +0800 Subject: [PATCH] rendering: formalize main light shadow bias settings --- .../builtin/shaders/shadow-caster.shader | 1 - .../Rendering/FrameData/RenderSceneData.h | 10 ++++--- .../Passes/BuiltinDepthStylePassBase.h | 4 +++ .../Rendering/Planning/CameraRenderRequest.h | 3 +++ .../Planning/SceneRenderRequestPlanner.h | 8 +++--- .../Shadow/DirectionalShadowSettings.h | 20 ++++++++++++++ .../Rendering/Execution/CameraRenderer.cpp | 9 ++----- .../BuiltinDepthStylePassBaseResources.cpp | 27 ++++++++++++++----- .../Internal/DirectionalShadowPlanning.cpp | 23 ++++++++++++++++ .../unit/test_camera_scene_renderer.cpp | 14 +++++----- .../test_scene_render_request_planner.cpp | 20 ++++++++++++++ 11 files changed, 113 insertions(+), 26 deletions(-) create mode 100644 engine/include/XCEngine/Rendering/Shadow/DirectionalShadowSettings.h diff --git a/engine/assets/builtin/shaders/shadow-caster.shader b/engine/assets/builtin/shaders/shadow-caster.shader index 3937d73b..6c9fe670 100644 --- a/engine/assets/builtin/shaders/shadow-caster.shader +++ b/engine/assets/builtin/shaders/shadow-caster.shader @@ -65,7 +65,6 @@ Shader "Builtin Shadow Caster" Cull Back ZWrite On ZTest LEqual - Offset 1.0, 2 HLSLPROGRAM #pragma target 4.5 #pragma vertex MainVS diff --git a/engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h b/engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h index 097d6978..3b9bc731 100644 --- a/engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h +++ b/engine/include/XCEngine/Rendering/FrameData/RenderSceneData.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -63,20 +64,23 @@ static_assert( struct RenderDirectionalShadowSamplingData { float enabled = 0.0f; - float receiverDepthBias = 0.0f; - float normalBiasScale = 0.0f; - float shadowStrength = 0.0f; + DirectionalShadowSamplingSettings settings = {}; }; static_assert( sizeof(RenderDirectionalShadowSamplingData) == sizeof(float) * 4u, "RenderDirectionalShadowSamplingData must stay float4-sized for GPU constant layout"); +struct RenderDirectionalShadowCasterBiasData { + DirectionalShadowCasterBiasSettings settings = {}; +}; + struct RenderDirectionalShadowData { bool enabled = false; Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity(); RenderDirectionalShadowMapMetrics mapMetrics = {}; RenderDirectionalShadowSamplingData sampling = {}; + RenderDirectionalShadowCasterBiasData casterBias = {}; RHI::RHIResourceView* shadowMap = nullptr; bool IsValid() const { diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h index 4d5a76c1..a20c5e12 100644 --- a/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinDepthStylePassBase.h @@ -171,6 +171,10 @@ private: ResolvedShaderPass ResolveSurfaceShaderPass( const RenderSceneData& sceneData, const Resources::Material* material) const; + Resources::MaterialRenderState ResolveEffectiveDepthRenderState( + const Resources::ShaderPass* shaderPass, + const Resources::Material* material, + const RenderSceneData& sceneData) const; bool TryBuildSupportedBindingPlan( const Resources::ShaderPass& shaderPass, BuiltinPassResourceBindingPlan& outPlan, diff --git a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h index 799e5cb2..67a631cb 100644 --- a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h +++ b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -131,6 +132,8 @@ struct DirectionalShadowRenderPlan { float texelWorldSize = 0.0f; float nearClipPlane = 0.1f; float farClipPlane = 0.0f; + DirectionalShadowSamplingSettings sampling = {}; + DirectionalShadowCasterBiasSettings casterBias = {}; uint32_t mapWidth = 0; uint32_t mapHeight = 0; RenderCameraData cameraData = {}; diff --git a/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h b/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h index 33679e80..adac0985 100644 --- a/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h +++ b/engine/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h @@ -12,9 +12,9 @@ class Scene; namespace Rendering { -// Planning-only knobs for fitting the single main-light shadow camera to the -// current view. Receiver-side bias/filtering parameters live in runtime shadow -// data and are not configured here. +// Planner-owned knobs for the single main-light shadow, including both camera +// fitting and the default sampling / caster bias values emitted into the +// runtime shadow contract. struct DirectionalShadowPlanningSettings { uint32_t mapDimension = 1024u; float minFocusDistance = 5.0f; @@ -24,6 +24,8 @@ struct DirectionalShadowPlanningSettings { float minDepthRange = 20.0f; float boundsPadding = 1.0f; float minDepthPadding = 2.0f; + DirectionalShadowSamplingSettings sampling = {}; + DirectionalShadowCasterBiasSettings casterBias = {}; }; class SceneRenderRequestPlanner { diff --git a/engine/include/XCEngine/Rendering/Shadow/DirectionalShadowSettings.h b/engine/include/XCEngine/Rendering/Shadow/DirectionalShadowSettings.h new file mode 100644 index 00000000..c6c5e6c8 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Shadow/DirectionalShadowSettings.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace XCEngine { +namespace Rendering { + +struct DirectionalShadowSamplingSettings { + float receiverDepthBias = 0.0015f; + float normalBiasScale = 1.5f; + float shadowStrength = 0.85f; +}; + +struct DirectionalShadowCasterBiasSettings { + float depthBiasFactor = 1.0f; + int32_t depthBiasUnits = 2; +}; + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index 4d9952d9..9cc2e32e 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -19,10 +19,6 @@ namespace Rendering { namespace { -constexpr float kDirectionalShadowReceiverDepthBias = 0.0015f; -constexpr float kDirectionalShadowNormalBiasScale = 1.5f; -constexpr float kDirectionalShadowStrength = 0.85f; - std::shared_ptr CreateDefaultPipelineAsset() { static const std::shared_ptr s_defaultPipelineAsset = std::make_shared(); @@ -395,9 +391,8 @@ RenderDirectionalShadowData BuildDirectionalShadowData( 1.0f / static_cast(plan.mapHeight)); shadowData.mapMetrics.worldTexelSize = texelWorldSize; shadowData.sampling.enabled = 1.0f; - shadowData.sampling.receiverDepthBias = kDirectionalShadowReceiverDepthBias; - shadowData.sampling.normalBiasScale = kDirectionalShadowNormalBiasScale; - shadowData.sampling.shadowStrength = kDirectionalShadowStrength; + shadowData.sampling.settings = plan.sampling; + shadowData.casterBias.settings = plan.casterBias; return shadowData; } diff --git a/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp b/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp index 9448cc41..4373c046 100644 --- a/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp +++ b/engine/src/Rendering/Passes/BuiltinDepthStylePassBaseResources.cpp @@ -108,7 +108,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( const Resources::ShaderPass& shaderPass, const Containers::String& passName, const Resources::ShaderKeywordSet& keywordSet, - const Resources::Material* material, + const Resources::MaterialRenderState& renderState, const RenderSurface& surface, const RHI::InputLayoutDesc& inputLayout) { RHI::GraphicsPipelineDesc pipelineDesc = {}; @@ -116,7 +116,7 @@ RHI::GraphicsPipelineDesc CreatePipelineDesc( pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); ::XCEngine::Rendering::Internal::ApplySurfacePropertiesToGraphicsPipelineDesc(surface, pipelineDesc); pipelineDesc.inputLayout = inputLayout; - ApplyResolvedRenderState(&shaderPass, material, pipelineDesc); + ApplyRenderState(renderState, pipelineDesc); if (!shaderPass.hasFixedFunctionState) { pipelineDesc.blendState.blendEnable = false; @@ -345,6 +345,20 @@ BuiltinDepthStylePassBase::ResolvedShaderPass BuiltinDepthStylePassBase::Resolve return {}; } +Resources::MaterialRenderState BuiltinDepthStylePassBase::ResolveEffectiveDepthRenderState( + const Resources::ShaderPass* shaderPass, + const Resources::Material* material, + const RenderSceneData& sceneData) const { + Resources::MaterialRenderState renderState = ResolveEffectiveRenderState(shaderPass, material); + if (m_passType == BuiltinMaterialPass::ShadowCaster) { + renderState.depthBiasFactor = + sceneData.lighting.mainDirectionalShadow.casterBias.settings.depthBiasFactor; + renderState.depthBiasUnits = + sceneData.lighting.mainDirectionalShadow.casterBias.settings.depthBiasUnits; + } + return renderState; +} + bool BuiltinDepthStylePassBase::TryBuildSupportedBindingPlan( const Resources::ShaderPass& shaderPass, BuiltinPassResourceBindingPlan& outPlan, @@ -466,8 +480,9 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState( } PipelineStateKey pipelineKey = {}; - pipelineKey.renderState = - BuildStaticPipelineRenderStateKey(ResolveEffectiveRenderState(resolvedShaderPass.pass, material)); + const Resources::MaterialRenderState effectiveRenderState = + ResolveEffectiveDepthRenderState(resolvedShaderPass.pass, material, sceneData); + pipelineKey.renderState = BuildStaticPipelineRenderStateKey(effectiveRenderState); pipelineKey.shader = resolvedShaderPass.shader; pipelineKey.passName = resolvedShaderPass.passName; pipelineKey.keywordSignature = ::XCEngine::Rendering::Internal::BuildShaderKeywordSignature(keywordSet); @@ -493,7 +508,7 @@ RHI::RHIPipelineState* BuiltinDepthStylePassBase::GetOrCreatePipelineState( *resolvedShaderPass.pass, resolvedShaderPass.passName, keywordSet, - material, + effectiveRenderState, surface, BuildCommonInputLayout()); RHI::RHIPipelineState* pipelineState = context.device->CreatePipelineState(pipelineDesc); @@ -743,7 +758,7 @@ bool BuiltinDepthStylePassBase::DrawVisibleItem( return false; } const Resources::MaterialRenderState effectiveRenderState = - ResolveEffectiveRenderState(resolvedShaderPass.pass, material); + ResolveEffectiveDepthRenderState(resolvedShaderPass.pass, material, sceneData); PassLayoutKey passLayoutKey = {}; passLayoutKey.shader = resolvedShaderPass.shader; diff --git a/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp b/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp index 1cbece7a..7019ce8f 100644 --- a/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp +++ b/engine/src/Rendering/Planning/Internal/DirectionalShadowPlanning.cpp @@ -111,6 +111,27 @@ DirectionalShadowPlanningSettings SanitizeDirectionalShadowPlanningSettings( if (sanitized.minDepthPadding < 0.0f) { sanitized.minDepthPadding = defaults.minDepthPadding; } + if (!std::isfinite(sanitized.sampling.receiverDepthBias) || + sanitized.sampling.receiverDepthBias < 0.0f) { + sanitized.sampling.receiverDepthBias = defaults.sampling.receiverDepthBias; + } + if (!std::isfinite(sanitized.sampling.normalBiasScale) || + sanitized.sampling.normalBiasScale < 0.0f) { + sanitized.sampling.normalBiasScale = defaults.sampling.normalBiasScale; + } + if (!std::isfinite(sanitized.sampling.shadowStrength)) { + sanitized.sampling.shadowStrength = defaults.sampling.shadowStrength; + } else { + sanitized.sampling.shadowStrength = + std::clamp(sanitized.sampling.shadowStrength, 0.0f, 1.0f); + } + if (!std::isfinite(sanitized.casterBias.depthBiasFactor) || + sanitized.casterBias.depthBiasFactor < 0.0f) { + sanitized.casterBias.depthBiasFactor = defaults.casterBias.depthBiasFactor; + } + if (sanitized.casterBias.depthBiasUnits < 0) { + sanitized.casterBias.depthBiasUnits = defaults.casterBias.depthBiasUnits; + } return sanitized; } @@ -340,6 +361,8 @@ DirectionalShadowRenderPlan BuildDirectionalShadowRenderPlan( plan.texelWorldSize = std::max(shadowTexelSizeX, shadowTexelSizeY); plan.nearClipPlane = minZ; plan.farClipPlane = maxZ; + plan.sampling = shadowSettings.sampling; + plan.casterBias = shadowSettings.casterBias; plan.mapWidth = shadowSettings.mapDimension; plan.mapHeight = shadowSettings.mapDimension; plan.cameraData.view = view.Transpose(); diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index d8b4c985..538aa47c 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -44,7 +44,8 @@ struct MockPipelineState { bool lastHasMainDirectionalShadow = false; XCEngine::RHI::RHIResourceView* lastShadowMap = nullptr; XCEngine::Math::Matrix4x4 lastShadowViewProjection = XCEngine::Math::Matrix4x4::Identity(); - XCEngine::Math::Vector4 lastShadowParams = XCEngine::Math::Vector4::Zero(); + RenderDirectionalShadowMapMetrics lastShadowMapMetrics = {}; + RenderDirectionalShadowSamplingData lastShadowSampling = {}; bool lastHasMainDirectionalShadowKeyword = false; RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None; bool lastHasSkybox = false; @@ -327,7 +328,8 @@ public: m_state->lastHasMainDirectionalShadow = sceneData.lighting.HasMainDirectionalShadow(); m_state->lastShadowMap = sceneData.lighting.mainDirectionalShadow.shadowMap; m_state->lastShadowViewProjection = sceneData.lighting.mainDirectionalShadow.viewProjection; - m_state->lastShadowParams = sceneData.lighting.mainDirectionalShadow.shadowParams; + m_state->lastShadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics; + m_state->lastShadowSampling = sceneData.lighting.mainDirectionalShadow.sampling; m_state->lastHasMainDirectionalShadowKeyword = XCEngine::Resources::ShaderKeywordSetContains( sceneData.globalShaderKeywords, @@ -1338,10 +1340,10 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) { EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[0][3], 11.0f); EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[1][3], 12.0f); EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[2][3], 13.0f); - EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.x, 0.0015f); - EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.y, 1.0f / 256.0f); - EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.z, 1.0f / 128.0f); - EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.w, 0.85f); + EXPECT_FLOAT_EQ(pipelineState->lastShadowSampling.settings.receiverDepthBias, 0.0015f); + EXPECT_FLOAT_EQ(pipelineState->lastShadowMapMetrics.inverseMapSize.x, 1.0f / 256.0f); + EXPECT_FLOAT_EQ(pipelineState->lastShadowMapMetrics.inverseMapSize.y, 1.0f / 128.0f); + EXPECT_FLOAT_EQ(pipelineState->lastShadowSampling.settings.shadowStrength, 0.85f); EXPECT_TRUE(pipelineState->lastHasMainDirectionalShadowKeyword); } diff --git a/tests/Rendering/unit/test_scene_render_request_planner.cpp b/tests/Rendering/unit/test_scene_render_request_planner.cpp index 1470b923..dadb6ae7 100644 --- a/tests/Rendering/unit/test_scene_render_request_planner.cpp +++ b/tests/Rendering/unit/test_scene_render_request_planner.cpp @@ -224,6 +224,11 @@ TEST(SceneRenderRequestPlanner_Test, AppliesConfiguredDirectionalShadowPlanningS settings.maxFocusDistance = 12.0f; settings.perspectiveFocusFactor = 0.01f; settings.minDepthRange = 80.0f; + settings.sampling.receiverDepthBias = 0.0025f; + settings.sampling.normalBiasScale = 2.0f; + settings.sampling.shadowStrength = 0.75f; + settings.casterBias.depthBiasFactor = 2.5f; + settings.casterBias.depthBiasUnits = 4; planner.SetDirectionalShadowPlanningSettings(settings); const std::vector requests = planner.BuildRequests( @@ -241,6 +246,11 @@ TEST(SceneRenderRequestPlanner_Test, AppliesConfiguredDirectionalShadowPlanningS EXPECT_EQ(request.shadowCaster.cameraDataOverride.viewportHeight, 2048u); EXPECT_GE(request.directionalShadow.focusPoint.z, 6.0f); EXPECT_LE(request.directionalShadow.focusPoint.z, 6.3f); + EXPECT_FLOAT_EQ(request.directionalShadow.sampling.receiverDepthBias, 0.0025f); + EXPECT_FLOAT_EQ(request.directionalShadow.sampling.normalBiasScale, 2.0f); + EXPECT_FLOAT_EQ(request.directionalShadow.sampling.shadowStrength, 0.75f); + EXPECT_FLOAT_EQ(request.directionalShadow.casterBias.depthBiasFactor, 2.5f); + EXPECT_EQ(request.directionalShadow.casterBias.depthBiasUnits, 4); EXPECT_GE( request.directionalShadow.farClipPlane - request.directionalShadow.nearClipPlane, 80.0f); @@ -258,6 +268,11 @@ TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSe invalidSettings.minDepthRange = 0.0f; invalidSettings.boundsPadding = -1.0f; invalidSettings.minDepthPadding = -3.0f; + invalidSettings.sampling.receiverDepthBias = -0.5f; + invalidSettings.sampling.normalBiasScale = -2.0f; + invalidSettings.sampling.shadowStrength = 3.0f; + invalidSettings.casterBias.depthBiasFactor = -1.0f; + invalidSettings.casterBias.depthBiasUnits = -6; planner.SetDirectionalShadowPlanningSettings(invalidSettings); const DirectionalShadowPlanningSettings& settings = @@ -270,4 +285,9 @@ TEST(SceneRenderRequestPlanner_Test, SanitizesInvalidDirectionalShadowPlanningSe EXPECT_FLOAT_EQ(settings.minDepthRange, 20.0f); EXPECT_FLOAT_EQ(settings.boundsPadding, 1.0f); EXPECT_FLOAT_EQ(settings.minDepthPadding, 2.0f); + EXPECT_FLOAT_EQ(settings.sampling.receiverDepthBias, 0.0015f); + EXPECT_FLOAT_EQ(settings.sampling.normalBiasScale, 1.5f); + EXPECT_FLOAT_EQ(settings.sampling.shadowStrength, 1.0f); + EXPECT_FLOAT_EQ(settings.casterBias.depthBiasFactor, 1.0f); + EXPECT_EQ(settings.casterBias.depthBiasUnits, 2); }