From f4f7f0772137afa255af9a255b34327a553c30fe Mon Sep 17 00:00:00 2001 From: mrieggeramzn <61609885+mrieggeramzn@users.noreply.github.com> Date: Thu, 2 Dec 2021 09:26:34 -0800 Subject: [PATCH] shadow fixes (#5890) * shadow fixes Signed-off-by: Michael Riegger * Adding missing line Signed-off-by: Michael Riegger * Adding point sampler Signed-off-by: Michael Riegger * feedback from pr Signed-off-by: mrieggeramzn * better variable name Signed-off-by: mrieggeramzn * Fix compile error Signed-off-by: mrieggeramzn --- .../ShaderLib/Atom/Features/Math/Filter.azsli | 94 ------------------- .../Atom/Features/PBR/Lights/PointLight.azsli | 18 +++- .../ShaderLib/Atom/Features/Shadow/ESM.azsli | 24 +++++ .../Features/Shadow/ProjectedShadow.azsli | 24 ++--- 4 files changed, 49 insertions(+), 111 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ESM.azsli diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Math/Filter.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Math/Filter.azsli index 8271aa46fb..11dac7b81c 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Math/Filter.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Math/Filter.azsli @@ -41,97 +41,3 @@ bool IsInsideOfImageSize( return IsInsideOfImageSize(coord, inputImageSize) && IsInsideOfImageSize(coord, outputImageSize); } - -//! This returns filtered value of "source" with weights in "filterTable" in 1 direction. -//! @param coord the center coordinate (in Texture2DArray) of the filtered area. -//! the xy coordinate is in pixel, and z is array slice index. -//! @param source image resource which is used as the source of the filtering. -//! Note that it contains entire of the shadowmap atlas, not a single shadowmap. -//! @param direction either (1,0) or (0,1). -//! If (1,0), the filtering direction is horizontal, -//! and if (0,1), it is vertical. -//! @param sourceMin the minimum (left/top most) index of the shadowmap. -//! @param sourceMax the maximum (right/bottom most) index of the shadowmap. -//! @param filterTable the weight table for this table. -//! Since the weight table of a Gaussian filter is left-right symmetry, -//! the right half is omitted in this filterTable. -//! @param filterOffset the offset of the filtering parameter in filterTable. -//! @param filterCount the element count of filtering parameter in filterTable. -//! For example, the weight table has size 11 in the original meaning -//! of Gaussian filter, filterCount == 6 by omitting the right half. -float FilteredFloat( - uint3 coord, - Texture2DArray source, - int2 direction, - int sourceMin, - int sourceMax, - Buffer filterTable, - uint filterOffset, - uint filterCount) -{ - if (filterCount == 0) - { - return 0.; // if no filtering info, early return. - } - - const int centerIndex = (int)dot(coord.xy, direction); - float result = 0.; - int index = 0; - - // This function summarizes the values stored in "source" - // from minIndex to maxIndex with weight in "filterTable". - // In the case that some point in [minIndex, maxIndex] go outside of - // the shadowmap (indicated by sourceMin and sourceMax), - // the edge value of the shadowmap is used. - - // 1. littler index side (left/up side) - const int minIndex = centerIndex - ((int)filterCount - 1); - - // 1-1. outside of shadowmap (littler) - // Assuming outside values are equal to the edge value, - // it first summarize the weights for outside of shadowmap - // then multiply it by the edge value. - float weight = 0.; // summation of weights of outside of shadowmap - for (index = minIndex; index < sourceMin; ++index) - { - weight += filterTable[filterOffset + index - minIndex]; - } - int2 edgeOffset = direction * (sourceMin - centerIndex); - int3 edgeCoord = coord + int3(edgeOffset, 0); - result += weight * source[edgeCoord]; - - // 1-2. inside of shadowmap (littler) - for (index = max(sourceMin, minIndex); index < centerIndex; ++index) - { - const int2 offset = direction * (index - centerIndex); - result += filterTable[filterOffset + index - minIndex] * - source[coord + int3(offset, 0)]; - } - - // 2. greater index side (right/down side) - const int maxIndex = centerIndex + ((int)filterCount - 1); - - // 2-1. outside of shadowmap (greater) - // This is similar to 1-1 above. - weight = 0.; // summation of weights of outside of shadowmap - for (index = maxIndex; index > sourceMax; --index) - { - weight += filterTable[filterOffset + maxIndex - index]; - } - edgeOffset = direction * (sourceMax - centerIndex); - edgeCoord = coord + int3(edgeOffset, 0); - result += weight * source[edgeCoord]; - - // 2-2. inside of shadowmap (greater) - for (index = min(sourceMax, maxIndex); index > centerIndex; --index) - { - const int2 offset = direction * (index - centerIndex); - result += filterTable[filterOffset + maxIndex - index] * - source[coord + int3(offset, 0)]; - } - - // 3. center - result += filterTable[filterOffset + filterCount - 1] * source[coord]; - - return result; -} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli index e6eea9728f..92f4065931 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli @@ -50,6 +50,20 @@ int UnpackPointLightShadowIndex(const ViewSrg::PointLight light, const int face) return (light.m_shadowIndices[index] >> shiftAmount) & 0xFFFF; } +uint ComputeShadowIndex(const ViewSrg::PointLight light, const Surface surface) +{ + // shadow map size and bias are the same across all shadowmaps used by a specific point light, so just grab the first one + const uint lightIndex0 = UnpackPointLightShadowIndex(light, 0); + const float shadowmapSize = ViewSrg::m_projectedFilterParams[lightIndex0].m_shadowmapSize; + + // Note that the normal bias offset could potentially move the shadowed position from one map to another map inside the same point light shadow. + const float normalBias = ViewSrg::m_projectedShadows[lightIndex0].m_normalShadowBias; + const float3 biasedPosition = surface.position + ComputeNormalShadowOffset(normalBias, surface.vertexNormal, shadowmapSize); + + const int shadowCubemapFace = GetPointLightShadowCubemapFace(biasedPosition, light.m_position); + return UnpackPointLightShadowIndex(light, shadowCubemapFace); +} + void ApplyPointLight(ViewSrg::PointLight light, Surface surface, inout LightingData lightingData) { float3 posToLight = light.m_position - surface.position; @@ -74,10 +88,8 @@ void ApplyPointLight(ViewSrg::PointLight light, Surface surface, inout LightingD float backShadowRatio = 0.0; if (o_enableShadows) { - const int shadowCubemapFace = GetPointLightShadowCubemapFace(surface.position, light.m_position); - const int shadowIndex = UnpackPointLightShadowIndex(light, shadowCubemapFace); const float3 lightDir = normalize(light.m_position - surface.position); - + const uint shadowIndex = ComputeShadowIndex(light, surface); litRatio *= ProjectedShadow::GetVisibility( shadowIndex, light.m_position, diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ESM.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ESM.azsli new file mode 100644 index 0000000000..3a258c2f47 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ESM.azsli @@ -0,0 +1,24 @@ +/* + * 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 + + +float SampleESM(const Texture2DArray shadowMap, const SamplerState samp, const float3 uv, const float zReceiver, const float esmExponent) +{ + const float mipmaplevel = 0; + const float occluder = shadowMap.SampleLevel(samp,uv, mipmaplevel).r; + const float lit = exp((occluder - zReceiver) * esmExponent); + return lit; +} + +float PCFFallbackForESM(const Texture2DArray shadowMap, const float3 uv, const float zReceiver, const float esmExponent) +{ + const float result = SampleESM(shadowMap, PassSrg::LinearSampler, uv, zReceiver, esmExponent); + return saturate(result); +} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli index 3b8379e7fa..98ec9ea8bc 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/ProjectedShadow.azsli @@ -15,6 +15,7 @@ #include "BicubicPcfFilters.azsli" #include "Shadow.azsli" #include "NormalOffsetShadows.azsli" +#include "ESM.azsli" // ProjectedShadow calculates shadowed area projected from a light. class ProjectedShadow @@ -190,13 +191,11 @@ float ProjectedShadow::GetVisibilityEsm() const float depth = PerspectiveDepthToLinear( m_shadowPosition.z - m_bias, coefficients); - const float occluder = shadowmap.SampleLevel( - PassSrg::LinearSampler, - float3(atlasPosition.xy * invAtlasSize, atlasPosition.z), - /*LOD=*/0).r; + + const float3 uv = float3(atlasPosition.xy * invAtlasSize, atlasPosition.z); + const float esmExponent = ViewSrg::m_projectedShadows[m_shadowIndex].m_esmExponent; + const float ratio = SampleESM(shadowmap, PassSrg::LinearSampler, uv, depth, esmExponent); - const float exponent = -ViewSrg::m_projectedShadows[m_shadowIndex].m_esmExponent * (depth - occluder); - const float ratio = exp(exponent); // pow() mitigates light bleeding to shadows from near shadow casters. return saturate( pow(ratio, 8) ); } @@ -229,21 +228,18 @@ float ProjectedShadow::GetVisibilityEsmPcf() return 1.; } const float3 atlasPosition = GetAtlasPosition(m_shadowPosition.xy); + const float3 uv = float3(atlasPosition.xy * invAtlasSize, atlasPosition.z); const float depth = PerspectiveDepthToLinear( m_shadowPosition.z - m_bias, coefficients); - const float occluder = shadowmap.SampleLevel( - PassSrg::LinearSampler, - float3(atlasPosition.xy * invAtlasSize, atlasPosition.z), - /*LOD=*/0).r; - - const float exponent = -ViewSrg::m_projectedShadows[m_shadowIndex].m_esmExponent * (depth - occluder); - float ratio = exp(exponent); + + const float esmExponent = ViewSrg::m_projectedShadows[m_shadowIndex].m_esmExponent; + float ratio = SampleESM(shadowmap, PassSrg::LinearSampler, uv, depth, esmExponent); static const float pcfFallbackThreshold = 1.04; if (ratio > pcfFallbackThreshold) { - ratio = GetVisibilityPcf(); + ratio = PCFFallbackForESM(shadowmap, uv, depth, esmExponent); } else {