Adding receiver plane bias to directional shadows (#3305)

* Adding receiver plane bias to directional shadows

* fixing whitespace issue

* Slight spelling change
monroegm-disable-blank-issue-2
mrieggeramzn 4 years ago committed by GitHub
parent 3a9268c955
commit e15634a867
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,6 +8,8 @@
#pragma once #pragma once
#include "ReceiverPlaneDepthBias.azsli"
// Pcf filtering taken from legacy Cry/Lumberyard ShadowCommon.cfi. // Pcf filtering taken from legacy Cry/Lumberyard ShadowCommon.cfi.
// //
// Approximate an expensive but smooth bicubic filter by taking multiple bilinear samples and then weighting the samples appropriately. // Approximate an expensive but smooth bicubic filter by taking multiple bilinear samples and then weighting the samples appropriately.
@ -28,8 +30,12 @@ struct SampleShadowMapBicubicParameters
float invShadowMapSize; float invShadowMapSize;
SamplerComparisonState samplerState; SamplerComparisonState samplerState;
float comparisonValue; float comparisonValue;
// Optionally pass in a non-zero value into the receiverPlaneDepthBias to reduce shadow acne with large Pcf kernels
float2 receiverPlaneDepthBias;
}; };
float SampleShadowMapBicubic_4Tap(SampleShadowMapBicubicParameters param) float SampleShadowMapBicubic_4Tap(SampleShadowMapBicubicParameters param)
{ {
// Compute the shadow map UV and fractionals. // Compute the shadow map UV and fractionals.
@ -47,13 +53,19 @@ float SampleShadowMapBicubic_4Tap(SampleShadowMapBicubicParameters param)
float2 uv1 = (fractionalUV / weights1) + 1.0; float2 uv1 = (fractionalUV / weights1) + 1.0;
// Accumulate the shadow results and return the resolve value. // Accumulate the shadow results and return the resolve value.
float shadow; float shadow = 0;
const float2 uvOffsets[4] = {float2(uv0.x, uv0.y), float2(uv1.x, uv0.y), float2(uv0.x, uv1.y), float2(uv1.x, uv1.y)};
const float weights[4] = {weights0.x * weights0.y, weights1.x * weights0.y, weights0.x * weights1.y, weights1.x * weights1.y};
[unroll]
for(int i = 0 ; i < 4; ++i)
{
const float2 texCoordOffset = uvOffsets[i] * param.invShadowMapSize;
const float fragmentDistance = param.comparisonValue + ApplyReceiverPlaneDepthBias(param.receiverPlaneDepthBias, texCoordOffset);
shadow += weights[i] * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + texCoordOffset, param.shadowPos.z), fragmentDistance);
}
shadow = weights0.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadow += weights1.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadow += weights0.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadow += weights1.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadow /= 16.0; shadow /= 16.0;
return shadow * shadow; return shadow * shadow;
} }
@ -80,20 +92,26 @@ float SampleShadowMapBicubic_9Tap(SampleShadowMapBicubicParameters param)
float2 uv1 = (3.0 + fractionalUV) / weights1; float2 uv1 = (3.0 + fractionalUV) / weights1;
float2 uv2 = (fractionalUV / weights2) + 2.0; float2 uv2 = (fractionalUV / weights2) + 2.0;
// Accumulate the shadow results and return the resolve value.
float shadowResult;
shadowResult = weights0.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadowResult += weights1.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadowResult += weights2.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv2.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadowResult += weights0.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); const float2 uvOffsets[9] = {float2(uv0.x, uv0.y), float2(uv1.x, uv0.y), float2(uv2.x, uv0.y),
shadowResult += weights1.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); float2(uv0.x, uv1.y), float2(uv1.x, uv1.y), float2(uv2.x, uv1.y),
shadowResult += weights2.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv2.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); float2(uv0.x, uv2.y), float2(uv1.x, uv2.y), float2(uv2.x, uv2.y) };
const float weights[9] = {weights0.x * weights0.y, weights1.x * weights0.y, weights2.x * weights0.y,
weights0.x * weights1.y, weights1.x * weights1.y, weights2.x * weights1.y,
weights0.x * weights2.y, weights1.x * weights2.y, weights2.x * weights2.y};
shadowResult += weights0.x * weights2.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv2.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); // Accumulate the shadow results and return the resolve value.
shadowResult += weights1.x * weights2.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv2.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); float shadowResult = 0;
shadowResult += weights2.x * weights2.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv2.x, uv2.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
[unroll]
for(int i = 0 ; i < 9 ; ++i)
{
const float2 texCoordOffset = uvOffsets[i] * param.invShadowMapSize;
const float fragmentDistance = param.comparisonValue + ApplyReceiverPlaneDepthBias(param.receiverPlaneDepthBias, texCoordOffset);
shadowResult += weights[i] * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + texCoordOffset, param.shadowPos.z), fragmentDistance);
}
shadowResult /= 144.0; shadowResult /= 144.0;
return shadowResult * shadowResult; return shadowResult * shadowResult;
@ -123,28 +141,27 @@ float SampleShadowMapBicubic_16Tap(SampleShadowMapBicubicParameters param)
float2 uv2 = -((7.0 * fractionalUV + 5.0) / weights2) + 1.0; float2 uv2 = -((7.0 * fractionalUV + 5.0) / weights2) + 1.0;
float2 uv3 = -(fractionalUV / weights3) + 3.0; float2 uv3 = -(fractionalUV / weights3) + 3.0;
// Accumulate the shadow results and return the resolve value. const float2 uvOffsets[16] = {float2(uv0.x, uv0.y), float2(uv1.x, uv0.y), float2(uv2.x, uv0.y), float2(uv3.x, uv0.y),
float shadow; float2(uv0.x, uv1.y), float2(uv1.x, uv1.y), float2(uv2.x, uv1.y), float2(uv3.x, uv1.y),
float2(uv0.x, uv2.y), float2(uv1.x, uv2.y), float2(uv2.x, uv2.y), float2(uv3.x, uv2.y),
shadow = weights0.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); float2(uv0.x, uv3.y), float2(uv1.x, uv3.y), float2(uv2.x, uv3.y), float2(uv3.x, uv3.y)};
shadow += weights1.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadow += weights2.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv2.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); const float weights[16] = {weights0.x * weights0.y, weights1.x * weights0.y, weights2.x * weights0.y, weights3.x * weights0.y,
shadow += weights3.x * weights0.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv3.x, uv0.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); weights0.x * weights1.y, weights1.x * weights1.y, weights2.x * weights1.y, weights3.x * weights1.y,
weights0.x * weights2.y, weights1.x * weights2.y, weights2.x * weights2.y, weights3.x * weights2.y,
shadow += weights0.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); weights0.x * weights3.y, weights1.x * weights3.y, weights2.x * weights3.y, weights3.x * weights3.y};
shadow += weights1.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadow += weights2.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv2.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); // Accumulate the shadow results and return the resolve value.
shadow += weights3.x * weights1.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv3.x, uv1.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); float shadow = 0;
shadow += weights0.x * weights2.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv2.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); [unroll]
shadow += weights1.x * weights2.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv2.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); for(int i = 0 ; i < 16 ; ++i)
shadow += weights2.x * weights2.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv2.x, uv2.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); {
shadow += weights3.x * weights2.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv3.x, uv2.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); const float2 texCoordOffset = uvOffsets[i] * param.invShadowMapSize;
const float fragmentDistance = param.comparisonValue + ApplyReceiverPlaneDepthBias(param.receiverPlaneDepthBias, texCoordOffset);
shadow += weights0.x * weights3.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv0.x, uv3.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadow += weights1.x * weights3.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv1.x, uv3.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); shadow += weights[i] * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + texCoordOffset, param.shadowPos.z), fragmentDistance);
shadow += weights2.x * weights3.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv2.x, uv3.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue); }
shadow += weights3.x * weights3.y * param.shadowMap.SampleCmpLevelZero(param.samplerState, float3(texelCenter + float2(uv3.x, uv3.y) * param.invShadowMapSize, param.shadowPos.z), param.comparisonValue);
shadow /= 2704; shadow /= 2704;
return shadow * shadow; return shadow * shadow;

