From 03f6ba55fd1d1ae58aa2d67a19ddc26266d0fe36 Mon Sep 17 00:00:00 2001 From: mrieggeramzn <61609885+mrieggeramzn@users.noreply.github.com> Date: Tue, 4 Jan 2022 08:29:34 -0800 Subject: [PATCH] Adding cascade blending for pcf (#6181) * Adding cascade blending for pcf Signed-off-by: mrieggeramzn * tabs to spaces Signed-off-by: mrieggeramzn * tabs to spaces Signed-off-by: mrieggeramzn * tabs 2 spaces Signed-off-by: mrieggeramzn * Feedback using min3 Signed-off-by: mrieggeramzn * Only enable flag if > 1 cascade Signed-off-by: mrieggeramzn * no blending if last cascade Signed-off-by: mrieggeramzn * Remove unused Signed-off-by: mrieggeramzn --- .../Shadow/DirectionalLightShadow.azsli | 52 +++++++++++++++++-- ...irectionalLightFeatureProcessorInterface.h | 3 ++ .../DirectionalLightFeatureProcessor.cpp | 13 +++-- .../DirectionalLightFeatureProcessor.h | 4 ++ .../CoreLights/DirectionalLightBus.h | 8 +++ .../DirectionalLightComponentConfig.h | 3 ++ .../DirectionalLightComponentConfig.cpp | 6 +-- .../DirectionalLightComponentController.cpp | 17 +++++- .../DirectionalLightComponentController.h | 4 +- .../EditorDirectionalLightComponent.cpp | 6 ++- 10 files changed, 104 insertions(+), 12 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli index 59817af701..870bebdf4b 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli @@ -10,6 +10,7 @@ #include #include +#include #include "Shadow.azsli" #include "ShadowmapAtlasLib.azsli" #include "BicubicPcfFilters.azsli" @@ -25,6 +26,10 @@ enum class ShadowFilterMethod {None, Pcf, Esm, EsmPcf}; option ShadowFilterMethod o_directional_shadow_filtering_method = ShadowFilterMethod::None; option bool o_directional_shadow_receiver_plane_bias_enable = true; +option bool o_blend_between_cascades_enable = false; + +static const float CascadeBlendArea = 0.015f; // might be worth exposing this as a slider. + // DirectionalLightShadow calculates lit ratio for a directional light. class DirectionalLightShadow @@ -98,6 +103,8 @@ class DirectionalLightShadow float SamplePcfBicubic(float3 shadowCoord, uint indexOfCascade); + float CalculateCascadeBlendAmount(const float3 texCoord); + uint m_lightIndex; float3 m_shadowCoords[ViewSrg::MaxCascadeCount]; float m_slopeBias[ViewSrg::MaxCascadeCount]; @@ -174,6 +181,7 @@ bool2 DirectionalLightShadow::IsShadowed(float3 shadowCoord, uint indexOfCascade const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount; Texture2DArray shadowmap = PassSrg::m_directionalLightShadowmap; + [branch] if (shadowCoord.x >= 0. && shadowCoord.x * size < size - PixelMargin && shadowCoord.y >= 0. && shadowCoord.y * size < size - PixelMargin) { @@ -213,20 +221,46 @@ float DirectionalLightShadow::GetVisibilityFromLightPcf() static const float PixelMargin = 1.5; // avoiding artifact between cascade levels. static const float DepthMargin = 1e-8; // avoiding artifact when near depth bounds. + bool cascadeFound = false; + int currentCascadeIndex = 0; + const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize; const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount; for (uint indexOfCascade = 0; indexOfCascade < cascadeCount; ++indexOfCascade) { const float3 shadowCoord = m_shadowCoords[indexOfCascade]; - + if (shadowCoord.x >= 0. && shadowCoord.x * size < size - PixelMargin && shadowCoord.y >= 0. && shadowCoord.y * size < size - PixelMargin && shadowCoord.z < 1. - DepthMargin) { - m_debugInfo.m_cascadeIndex = indexOfCascade; - return SamplePcfBicubic(shadowCoord, indexOfCascade); + currentCascadeIndex = m_debugInfo.m_cascadeIndex = indexOfCascade; + cascadeFound = true; + break; } } + + [branch] + if (cascadeFound) + { + float lit = SamplePcfBicubic(m_shadowCoords[currentCascadeIndex], currentCascadeIndex); + + if(o_blend_between_cascades_enable) + { + const float blendBetweenCascadesAmount = CalculateCascadeBlendAmount(m_shadowCoords[currentCascadeIndex].xyz); + + const int nextCascadeIndex = currentCascadeIndex + 1; + [branch] + if (blendBetweenCascadesAmount < 1.0f && nextCascadeIndex < cascadeCount) + { + const float nextLit = SamplePcfBicubic(m_shadowCoords[nextCascadeIndex], nextCascadeIndex); + lit = lerp(nextLit, lit, blendBetweenCascadesAmount); + } + } + + return lit; + } + m_debugInfo.m_cascadeIndex = cascadeCount; return 1.; } @@ -244,6 +278,8 @@ float DirectionalLightShadow::GetVisibilityFromLightEsm() const float distanceMin = ViewSrg::m_esmsDirectional[indexOfCascade].m_lightDistanceOfCameraViewFrustum; bool2 checkedShadowed = IsShadowed(shadowCoord, indexOfCascade); const float depthDiff = shadowCoord.z - distanceMin; + + [branch] if (checkedShadowed.x && depthDiff >= 0) { const float distanceWithinCameraView = depthDiff / (1. - distanceMin); @@ -274,6 +310,8 @@ float DirectionalLightShadow::GetVisibilityFromLightEsmPcf() const float distanceMin = ViewSrg::m_esmsDirectional[indexOfCascade].m_lightDistanceOfCameraViewFrustum; bool2 checkedShadowed = IsShadowed(shadowCoord, indexOfCascade); const float depthDiff = shadowCoord.z - distanceMin; + + [branch] if (checkedShadowed.x && depthDiff >= 0) { const float distanceWithinCameraView = depthDiff / (1. - distanceMin); @@ -319,6 +357,7 @@ float DirectionalLightShadow::SamplePcfBicubic(float3 shadowCoord, uint indexOfC param.samplerState = SceneSrg::m_hwPcfSampler; param.receiverPlaneDepthBias = o_directional_shadow_receiver_plane_bias_enable ? ComputeReceiverPlaneDepthBias(m_shadowPosDX[indexOfCascade], m_shadowPosDY[indexOfCascade]) : 0; + [branch] if (filteringSampleCount <= 4) { return SampleShadowMapBicubic_4Tap(param); @@ -420,3 +459,10 @@ float3 DirectionalLightShadow::AddDebugColoring( } return color; } + +float DirectionalLightShadow::CalculateCascadeBlendAmount(const float3 texCoord) +{ + const float distanceToOneMin = min3(1.0f - texCoord); + const float currentPixelsBlendBandLocation = min(min(texCoord.x, texCoord.y), distanceToOneMin); + return currentPixelsBlendBandLocation / CascadeBlendArea; +} diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h index fa987a7156..6119585d9d 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h @@ -163,6 +163,9 @@ namespace AZ //! Reduces acne by biasing the shadowmap lookup along the geometric normal. virtual void SetNormalShadowBias(LightHandle handle, float normalShadowBias) = 0; + + //! Sets whether or not blending between shadow map cascades is enabled. + virtual void SetCascadeBlendingEnabled(LightHandle handle, bool enable) = 0; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index ee60ff412a..5cf65adcee 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -198,13 +198,15 @@ namespace AZ if (m_shadowingLightHandle.IsValid()) { - uint32_t shadowFilterMethod = m_shadowData.at(nullptr).GetData(m_shadowingLightHandle.GetIndex()).m_shadowFilterMethod; + const uint32_t shadowFilterMethod = m_shadowData.at(nullptr).GetData(m_shadowingLightHandle.GetIndex()).m_shadowFilterMethod; RPI::ShaderSystemInterface::Get()->SetGlobalShaderOption(m_directionalShadowFilteringMethodName, AZ::RPI::ShaderOptionValue{shadowFilterMethod}); - RPI::ShaderSystemInterface::Get()->SetGlobalShaderOption(m_directionalShadowReceiverPlaneBiasEnableName, AZ::RPI::ShaderOptionValue{ m_shadowProperties.GetData(m_shadowingLightHandle.GetIndex()).m_isReceiverPlaneBiasEnabled }); + RPI::ShaderSystemInterface::Get()->SetGlobalShaderOption(m_directionalShadowReceiverPlaneBiasEnableName, AZ::RPI::ShaderOptionValue{ m_shadowProperties.GetData(m_shadowingLightHandle.GetIndex()).m_isReceiverPlaneBiasEnabled }); const uint32_t cascadeCount = m_shadowData.at(nullptr).GetData(m_shadowingLightHandle.GetIndex()).m_cascadeCount; - ShadowProperty& property = m_shadowProperties.GetData(m_shadowingLightHandle.GetIndex()); + RPI::ShaderSystemInterface::Get()->SetGlobalShaderOption(m_BlendBetweenCascadesEnableName, AZ::RPI::ShaderOptionValue{cascadeCount > 1 && m_shadowProperties.GetData(m_shadowingLightHandle.GetIndex()).m_blendBetwenCascades }); + + ShadowProperty& property = m_shadowProperties.GetData(m_shadowingLightHandle.GetIndex()); bool segmentsNeedUpdate = property.m_segments.empty(); for (const auto& passIt : m_cascadedShadowmapsPasses) { @@ -577,6 +579,11 @@ namespace AZ m_shadowProperties.GetData(handle.GetIndex()).m_isReceiverPlaneBiasEnabled = enable; } + void DirectionalLightFeatureProcessor::SetCascadeBlendingEnabled(LightHandle handle, bool enable) + { + m_shadowProperties.GetData(handle.GetIndex()).m_blendBetwenCascades = enable; + } + void DirectionalLightFeatureProcessor::SetShadowBias(LightHandle handle, float bias) { for (auto& it : m_shadowData) diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h index c206a5097f..83ab7cb15b 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h @@ -176,6 +176,8 @@ namespace AZ // If true, this will reduce the shadow acne introduced by large pcf kernels by estimating the angle of the triangle being shaded // with the ddx/ddy functions. bool m_isReceiverPlaneBiasEnabled = true; + + bool m_blendBetwenCascades = false; }; static void Reflect(ReflectContext* context); @@ -215,6 +217,7 @@ namespace AZ void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override; void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override; + void SetCascadeBlendingEnabled(LightHandle handle, bool enable) override; void SetShadowBias(LightHandle handle, float bias) override; void SetNormalShadowBias(LightHandle handle, float normalShadowBias) override; @@ -367,6 +370,7 @@ namespace AZ Name m_lightTypeName = Name("directional"); Name m_directionalShadowFilteringMethodName = Name("o_directional_shadow_filtering_method"); Name m_directionalShadowReceiverPlaneBiasEnableName = Name("o_directional_shadow_receiver_plane_bias_enable"); + Name m_BlendBetweenCascadesEnableName = Name("o_blend_between_cascades_enable"); static constexpr const char* FeatureProcessorName = "DirectionalLightFeatureProcessor"; }; } // namespace Render diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h index 3cafc183a5..ebccccadcd 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h @@ -184,6 +184,14 @@ namespace AZ //! Reduces acne by biasing the shadowmap lookup along the geometric normal. //! @param normalShadowBias Sets the amount of normal shadow bias to apply. virtual void SetNormalShadowBias(float normalShadowBias) = 0; + + //! Gets whether the directional shadow map has cascade blending enabled. + //! This smooths out the border between cascades at the cost of some performance in the blend area. + virtual bool GetCascadeBlendingEnabled() const = 0; + + //! Sets whether the directional shadow map has cascade blending enabled. + //! @param enable flag specifying whether to enable cascade blending. + virtual void SetCascadeBlendingEnabled(bool enable) = 0; }; using DirectionalLightRequestBus = EBus; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h index 92d5cc9ac0..ca872d78d6 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h @@ -115,6 +115,9 @@ namespace AZ //! Reduces shadow acne by applying a small amount of offset along shadow-space z. float m_shadowBias = 0.0f; + // If true, sample between two adjacent shadow map cascades in a small boundary area to smooth out the transition. + bool m_cascadeBlendingEnabled = false; + bool IsSplitManual() const; bool IsSplitAutomatic() const; bool IsCascadeCorrectionDisabled() const; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp index 78a9cc21d1..d9f93cc825 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp @@ -40,7 +40,8 @@ namespace AZ ->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount) ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled) ->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias) - ->Field("Normal Shadow Bias", &DirectionalLightComponentConfig::m_normalShadowBias); + ->Field("Normal Shadow Bias", &DirectionalLightComponentConfig::m_normalShadowBias) + ->Field("CascadeBlendingEnabled", &DirectionalLightComponentConfig::m_cascadeBlendingEnabled); } } @@ -113,8 +114,7 @@ namespace AZ bool DirectionalLightComponentConfig::IsShadowPcfDisabled() const { - return !(m_shadowFilterMethod == ShadowFilterMethod::Pcf || - m_shadowFilterMethod == ShadowFilterMethod::EsmPcf); + return !(m_shadowFilterMethod == ShadowFilterMethod::Pcf); } bool DirectionalLightComponentConfig::IsEsmDisabled() const diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp index e36868c4bb..ce39a32b19 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp @@ -88,6 +88,8 @@ namespace AZ ->Event("SetShadowBias", &DirectionalLightRequestBus::Events::SetShadowBias) ->Event("GetNormalShadowBias", &DirectionalLightRequestBus::Events::GetNormalShadowBias) ->Event("SetNormalShadowBias", &DirectionalLightRequestBus::Events::SetNormalShadowBias) + ->Event("GetCascadeBlendingEnabled", &DirectionalLightRequestBus::Events::GetCascadeBlendingEnabled) + ->Event("SetCascadeBlendingEnabled", &DirectionalLightRequestBus::Events::SetCascadeBlendingEnabled) ->VirtualProperty("Color", "GetColor", "SetColor") ->VirtualProperty("Intensity", "GetIntensity", "SetIntensity") ->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter") @@ -104,7 +106,8 @@ namespace AZ ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled") ->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias") - ->VirtualProperty("NormalShadowBias", "GetNormalShadowBias", "SetNormalShadowBias"); + ->VirtualProperty("NormalShadowBias", "GetNormalShadowBias", "SetNormalShadowBias") + ->VirtualProperty("BlendBetweenCascadesEnabled", "GetCascadeBlendingEnabled", "SetCascadeBlendingEnabled"); ; } } @@ -537,6 +540,7 @@ namespace AZ SetNormalShadowBias(m_configuration.m_normalShadowBias); SetFilteringSampleCount(m_configuration.m_filteringSampleCount); SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled); + SetCascadeBlendingEnabled(m_configuration.m_cascadeBlendingEnabled); // [GFX TODO][ATOM-1726] share config for multiple light (e.g., light ID). // [GFX TODO][ATOM-2416] adapt to multiple viewports. @@ -636,5 +640,16 @@ namespace AZ m_featureProcessor->SetShadowReceiverPlaneBiasEnabled(m_lightHandle, enable); } + bool DirectionalLightComponentController::GetCascadeBlendingEnabled() const + { + return m_configuration.m_cascadeBlendingEnabled; + } + + void DirectionalLightComponentController::SetCascadeBlendingEnabled(bool enable) + { + m_configuration.m_cascadeBlendingEnabled = enable; + m_featureProcessor->SetCascadeBlendingEnabled(m_lightHandle, enable); + } + } // namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h index 9a6edda666..4aa8aed2fd 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h @@ -83,7 +83,9 @@ namespace AZ float GetShadowBias() const override; void SetShadowBias(float bias) override; float GetNormalShadowBias() const override; - void SetNormalShadowBias(float bias) override; + void SetNormalShadowBias(float bias) override; + bool GetCascadeBlendingEnabled() const override; + void SetCascadeBlendingEnabled(bool enable) override; private: friend class EditorDirectionalLightComponent; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index 545064b86f..2b1510b861 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -161,7 +161,11 @@ namespace AZ ->Attribute(Edit::Attributes::Min, 0.f) ->Attribute(Edit::Attributes::Max, 10.0f) ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ; + ->DataElement( + Edit::UIHandlers::CheckBox, &DirectionalLightComponentConfig::m_cascadeBlendingEnabled, + "Blend between cascades\n", "Enables smooth blending between shadow map cascades.") + ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled) + ; } }