diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl index 69f86cbe9f..1fc8a410a1 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl @@ -54,6 +54,7 @@ VSOutput ShadowCatcherVS(VSInput IN) DirectionalLightShadow::GetShadowCoords( ViewSrg::m_shadowIndexDirectionalLight, worldPosition, + OUT.m_worldNormal, OUT.m_shadowCoords); return OUT; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl index 11859de7d6..0f9771e480 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl @@ -112,13 +112,15 @@ VSOutput EnhancedPbr_ForwardPassVS(VSInput IN) PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depth) { + const float3 vertexNormal = normalize(IN.m_normal); + // ------- Tangents & Bitangets ------- float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture) || o_detail_normal_useTexture) { - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); + PrepareGeneratedTangent(vertexNormal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } // ------- Depth & Parallax ------- @@ -137,7 +139,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, + GetParallaxInput(vertexNormal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, IN.m_position.w, displacementIsClipped); @@ -150,7 +152,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { - DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, IN.m_shadowCoords); + DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords); } } } @@ -185,7 +187,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex]; float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms. float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor; - surface.vertexNormal = normalize(IN.m_normal); + surface.vertexNormal = vertexNormal; surface.normal = GetDetailedNormalInputWS( isFrontFace, IN.m_normal, tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_normalFactor, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, uvMatrix, o_normal_useTexture, diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index 6a97c0e785..a53dab7a01 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -302,6 +302,7 @@ ProcessedMaterialInputs ProcessStandardMaterialInputs(StandardMaterialInputs inp PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC) { + const float3 vertexNormal = normalize(IN.m_normal); depthNDC = IN.m_position.z; s_blendMaskFromVertexStream = IN.m_blendMask; @@ -321,7 +322,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float || o_layer3_o_clearCoat_normal_useTexture ) { - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); + PrepareGeneratedTangent(vertexNormal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } // ------- Debug Modes ------- @@ -368,7 +369,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { - DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, IN.m_shadowCoords); + DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords); } } } @@ -445,7 +446,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer3.m_normalTS); } // [GFX TODO][ATOM-14591]: This will only work if the normal maps all use the same UV stream. We would need to add support for having them in different UV streams. - surface.vertexNormal = normalize(IN.m_normal); + surface.vertexNormal = vertexNormal; surface.normal = normalize(TangentSpaceToWorld(normalTS, IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex])); // ------- Combine Albedo, roughness, specular, roughness --------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index 8df5e1ba56..1d5e4ad9e3 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -98,6 +98,8 @@ VSOutput StandardPbr_ForwardPassVS(VSInput IN) PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC) { + const float3 vertexNormal = normalize(IN.m_normal); + // ------- Tangents & Bitangets ------- float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; @@ -128,7 +130,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { - DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, IN.m_shadowCoords); + DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords); } } } @@ -146,7 +148,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex]; float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms. - surface.vertexNormal = normalize(IN.m_normal); + surface.vertexNormal = vertexNormal; surface.normal = GetNormalInputWS(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, isFrontFace, IN.m_normal, tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], uvMatrix, o_normal_useTexture, MaterialSrg::m_normalFactor); 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 b53dda13aa..a7122aaf3a 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 @@ -14,6 +14,7 @@ #include "ShadowmapAtlasLib.azsli" #include "BicubicPcfFilters.azsli" #include "ReceiverPlaneDepthBias.azsli" +#include "NormalOffsetShadows.azsli" // Before including this azsli file, a PassSrg must be defined with the following members: // Texture2DArray m_directionalLightShadowmap; @@ -45,6 +46,7 @@ class DirectionalLightShadow static void GetShadowCoords( uint lightIndex, float3 worldPosition, + float3 worldNormal, out float3 shadowCoords[ViewSrg::MaxCascadeCount]); //! This calculates visibility ratio of the surface from the light origin. @@ -109,18 +111,22 @@ class DirectionalLightShadow void DirectionalLightShadow::GetShadowCoords( uint lightIndex, float3 worldPosition, + float3 worldNormal, out float3 shadowCoords[ViewSrg::MaxCascadeCount]) { - const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount; const float shadowBias = ViewSrg::m_directionalLightShadows[lightIndex].m_shadowBias; + const float4x4 lightViewToShadowmapMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_lightViewToShadowmapMatrices; const float4x4 worldToLightViewMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_worldToLightViewMatrices; - - for (uint index = 0; index < cascadeCount; ++index) - { - float4 lightSpacePos = mul(worldToLightViewMatrices[index], float4(worldPosition, 1.)); - lightSpacePos.z += shadowBias; + const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount; + const float3 shadowOffset = ComputeNormalShadowOffset(ViewSrg::m_directionalLightShadows[lightIndex].m_normalShadowBias, worldNormal, ViewSrg::m_directionalLightShadows[lightIndex].m_shadowmapSize); + + for (uint index = 0; index < cascadeCount; ++index) + { + float4 lightSpacePos = mul(worldToLightViewMatrices[index], float4(worldPosition + shadowOffset, 1.)); + lightSpacePos.z += shadowBias; + const float4 clipSpacePos = mul(lightViewToShadowmapMatrices[index], lightSpacePos); shadowCoords[index] = clipSpacePos.xyz / clipSpacePos.w; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/NormalOffsetShadows.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/NormalOffsetShadows.azsli new file mode 100644 index 0000000000..1e78cec89e --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/NormalOffsetShadows.azsli @@ -0,0 +1,22 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +// Helper functions for normal offset shadow mapping. +// Normal Offset is an alternative to slope-scale depth bias. +// We bias the shadow map lookup by transforming the world-position along the geometric normal before hand. +// http://web.archive.org/web/20140810230446/https://www.dissidentlogic.com/old/#Normal%20Offset%20Shadows +// + +// Apply the following to the world position. Then use this modified world position to look up in the shadow map +float3 ComputeNormalShadowOffset(const float normalOffsetBias, const float3 worldNormal, const float shadowMapDimension) +{ + const float shadowmapSize = 2.0f / shadowMapDimension; + return float3(worldNormal * normalOffsetBias * shadowmapSize); +} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Vertex/VertexHelper.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Vertex/VertexHelper.azsli index 4b5047d4c3..c93986cea1 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Vertex/VertexHelper.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Vertex/VertexHelper.azsli @@ -47,6 +47,7 @@ void VertexHelper(in VSInput IN, inout VSOutput OUT, float3 worldPosition, bool DirectionalLightShadow::GetShadowCoords( shadowIndex, worldPosition, + OUT.m_normal, OUT.m_shadowCoords); } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli index 27fa36182c..b3bcc186b6 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli @@ -81,10 +81,10 @@ partial ShaderResourceGroup ViewSrg uint m_shadowmapArraySlice; // array slice who has shadowmap in the atlas. uint m_shadowFilterMethod; float m_boundaryScale; - uint m_predictionSampleCount; uint m_filteringSampleCount; float2 m_unprojectConstants; float m_bias; + float m_normalShadowBias; float m_esmExponent; float3 m_padding; }; @@ -109,7 +109,7 @@ partial ShaderResourceGroup ViewSrg uint m_shadowmapSize; // width and height of shadowmap uint m_cascadeCount; float m_shadowBias; - uint m_predictionSampleCount; + float m_normalShadowBias; uint m_filteringSampleCount; uint m_debugFlags; uint m_shadowFilterMethod; 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 2bba1338a1..fa987a7156 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 @@ -160,6 +160,9 @@ namespace AZ //! Reduces acne by applying a small amount of bias along shadow-space z. virtual void SetShadowBias(LightHandle handle, float bias) = 0; + + //! Reduces acne by biasing the shadowmap lookup along the geometric normal. + virtual void SetNormalShadowBias(LightHandle handle, float normalShadowBias) = 0; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h index 46560f435d..1517d655bb 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h @@ -48,11 +48,13 @@ namespace AZ::Render virtual void SetAspectRatio(ShadowId id, float aspectRatio) = 0; //! Sets the field of view for the shadow in radians in the Y direction. virtual void SetFieldOfViewY(ShadowId id, float fieldOfView) = 0; - //! Sets the maximum resolution of the shadow map + //! Sets the maximum resolution of the shadow map. virtual void SetShadowmapMaxResolution(ShadowId id, ShadowmapSize size) = 0; - //! Sets the shadow bias + //! Sets the shadow bias. virtual void SetShadowBias(ShadowId id, float bias) = 0; - //! Sets the shadow filter method + //! Sets the normal shadow bias. + virtual void SetNormalShadowBias(ShadowId id, float normalShadowBias) = 0; + //! Sets the shadow filter method. virtual void SetShadowFilterMethod(ShadowId id, ShadowFilterMethod method) = 0; //! Sets the sample count for filtering of the shadow boundary, max 64. virtual void SetFilteringSampleCount(ShadowId id, uint16_t count) = 0; diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index 4774ac31a1..6c24b7d35b 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -598,6 +598,15 @@ namespace AZ m_shadowBufferNeedsUpdate = true; } + void DirectionalLightFeatureProcessor::SetNormalShadowBias(LightHandle handle, float normalShadowBias) + { + for (auto& it : m_shadowData) + { + it.second.GetData(handle.GetIndex()).m_normalShadowBias = normalShadowBias; + } + m_shadowBufferNeedsUpdate = true; + } + void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) { PrepareForChangingRenderPipelineAndCameraView(); diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h index 2cf5b0b1e6..8d7a9d76e4 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h @@ -92,8 +92,9 @@ namespace AZ uint32_t m_shadowmapSize = 1; // width and height of shadowmap uint32_t m_cascadeCount = 1; // Reduce acne by applying a small amount of bias to apply along shadow-space z. - float m_shadowBias = 0.0f; - uint32_t m_predictionSampleCount = 0; + float m_shadowBias = 0.0f; + // Reduces acne by biasing the shadowmap lookup along the geometric normal. + float m_normalShadowBias; uint32_t m_filteringSampleCount = 0; uint32_t m_debugFlags = 0; uint32_t m_shadowFilterMethod = 0; @@ -101,6 +102,8 @@ namespace AZ float m_padding[3]; }; + static_assert(sizeof(DirectionalLightShadowData) % 16 == 0); // Structured buffers need alignment to be a multiple of 16 bytes. + class DirectionalLightFeatureProcessor final : public DirectionalLightFeatureProcessorInterface { @@ -216,6 +219,7 @@ namespace AZ void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override; void SetShadowBias(LightHandle handle, float bias) override; + void SetNormalShadowBias(LightHandle handle, float normalShadowBias) override; const Data::Instance GetLightBuffer() const; uint32_t GetLightCount() const; diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp index da92cc04e7..7c0b3563c7 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp @@ -151,6 +151,14 @@ namespace AZ::Render shadowProperty.m_bias = bias; } + void ProjectedShadowFeatureProcessor::SetNormalShadowBias(ShadowId id, float normalShadowBias) + { + AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetNormalShadowBias()."); + + ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id); + shadowProperty.m_normalShadowBias = normalShadowBias; + } + void ProjectedShadowFeatureProcessor::SetShadowmapMaxResolution(ShadowId id, ShadowmapSize size) { AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetShadowmapMaxResolution()."); diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h index 6269166827..fafcb25a08 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h @@ -48,6 +48,7 @@ namespace AZ::Render void SetFieldOfViewY(ShadowId id, float fieldOfViewYRadians) override; void SetShadowmapMaxResolution(ShadowId id, ShadowmapSize size) override; void SetShadowBias(ShadowId id, float bias) override; + void SetNormalShadowBias(ShadowId id, float normalShadowBias) override; void SetShadowFilterMethod(ShadowId id, ShadowFilterMethod method) override; void SetFilteringSampleCount(ShadowId id, uint16_t count) override; void SetShadowProperties(ShadowId id, const ProjectedShadowDescriptor& descriptor) override; @@ -64,10 +65,10 @@ namespace AZ::Render uint32_t m_shadowmapArraySlice = 0; // array slice who has shadowmap in the atlas. uint32_t m_shadowFilterMethod = 0; // filtering method of shadows. float m_boundaryScale = 0.f; // the half of boundary of lit/shadowed areas. (in degrees) - uint32_t m_predictionSampleCount = 0; // sample count to judge whether it is on the shadow boundary or not. uint32_t m_filteringSampleCount = 0; AZStd::array m_unprojectConstants = { {0, 0} }; float m_bias; + float m_normalShadowBias; float m_esmExponent = 87.0f; float m_padding[3]; }; @@ -78,6 +79,7 @@ namespace AZ::Render ProjectedShadowDescriptor m_desc; RPI::ViewPtr m_shadowmapView; float m_bias = 0.1f; + float m_normalShadowBias = 0.0f; ShadowId m_shadowId; }; 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 9ccc4f329b..3cafc183a5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h @@ -176,6 +176,14 @@ namespace AZ //! Shadow bias reduces acne by applying a small amount of offset along shadow-space z. //! @param Sets the amount of bias to apply. virtual void SetShadowBias(float bias) = 0; + + //! Reduces acne by biasing the shadowmap lookup along the geometric normal. + //! @return Returns the amount of bias to apply. + virtual float GetNormalShadowBias() const = 0; + + //! 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; }; 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 20073f437c..92d5cc9ac0 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h @@ -101,6 +101,9 @@ namespace AZ //! Method of shadow's filtering. ShadowFilterMethod m_shadowFilterMethod = ShadowFilterMethod::None; + // Reduces acne by biasing the shadowmap lookup along the geometric normal. + float m_normalShadowBias = 0.0f; + //! Sample Count for filtering (from 4 to 64) //! It is used only when the pixel is predicted as on the boundary. uint16_t m_filteringSampleCount = 32; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp index 98d3f838c0..78a9cc21d1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp @@ -39,7 +39,8 @@ namespace AZ ->Field("ShadowFilterMethod", &DirectionalLightComponentConfig::m_shadowFilterMethod) ->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount) ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled) - ->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias); + ->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias) + ->Field("Normal Shadow Bias", &DirectionalLightComponentConfig::m_normalShadowBias); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp index c20a9f8e17..e36868c4bb 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp @@ -86,6 +86,8 @@ namespace AZ ->Event("SetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::SetShadowReceiverPlaneBiasEnabled) ->Event("GetShadowBias", &DirectionalLightRequestBus::Events::GetShadowBias) ->Event("SetShadowBias", &DirectionalLightRequestBus::Events::SetShadowBias) + ->Event("GetNormalShadowBias", &DirectionalLightRequestBus::Events::GetNormalShadowBias) + ->Event("SetNormalShadowBias", &DirectionalLightRequestBus::Events::SetNormalShadowBias) ->VirtualProperty("Color", "GetColor", "SetColor") ->VirtualProperty("Intensity", "GetIntensity", "SetIntensity") ->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter") @@ -101,7 +103,8 @@ namespace AZ ->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod") ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled") - ->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias"); + ->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias") + ->VirtualProperty("NormalShadowBias", "GetNormalShadowBias", "SetNormalShadowBias"); ; } } @@ -423,6 +426,20 @@ namespace AZ return m_configuration.m_shadowBias; } + void DirectionalLightComponentController::SetNormalShadowBias(float bias) + { + m_configuration.m_normalShadowBias = bias; + if (m_featureProcessor) + { + m_featureProcessor->SetNormalShadowBias(m_lightHandle, bias); + } + } + + float DirectionalLightComponentController::GetNormalShadowBias() const + { + return m_configuration.m_normalShadowBias; + } + void DirectionalLightComponentController::SetFilteringSampleCount(uint32_t count) { const uint16_t count16 = GetMin(Shadow::MaxPcfSamplingCount, aznumeric_cast(count)); @@ -517,6 +534,7 @@ namespace AZ SetDebugColoringEnabled(m_configuration.m_isDebugColoringEnabled); SetShadowFilterMethod(m_configuration.m_shadowFilterMethod); SetShadowBias(m_configuration.m_shadowBias); + SetNormalShadowBias(m_configuration.m_normalShadowBias); SetFilteringSampleCount(m_configuration.m_filteringSampleCount); SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h index a0d552cf99..9a6edda666 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h @@ -81,7 +81,9 @@ namespace AZ bool GetShadowReceiverPlaneBiasEnabled() const override; void SetShadowReceiverPlaneBiasEnabled(bool enable) override; float GetShadowBias() const override; - void SetShadowBias(float width) override; + void SetShadowBias(float bias) override; + float GetNormalShadowBias() const override; + void SetNormalShadowBias(float bias) override; private: friend class EditorDirectionalLightComponent; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp index 1e5b2580f7..db46434d9a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp @@ -136,7 +136,7 @@ namespace AZ ->Attribute(Edit::Attributes::Min, 0.0f) ->Attribute(Edit::Attributes::Max, 100.0f) ->Attribute(Edit::Attributes::SoftMin, 0.0f) - ->Attribute(Edit::Attributes::SoftMax, 1.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::Visibility, &AreaLightComponentConfig::SupportsShadows) ->Attribute(Edit::Attributes::ReadOnly, &AreaLightComponentConfig::ShadowsDisabled) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index 99e2cc1485..545064b86f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -134,7 +134,7 @@ namespace AZ ->EnumAttribute(ShadowFilterMethod::EsmPcf, "ESM+PCF") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count\n", - "This is used only when the pixel is predicted as on the boundary.\n" + "This is used only when the pixel is predicted to be on the boundary.\n" "Specific to PCF and ESM+PCF.") ->Attribute(Edit::Attributes::Min, 4) ->Attribute(Edit::Attributes::Max, 64) @@ -154,6 +154,13 @@ namespace AZ ->Attribute(Edit::Attributes::Min, 0.f) ->Attribute(Edit::Attributes::Max, 0.2) ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement( + Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_normalShadowBias, "Normal Shadow Bias\n", + "Reduces acne by biasing the shadowmap lookup along the geometric normal.\n" + "If this is 0, no biasing is applied.") + ->Attribute(Edit::Attributes::Min, 0.f) + ->Attribute(Edit::Attributes::Max, 10.0f) + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ; } } diff --git a/Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli b/Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli index 4b0fa654e3..ffabf7c298 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli @@ -465,7 +465,7 @@ void ApplyLighting(inout Surface surface, inout LightingData lightingData) const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { - DirectionalLightShadow::GetShadowCoords(shadowIndex, surface.position, lightingData.shadowCoords); + DirectionalLightShadow::GetShadowCoords(shadowIndex, surface.position, surface.vertexNormal, lightingData.shadowCoords); } // Light loops application. diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl index 91e367500b..750cd2fb29 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -59,6 +59,7 @@ VSOutput TerrainPBR_MainPassVS(VertexInput IN) DirectionalLightShadow::GetShadowCoords( shadowIndex, worldPosition, + OUT.m_normal, OUT.m_shadowCoords); }