@ -14,6 +14,7 @@
#include "Shadow.azsli" #include "Shadow.azsli"
#include "ShadowmapAtlasLib.azsli" #include "ShadowmapAtlasLib.azsli"
#include "BicubicPcfFilters.azsli" #include "BicubicPcfFilters.azsli"
#include "ReceiverPlaneDepthBias.azsli"
// Before including this azsli file, a PassSrg must be defined with the following members: // Before including this azsli file, a PassSrg must be defined with the following members:
// Texture2DArray<float> m_directionalLightShadowmap; // Texture2DArray<float> m_directionalLightShadowmap;
@ -23,6 +24,7 @@
// This matchs ShadowFilterMethod in ShadowConstants.h // This matchs ShadowFilterMethod in ShadowConstants.h
enum class ShadowFilterMethod {None, Pcf, Esm, EsmPcf}; enum class ShadowFilterMethod {None, Pcf, Esm, EsmPcf};
option ShadowFilterMethod o_directional_shadow_filtering_method = ShadowFilterMethod::None; option ShadowFilterMethod o_directional_shadow_filtering_method = ShadowFilterMethod::None;
option bool o_directional_shadow_receiver_plane_bias_enable = true;
// DirectionalLightShadow calculates lit ratio for a directional light. // DirectionalLightShadow calculates lit ratio for a directional light.
class DirectionalLightShadow class DirectionalLightShadow
@ -107,6 +109,8 @@ class DirectionalLightShadow
float m_slopeBias[ViewSrg::MaxCascadeCount]; float m_slopeBias[ViewSrg::MaxCascadeCount];
float3 m_normalVector; float3 m_normalVector;
DebugInfo m_debugInfo; DebugInfo m_debugInfo;
float3 m_shadowPosDX[ViewSrg::MaxCascadeCount];
float3 m_shadowPosDY[ViewSrg::MaxCascadeCount];
}; };
// This outputs the coordinate in shadowmap Texture space of the given point. // This outputs the coordinate in shadowmap Texture space of the given point.
@ -438,6 +442,7 @@ float DirectionalLightShadow::SamplePcfBicubic(float3 shadowCoord, uint indexOfC
{ {
const uint filteringSampleCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_filteringSampleCount; const uint filteringSampleCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_filteringSampleCount;
const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize; const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize;
const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount; const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount;
Texture2DArray<float> shadowmap = PassSrg::m_directionalLightShadowmap; Texture2DArray<float> shadowmap = PassSrg::m_directionalLightShadowmap;
@ -448,7 +453,8 @@ float DirectionalLightShadow::SamplePcfBicubic(float3 shadowCoord, uint indexOfC
param.invShadowMapSize = rcp(size); param.invShadowMapSize = rcp(size);
param.comparisonValue = shadowCoord.z; param.comparisonValue = shadowCoord.z;
param.samplerState = SceneSrg::m_hwPcfSampler; param.samplerState = SceneSrg::m_hwPcfSampler;
param.receiverPlaneDepthBias = o_directional_shadow_receiver_plane_bias_enable ? ComputeReceiverPlaneDepthBias(m_shadowPosDX[indexOfCascade], m_shadowPosDY[indexOfCascade]) : 0;
if (filteringSampleCount <= 4) if (filteringSampleCount <= 4)
{ {
return SampleShadowMapBicubic_4Tap(param); return SampleShadowMapBicubic_4Tap(param);
@ -476,6 +482,16 @@ float DirectionalLightShadow::GetVisibility(
shadow.m_debugInfo.m_cascadeIndex = 0; shadow.m_debugInfo.m_cascadeIndex = 0;
shadow.m_debugInfo.m_usePcfFallback = false; shadow.m_debugInfo.m_usePcfFallback = false;
if (o_directional_shadow_receiver_plane_bias_enable)
{
[unroll]
for(int i = 0 ; i < ViewSrg::MaxCascadeCount ; ++i)
{
shadow.m_shadowPosDX[i] = ddx_fine(shadowCoords[i]);
shadow.m_shadowPosDY[i] = ddy_fine(shadowCoords[i]);
}
}
// Calculate slope bias // Calculate slope bias
const float3 lightDirection = const float3 lightDirection =
normalize(SceneSrg::m_directionalLights[lightIndex].m_direction); normalize(SceneSrg::m_directionalLights[lightIndex].m_direction);

@ -0,0 +1,38 @@
/*
* 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
// Use ddx/ddy to attempt to predict the slope of the receiver plane (the triangle we are shading) and adjust the pcf comparision depth accordingly
// See Section 20.5 Large Pcf Kernels in "Introduction to 3D Game Programming with DirectX 12" for a good explanation and diagrams of the problem
// Solution from:
// https://web.archive.org/web/20120220010450/http://developer.amd.com/media/gpu_assets/Isidoro-ShadowMapping.pdf
// Another implementation example: https://github.com/TheRealMJP/Shadows
float2 ComputeReceiverPlaneDepthBias(float3 projCoordsDDX, float3 projCoordsDDY)
{
const float invDet = 1.0f / ((projCoordsDDX.x * projCoordsDDY.y) - (projCoordsDDX.y * projCoordsDDY.x));
float2 receiverPlaneDepthBias;
receiverPlaneDepthBias.x = projCoordsDDY.y * projCoordsDDX.z - projCoordsDDX.y * projCoordsDDY.z;
receiverPlaneDepthBias.y = projCoordsDDX.x * projCoordsDDY.z - projCoordsDDY.x * projCoordsDDX.z;
receiverPlaneDepthBias *= invDet;
return receiverPlaneDepthBias;
}
// For each sample in the Pcf kernel, offset the z comparison depth value by the amount returned by this function.
// receiverPlaneDepthBias should be the input from ComputeReceiverPlaneDepthBias()
float ApplyReceiverPlaneDepthBias(const float2 receiverPlaneDepthBias, const float2 texCoordOffset)
{
float fragmentDepthBias = dot(receiverPlaneDepthBias, texCoordOffset);
// Because ddx/ddy can only give us a very rough approximation of the underlying surface, we might introduce acne by biasing away from the camera.
// Clamping this will remove this effect
fragmentDepthBias = min(fragmentDepthBias, 0.0);
return fragmentDepthBias;
}

@ -168,6 +168,10 @@ namespace AZ
//! Sets the shadowmap Pcf method. //! Sets the shadowmap Pcf method.
virtual void SetPcfMethod(LightHandle handle, PcfMethod method) = 0; virtual void SetPcfMethod(LightHandle handle, PcfMethod method) = 0;
//! Sets whether the directional shadowmap should use receiver plane bias.
//! This attempts to reduce shadow acne when using large pcf filters.
virtual void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) = 0;
}; };
} // namespace Render } // namespace Render
} // namespace AZ } // namespace AZ

@ -202,6 +202,7 @@ namespace AZ
{ {
uint32_t shadowFilterMethod = m_shadowData.at(nullptr).GetData(m_shadowingLightHandle.GetIndex()).m_shadowFilterMethod; 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_directionalShadowFilteringMethodName, AZ::RPI::ShaderOptionValue{shadowFilterMethod});
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; const uint32_t cascadeCount = m_shadowData.at(nullptr).GetData(m_shadowingLightHandle.GetIndex()).m_cascadeCount;
ShadowProperty& property = m_shadowProperties.GetData(m_shadowingLightHandle.GetIndex()); ShadowProperty& property = m_shadowProperties.GetData(m_shadowingLightHandle.GetIndex());
@ -616,6 +617,10 @@ namespace AZ
m_shadowBufferNeedsUpdate = true; m_shadowBufferNeedsUpdate = true;
} }
void DirectionalLightFeatureProcessor::SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable)
{
m_shadowProperties.GetData(handle.GetIndex()).m_isReceiverPlaneBiasEnabled = enable;
}
void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline)
{ {

@ -177,6 +177,10 @@ namespace AZ
// Shadow filter method of the light // Shadow filter method of the light
ShadowFilterMethod m_shadowFilterMethod = ShadowFilterMethod::None; ShadowFilterMethod m_shadowFilterMethod = ShadowFilterMethod::None;
// 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;
}; };
static void Reflect(ReflectContext* context); static void Reflect(ReflectContext* context);
@ -218,6 +222,7 @@ namespace AZ
void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetFilteringSampleCount(LightHandle handle, uint16_t count) override;
void SetShadowBoundaryWidth(LightHandle handle, float boundaryWidth) override; void SetShadowBoundaryWidth(LightHandle handle, float boundaryWidth) override;
void SetPcfMethod(LightHandle handle, PcfMethod method) override; void SetPcfMethod(LightHandle handle, PcfMethod method) override;
void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override;
const Data::Instance<RPI::Buffer> GetLightBuffer() const; const Data::Instance<RPI::Buffer> GetLightBuffer() const;
uint32_t GetLightCount() const; uint32_t GetLightCount() const;
@ -371,6 +376,7 @@ namespace AZ
Name m_lightTypeName = Name("directional"); Name m_lightTypeName = Name("directional");
Name m_directionalShadowFilteringMethodName = Name("o_directional_shadow_filtering_method"); Name m_directionalShadowFilteringMethodName = Name("o_directional_shadow_filtering_method");
Name m_directionalShadowReceiverPlaneBiasEnableName = Name("o_directional_shadow_receiver_plane_bias_enable");
static constexpr const char* FeatureProcessorName = "DirectionalLightFeatureProcessor"; static constexpr const char* FeatureProcessorName = "DirectionalLightFeatureProcessor";
}; };
} // namespace Render } // namespace Render

@ -185,6 +185,14 @@ namespace AZ
//! This sets the type of Pcf (percentage-closer filtering) to use. //! This sets the type of Pcf (percentage-closer filtering) to use.
//! @param method The Pcf method to use. //! @param method The Pcf method to use.
virtual void SetPcfMethod(PcfMethod method) = 0; virtual void SetPcfMethod(PcfMethod method) = 0;
//! Gets whether the directional shadowmap should use receiver plane bias.
//! This attempts to reduce shadow acne when using large pcf filters.
virtual bool GetShadowReceiverPlaneBiasEnabled() const = 0;
//! Sets whether the directional shadowmap should use receiver plane bias.
//! @param enable flag specifying whether to enable the receiver plane bias feature
virtual void SetShadowReceiverPlaneBiasEnabled(bool enable) = 0;
}; };
using DirectionalLightRequestBus = EBus<DirectionalLightRequests>; using DirectionalLightRequestBus = EBus<DirectionalLightRequests>;

@ -115,6 +115,10 @@ namespace AZ
PcfMethod m_pcfMethod = PcfMethod::Bicubic; PcfMethod m_pcfMethod = PcfMethod::Bicubic;
//! Whether not to enable the receiver plane bias.
//! This uses partial derivatives to reduce shadow acne when using large pcf kernels.
bool m_receiverPlaneBiasEnabled = true;
bool IsSplitManual() const; bool IsSplitManual() const;
bool IsSplitAutomatic() const; bool IsSplitAutomatic() const;
bool IsCascadeCorrectionDisabled() const; bool IsCascadeCorrectionDisabled() const;

@ -21,7 +21,7 @@ namespace AZ
if (auto* serializeContext = azrtti_cast<SerializeContext*>(context)) if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
{ {
serializeContext->Class<DirectionalLightComponentConfig, ComponentConfig>() serializeContext->Class<DirectionalLightComponentConfig, ComponentConfig>()
->Version(7) ->Version(8)
->Field("Color", &DirectionalLightComponentConfig::m_color) ->Field("Color", &DirectionalLightComponentConfig::m_color)
->Field("IntensityMode", &DirectionalLightComponentConfig::m_intensityMode) ->Field("IntensityMode", &DirectionalLightComponentConfig::m_intensityMode)
->Field("Intensity", &DirectionalLightComponentConfig::m_intensity) ->Field("Intensity", &DirectionalLightComponentConfig::m_intensity)
@ -41,7 +41,7 @@ namespace AZ
->Field("PcfPredictionSampleCount", &DirectionalLightComponentConfig::m_predictionSampleCount) ->Field("PcfPredictionSampleCount", &DirectionalLightComponentConfig::m_predictionSampleCount)
->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount) ->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount)
->Field("Pcf Method", &DirectionalLightComponentConfig::m_pcfMethod) ->Field("Pcf Method", &DirectionalLightComponentConfig::m_pcfMethod)
; ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled);
} }
} }

@ -88,6 +88,8 @@ namespace AZ
->Event("SetFilteringSampleCount", &DirectionalLightRequestBus::Events::SetFilteringSampleCount) ->Event("SetFilteringSampleCount", &DirectionalLightRequestBus::Events::SetFilteringSampleCount)
->Event("GetPcfMethod", &DirectionalLightRequestBus::Events::GetPcfMethod) ->Event("GetPcfMethod", &DirectionalLightRequestBus::Events::GetPcfMethod)
->Event("SetPcfMethod", &DirectionalLightRequestBus::Events::SetPcfMethod) ->Event("SetPcfMethod", &DirectionalLightRequestBus::Events::SetPcfMethod)
->Event("GetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::GetShadowReceiverPlaneBiasEnabled)
->Event("SetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::SetShadowReceiverPlaneBiasEnabled)
->VirtualProperty("Color", "GetColor", "SetColor") ->VirtualProperty("Color", "GetColor", "SetColor")
->VirtualProperty("Intensity", "GetIntensity", "SetIntensity") ->VirtualProperty("Intensity", "GetIntensity", "SetIntensity")
->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter") ->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter")
@ -104,7 +106,8 @@ namespace AZ
->VirtualProperty("SofteningBoundaryWidth", "GetSofteningBoundaryWidth", "SetSofteningBoundaryWidth") ->VirtualProperty("SofteningBoundaryWidth", "GetSofteningBoundaryWidth", "SetSofteningBoundaryWidth")
->VirtualProperty("PredictionSampleCount", "GetPredictionSampleCount", "SetPredictionSampleCount") ->VirtualProperty("PredictionSampleCount", "GetPredictionSampleCount", "SetPredictionSampleCount")
->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount")
->VirtualProperty("PcfMethod", "GetPcfMethod", "SetPcfMethod"); ->VirtualProperty("PcfMethod", "GetPcfMethod", "SetPcfMethod")
->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled");
; ;
} }
} }
@ -539,6 +542,7 @@ namespace AZ
SetPredictionSampleCount(m_configuration.m_predictionSampleCount); SetPredictionSampleCount(m_configuration.m_predictionSampleCount);
SetFilteringSampleCount(m_configuration.m_filteringSampleCount); SetFilteringSampleCount(m_configuration.m_filteringSampleCount);
SetPcfMethod(m_configuration.m_pcfMethod); SetPcfMethod(m_configuration.m_pcfMethod);
SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled);
// [GFX TODO][ATOM-1726] share config for multiple light (e.g., light ID). // [GFX TODO][ATOM-1726] share config for multiple light (e.g., light ID).
// [GFX TODO][ATOM-2416] adapt to multiple viewports. // [GFX TODO][ATOM-2416] adapt to multiple viewports.
@ -637,6 +641,17 @@ namespace AZ
m_configuration.m_pcfMethod = method; m_configuration.m_pcfMethod = method;
m_featureProcessor->SetPcfMethod(m_lightHandle, method); m_featureProcessor->SetPcfMethod(m_lightHandle, method);
} }
bool DirectionalLightComponentController::GetShadowReceiverPlaneBiasEnabled() const
{
return m_configuration.m_receiverPlaneBiasEnabled;
}
void DirectionalLightComponentController::SetShadowReceiverPlaneBiasEnabled(bool enable)
{
m_configuration.m_receiverPlaneBiasEnabled = enable;
m_featureProcessor->SetShadowReceiverPlaneBiasEnabled(m_lightHandle, enable);
}
} // namespace Render } // namespace Render
} // namespace AZ } // namespace AZ

@ -84,6 +84,8 @@ namespace AZ
void SetFilteringSampleCount(uint32_t count) override; void SetFilteringSampleCount(uint32_t count) override;
PcfMethod GetPcfMethod() const override; PcfMethod GetPcfMethod() const override;
void SetPcfMethod(PcfMethod method) override; void SetPcfMethod(PcfMethod method) override;
bool GetShadowReceiverPlaneBiasEnabled() const override;
void SetShadowReceiverPlaneBiasEnabled(bool enable) override;
private: private:
friend class EditorDirectionalLightComponent; friend class EditorDirectionalLightComponent;

@ -164,9 +164,13 @@ namespace AZ
->EnumAttribute(PcfMethod::Bicubic, "Bicubic") ->EnumAttribute(PcfMethod::Bicubic, "Bicubic")
->EnumAttribute(PcfMethod::BoundarySearch, "Boundary search") ->EnumAttribute(PcfMethod::BoundarySearch, "Boundary search")
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled); ->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled)
; ->DataElement(
Edit::UIHandlers::CheckBox, &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled,
"Shadow Receiver Plane Bias Enable",
"This reduces shadow acne when using large pcf kernels.")
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled);
} }
} }

Loading…
Cancel
Save