Replacing the old esm shadow blur with faster blur (#4095)

* Replacing the old gaussian blur with a much faster, better quality kawase blur

Signed-off-by: mrieggeramzn <mriegger@amazon.com>

* Removing atomtesting outdated msg

Signed-off-by: mrieggeramzn <mriegger@amazon.com>

* Some recommendations from Tommy

Signed-off-by: mrieggeramzn <mriegger@amazon.com>

* Adding early termination from previous gaussian filtering algorithm that kawase replaces

Signed-off-by: mrieggeramzn <mriegger@amazon.com>

* Removing pcf method

Signed-off-by: mrieggeramzn <mriegger@amazon.com>

* removing the old blur and adding in the new kawase blur into .cmake file

Signed-off-by: mrieggeramzn <mriegger@amazon.com>
This commit is contained in:
mrieggeramzn
2021-09-14 17:12:17 -07:00
committed by GitHub
parent 603e33d7f7
commit bd8c53550a
12 changed files with 201 additions and 259 deletions
@@ -40,9 +40,11 @@
}
]
},
{
"Name": "HorizontalGaussianFilter",
"TemplateName": "FilterDepthHorizontalTemplate",
"Name": "KawaseBlur0",
"TemplateName": "KawaseShadowBlurTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "Input",
@@ -54,26 +56,27 @@
]
},
{
"Name": "VerticalGaussianFiter",
"TemplateName": "FilterDepthVerticalTemplate",
"Name": "KawaseBlur1",
"TemplateName": "KawaseShadowBlurTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "Input",
"AttachmentRef": {
"Pass": "HorizontalGaussianFilter",
"Pass": "KawaseBlur0",
"Attachment": "Output"
}
}
]
}
}
],
"Connections": [
{
"LocalSlot": "EsmShadowmaps",
"AttachmentRef": {
"Pass": "VerticalGaussianFiter",
"Pass": "KawaseBlur1",
"Attachment": "Output"
}
}
}
]
}
@@ -1,72 +0,0 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "FilterDepthHorizontalTemplate",
"PassClass": "ComputePass",
"Slots": [
{
"Name": "Input",
"SlotType": "Input",
"ShaderInputName": "m_inputImage",
"ScopeAttachmentUsage": "Shader",
"LoadStoreAction": {
"LoadAction": "Load",
"StoreAction": "DontCare"
},
"ImageViewDesc": {
"IsArray": 1
}
},
{
"Name": "Output",
"SlotType": "Output",
"ShaderInputName": "m_outputImage",
"ScopeAttachmentUsage": "Shader",
"LoadStoreAction": {
"LoadAction": "DontCare",
"StoreAction": "Store"
},
"ImageViewDesc": {
"IsArray": 1
}
}
],
"PassData": {
"$type": "ComputePassData",
"ShaderAsset": {
"FilePath": "Shaders/Math/GaussianFilterFloatHorizontal.shader"
}
},
"ImageAttachments": [
{
"Name": "HorizontalFiltered",
"SizeSource": {
"Source": {
"Pass": "This",
"Attachment": "Input"
}
},
"ArraySizeSource": {
"Pass": "This",
"Attachment": "Input"
},
"ImageDescriptor": {
"Format": "R32_FLOAT"
}
}
],
"Connections": [
{
"localSlot": "Output",
"AttachmentRef": {
"Pass": "This",
"Attachment": "HorizontalFiltered"
}
}
]
}
}
}
@@ -4,7 +4,7 @@
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "FilterDepthVerticalTemplate",
"Name": "KawaseShadowBlurTemplate",
"PassClass": "ComputePass",
"Slots": [
{
@@ -28,7 +28,7 @@
"LoadStoreAction": {
"LoadAction": "DontCare",
"StoreAction": "Store"
},
},
"ImageViewDesc": {
"IsArray": 1
}
@@ -37,12 +37,12 @@
"PassData": {
"$type": "ComputePassData",
"ShaderAsset": {
"FilePath": "Shaders/Math/GaussianFilterFloatVertical.shader"
"FilePath": "Shaders/Shadow/KawaseShadowBlur.shader"
}
},
"ImageAttachments": [
{
"Name": "VerticalFiltered",
"Name": "FilteredImage",
"SizeSource": {
"Source": {
"Pass": "This",
@@ -63,7 +63,7 @@
"localSlot": "Output",
"AttachmentRef": {
"Pass": "This",
"Attachment": "VerticalFiltered"
"Attachment": "FilteredImage"
}
}
]
@@ -184,14 +184,6 @@
"Name": "DepthOfFieldWriteFocusDepthFromGpuTemplate",
"Path": "Passes/DepthOfFieldWriteFocusDepthFromGpu.pass"
},
{
"Name": "FilterDepthHorizontalTemplate",
"Path": "Passes/FilterDepthHorizontal.pass"
},
{
"Name": "FilterDepthVerticalTemplate",
"Path": "Passes/FilterDepthVertical.pass"
},
{
"Name": "EsmShadowmapsTemplate",
"Path": "Passes/EsmShadowmaps.pass"
@@ -503,7 +495,11 @@
{
"Name": "LowEndPipelineTemplate",
"Path": "Passes/LowEndPipeline.pass"
}
},
{
"Name": "KawaseShadowBlurTemplate",
"Path": "Passes/KawaseShadowBlur.pass"
}
]
}
}
@@ -1,60 +0,0 @@
/*
* 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
*
*/
// [GFX TODO][ATOM-3365] optimization using intermediary results in groupshared memory.
#include <Atom/Features/Math/Filter.azsli>
#include <Atom/Features/Math/FilterPassSrg.azsli>
#include <Atom/Features/Shadow/ShadowmapAtlasLib.azsli>
[numthreads(16,16,1)]
void MainCS(uint3 dispatchId: SV_DispatchThreadID)
{
const float3 inputSize = GetImageSize(FilterPassSrg::m_inputImage);
const float3 outputSize = GetImageSize(FilterPassSrg::m_outputImage);
const uint shadowmapIndex = GetShadowmapIndex(
FilterPassSrg::m_shadowmapIndexTable,
dispatchId,
inputSize.x);
// Early return if thread is outside of shadowmaps.
if (shadowmapIndex == ~0)
{
return;
}
const FilterParameter filterParameter = FilterPassSrg::m_filterParameters[shadowmapIndex];
const uint shadowmapSize = filterParameter.m_shadowmapSize;
// Early return if filter is disabled.
if (!filterParameter.m_isEnabled || shadowmapSize <= 1)
{
return; // early return if filter parameter is empty.
}
const uint sourceMin = filterParameter.m_shadowmapOriginInSlice.x;
const uint sourceMax = sourceMin + shadowmapSize - 1;
uint filterTableSize = 0;
FilterPassSrg::m_filterTable.GetDimensions(filterTableSize);
if (filterTableSize == 0 || filterParameter.m_parameterCount == 0)
{
return; // If filter parameter is empty, early return.
}
// [GFX TODO][ATOM-5676] pass proper source min/max for each shadowmap
const float result = FilteredFloat(
dispatchId,
FilterPassSrg::m_inputImage,
uint2(1, 0), // horizontal
sourceMin,
sourceMax,
FilterPassSrg::m_filterTable,
filterParameter.m_parameterOffset,
filterParameter.m_parameterCount);
FilterPassSrg::m_outputImage[dispatchId].r = result;
}
@@ -1,16 +0,0 @@
{
"Source" : "GaussianFilterFloatHorizontal",
"DrawList" : "shadow",
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainCS",
"type": "Compute"
}
]
}
}
@@ -1,60 +0,0 @@
/*
* 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
*
*/
// [GFX TODO][ATOM-3365] optimization using intermediary results in groupshared memory.
#include <Atom/Features/Math/Filter.azsli>
#include <Atom/Features/Math/FilterPassSrg.azsli>
#include <Atom/Features/Shadow/ShadowmapAtlasLib.azsli>
[numthreads(16,16,1)]
void MainCS(uint3 dispatchId: SV_DispatchThreadID)
{
const float3 inputSize = GetImageSize(FilterPassSrg::m_inputImage);
const float3 outputSize = GetImageSize(FilterPassSrg::m_outputImage);
const uint shadowmapIndex = GetShadowmapIndex(
FilterPassSrg::m_shadowmapIndexTable,
dispatchId,
inputSize.x);
// Early return if thread is outside of shadowmaps.
if (shadowmapIndex == ~0)
{
return;
}
const FilterParameter filterParameter = FilterPassSrg::m_filterParameters[shadowmapIndex];
const uint shadowmapSize = filterParameter.m_shadowmapSize;
// Early return if filter is disabled.
if (!filterParameter.m_isEnabled || shadowmapSize <= 1)
{
return; // early return if filter parameter is empty.
}
const uint sourceMin = filterParameter.m_shadowmapOriginInSlice.y;
const uint sourceMax = sourceMin + shadowmapSize - 1;
uint filterTableSize = 0;
FilterPassSrg::m_filterTable.GetDimensions(filterTableSize);
if (filterTableSize == 0 || filterParameter.m_parameterCount == 0)
{
return; // If filter parameter is empty, early return.
}
// [GFX TODO][ATOM-5676] pass proper source min/max for each shadowmap
const float result = FilteredFloat(
dispatchId,
FilterPassSrg::m_inputImage,
uint2(0, 1), // vertical
sourceMin,
sourceMax,
FilterPassSrg::m_filterTable,
filterParameter.m_parameterOffset,
filterParameter.m_parameterCount);
FilterPassSrg::m_outputImage[dispatchId].r = result;
}
@@ -0,0 +1,132 @@
/*
* 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
*
*/
// [GFX TODO][ATOM-3365] optimization using intermediary results in groupshared memory.
// This shader blurs the ESM results using a multi-pass kawase filter.
// It should generally be faster than separable gaussian blur
// https://software.intel.com/content/www/us/en/develop/blogs/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms.html
#include <Atom/Features/Math/Filter.azsli>
#include <Atom/Features/Shadow/ShadowmapAtlasLib.azsli>
#include <Atom/Features/Shadow/Shadow.azsli>
#include <Atom/Features/SrgSemantics.azsli>
ShaderResourceGroup FilterPassSrg : SRG_PerPass
{
// This shader filters multiple images with distinct filter parameters.
// So, the input and output are arrays of texture2Ds.
Texture2DArray<float> m_inputImage;
RWTexture2DArray<float> m_outputImage;
// This can convert a coordinate in an atlas to
// the shadowmap index.
Buffer<uint2> m_shadowmapIndexTable;
// This contains parameters related to filtering.
StructuredBuffer<FilterParameter> m_filterParameters;
// x and y contain the inverse of the texture map resolution, z contains the kawase iteration
// i.e. a two pass kawase blur passes in 0 for the 1st pass and 1 for the second pass
float4 m_rcpResolutionAndIteration;
Sampler LinearSampler
{
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};
}
void CalculateBlurBoundaries(const uint shadowmapIndex, out float2 sourceMinTex, out float2 sourceMaxTex)
{
const float2 rcpPixelSize = FilterPassSrg::m_rcpResolutionAndIteration.xy;
const FilterParameter filterParameter = FilterPassSrg::m_filterParameters[shadowmapIndex];
const uint shadowmapSize = filterParameter.m_shadowmapSize;
// location of the shadow bounds in texels
const uint2 sourceMinPixel = filterParameter.m_shadowmapOriginInSlice.xy;
const uint2 sourceMaxPixel = sourceMinPixel + shadowmapSize - 1;
// location of the shadow bounds in uv space
sourceMinTex = (sourceMinPixel + 0.5f) * rcpPixelSize;
sourceMaxTex = (sourceMaxPixel + 0.5f) * rcpPixelSize;
}
float AccumulateShadowSamples(Texture2DArray<float> tex, float3 texCoord, SamplerState s)
{
float4 values = tex.GatherRed(s, texCoord);
float result = values.x + values.y + values.z + values.w;
return result;
}
[numthreads(16,16,1)]
void MainCS(uint3 dispatchId: SV_DispatchThreadID)
{
const float inputSize = GetImageSize(FilterPassSrg::m_inputImage).x;
const uint shadowmapIndex = GetShadowmapIndex(
FilterPassSrg::m_shadowmapIndexTable,
dispatchId,
inputSize);
// Early return if thread is outside of shadowmaps.
if (shadowmapIndex == ~0)
{
return;
}
const FilterParameter filterParameter = FilterPassSrg::m_filterParameters[shadowmapIndex];
const uint shadowmapSize = filterParameter.m_shadowmapSize;
// Early return if filter is disabled.
if (!filterParameter.m_isEnabled || shadowmapSize <= 1)
{
return; // early return if filter parameter is empty.
}
const float2 rcpPixelSize = FilterPassSrg::m_rcpResolutionAndIteration.xy;
const float blurIteration = FilterPassSrg::m_rcpResolutionAndIteration.z;
float2 sourceMinTex, sourceMaxTex;
CalculateBlurBoundaries(shadowmapIndex, sourceMinTex, sourceMaxTex);
const float2 halfRcpPixelSize = rcpPixelSize / 2.0f;
const float2 dUV = rcpPixelSize.xy * blurIteration + halfRcpPixelSize.xy;
const float2 texCoord = (dispatchId.xy + 0.5f) * rcpPixelSize;
const float3 texCoordSamples[4] = {
float3(texCoord.x - dUV.x, texCoord.y - dUV.y, dispatchId.z),
float3(texCoord.x - dUV.x, texCoord.y + dUV.y, dispatchId.z),
float3(texCoord.x + dUV.x, texCoord.y - dUV.y, dispatchId.z),
float3(texCoord.x + dUV.x, texCoord.y + dUV.y, dispatchId.z),
};
float accumulatedBlur = 0;
float numSamplesAccumulated = 0;
for(int i = 0 ; i < 4; ++i)
{
if (texCoordSamples[i].x >= sourceMinTex.x &&
texCoordSamples[i].y >= sourceMinTex.y &&
texCoordSamples[i].x < sourceMaxTex.x &&
texCoordSamples[i].y < sourceMaxTex.y)
{
// we should be tapping the location directly in between 4 adjacent texels
accumulatedBlur += AccumulateShadowSamples(FilterPassSrg::m_inputImage, texCoordSamples[i], FilterPassSrg::LinearSampler);
numSamplesAccumulated += 4;
}
}
if (numSamplesAccumulated > 0)
{
float result = accumulatedBlur / numSamplesAccumulated;
FilterPassSrg::m_outputImage[dispatchId].r = result;
}
}
@@ -1,5 +1,5 @@
{
"Source" : "GaussianFilterFloatVertical",
"Source" : "KawaseShadowBlur",
"DrawList" : "shadow",
@@ -137,8 +137,6 @@ set(FILES
Passes/FastDepthAwareBlur.pass
Passes/FastDepthAwareBlurHor.pass
Passes/FastDepthAwareBlurVer.pass
Passes/FilterDepthHorizontal.pass
Passes/FilterDepthVertical.pass
Passes/Forward.pass
Passes/ForwardCheckerboard.pass
Passes/ForwardMSAA.pass
@@ -146,6 +144,7 @@ set(FILES
Passes/FullscreenCopy.pass
Passes/FullscreenOutputOnly.pass
Passes/ImGui.pass
Passes/KawaseShadowBlur.pass
Passes/LightAdaptationParent.pass
Passes/LightCulling.pass
Passes/LightCullingHeatmap.pass
@@ -331,10 +330,6 @@ set(FILES
Shaders/LightCulling/LightCullingTilePrepare.shader
Shaders/LuxCore/RenderTexture.azsl
Shaders/LuxCore/RenderTexture.shader
Shaders/Math/GaussianFilterFloatHorizontal.azsl
Shaders/Math/GaussianFilterFloatHorizontal.shader
Shaders/Math/GaussianFilterFloatVertical.azsl
Shaders/Math/GaussianFilterFloatVertical.shader
Shaders/MorphTargets/MorphTargetCS.azsl
Shaders/MorphTargets/MorphTargetCS.shader
Shaders/MorphTargets/MorphTargetSRG.azsli
@@ -457,6 +452,8 @@ set(FILES
Shaders/ScreenSpace/DeferredFog.shader
Shaders/Shadow/DepthExponentiation.azsl
Shaders/Shadow/DepthExponentiation.shader
Shaders/Shadow/KawaseShadowBlur.azsl
Shaders/Shadow/KawaseShadowBlur.shader
Shaders/Shadow/Shadowmap.azsl
Shaders/Shadow/Shadowmap.shader
Shaders/SkinnedMesh/LinearSkinningCS.azsl
@@ -130,32 +130,17 @@ namespace AZ
const AZStd::array_view<RPI::Ptr<RPI::Pass>>& children = GetChildren();
AZ_Assert(children.size() == EsmChildPassKindCount, "[EsmShadowmapsPass '%s'] The count of children is wrong.", GetPathName().GetCStr());
for (uint32_t index = 0; index < EsmChildPassKindCount; ++index)
for (uint32_t childPassIndex = 0; childPassIndex < EsmChildPassKindCount; ++childPassIndex)
{
RPI::ComputePass* child = azrtti_cast<RPI::ComputePass*>(children[index].get());
RPI::ComputePass* child = azrtti_cast<RPI::ComputePass*>(children[childPassIndex].get());
AZ_Assert(child, "[EsmShadowmapsPass '%s'] A child does not compute.", GetPathName().GetCStr());
Data::Instance<RPI::ShaderResourceGroup> srg = child->GetShaderResourceGroup();
if (m_shadowmapIndexTableBufferIndices[index].IsNull())
SetBlurParameters(srg, childPassIndex);
if (childPassIndex >= aznumeric_cast<uint32_t>(EsmChildPassKind::KawaseBlur0))
{
m_shadowmapIndexTableBufferIndices[index] = srg->FindShaderInputBufferIndex(Name("m_shadowmapIndexTable"));
}
srg->SetBuffer(m_shadowmapIndexTableBufferIndices[index], m_shadowmapIndexTableBuffer);
if (m_filterParameterBufferIndices[index].IsNull())
{
m_filterParameterBufferIndices[index] = srg->FindShaderInputBufferIndex(Name("m_filterParameters"));
}
srg->SetBuffer(m_filterParameterBufferIndices[index], m_filterParameterBuffer);
if (index != static_cast<uint32_t>(EsmChildPassKind::Exponentiation))
{
if (m_filterTableBufferIndices[index].IsNull())
{
m_filterTableBufferIndices[index] = srg->FindShaderInputBufferIndex(Name("m_filterTable"));
}
srg->SetBuffer(m_filterTableBufferIndices[index], m_filterTableBuffer);
SetKawaseBlurSpecificParameters(srg, childPassIndex - aznumeric_cast<uint32_t>(EsmChildPassKind::KawaseBlur0));
}
child->SetTargetThreadCounts(
@@ -165,5 +150,32 @@ namespace AZ
}
}
void EsmShadowmapsPass::SetBlurParameters(Data::Instance<RPI::ShaderResourceGroup> srg, const uint32_t childPassIndex)
{
if (m_shadowmapIndexTableBufferIndices[childPassIndex].IsNull())
{
m_shadowmapIndexTableBufferIndices[childPassIndex] = srg->FindShaderInputBufferIndex(Name("m_shadowmapIndexTable"));
}
srg->SetBuffer(m_shadowmapIndexTableBufferIndices[childPassIndex], m_shadowmapIndexTableBuffer);
if (m_filterParameterBufferIndices[childPassIndex].IsNull())
{
m_filterParameterBufferIndices[childPassIndex] = srg->FindShaderInputBufferIndex(Name("m_filterParameters"));
}
srg->SetBuffer(m_filterParameterBufferIndices[childPassIndex], m_filterParameterBuffer);
}
void EsmShadowmapsPass::SetKawaseBlurSpecificParameters(Data::Instance<RPI::ShaderResourceGroup> srg, uint32_t kawaseBlurIndex)
{
if (m_kawaseBlurConstantIndices[kawaseBlurIndex].IsNull())
{
m_kawaseBlurConstantIndices[kawaseBlurIndex] = srg->FindShaderInputConstantIndex(Name("m_rcpResolutionAndIteration"));
}
const AZ::Vector4 data(
1.0f / m_shadowmapImageSize.m_width, 1.0f / m_shadowmapImageSize.m_height, aznumeric_cast<float>(kawaseBlurIndex), 0.0f);
srg->SetConstant(m_kawaseBlurConstantIndices[kawaseBlurIndex], data);
}
} // namespace Render
} // namespace AZ
@@ -21,12 +21,17 @@
namespace AZ
{
namespace RPI
{
class ShaderResourceGroup;
}
namespace Render
{
AZ_ENUM_CLASS_WITH_UNDERLYING_TYPE(EsmChildPassKind, uint32_t,
(Exponentiation, 0),
HorizontalFilter,
VerticalFilter);
KawaseBlur0,
KawaseBlur1);
//! This pass outputs filtered shadowmap images used in ESM.
//! ESM is an abbreviation of Exponential Shadow Maps.
@@ -88,6 +93,9 @@ namespace AZ
void FrameBeginInternal(FramePrepareParams params) override;
void UpdateChildren();
// Parameters for both the depth exponentiation pass along with the kawase blur passes
void SetBlurParameters(Data::Instance<RPI::ShaderResourceGroup> srg, const uint32_t childPassIndex);
void SetKawaseBlurSpecificParameters(Data::Instance<RPI::ShaderResourceGroup> srg, const uint32_t kawaseBlurIndex);
bool m_computationEnabled = false;
Name m_lightTypeName;
@@ -102,6 +110,8 @@ namespace AZ
Data::Instance<RPI::Buffer> m_shadowmapIndexTableBuffer;
AZStd::array<RHI::ShaderInputBufferIndex, EsmChildPassKindCount> m_filterParameterBufferIndices;
Data::Instance<RPI::Buffer> m_filterParameterBuffer;
AZStd::array<RHI::ShaderInputConstantIndex, 2> m_kawaseBlurConstantIndices;
};
} // namespace Render
} // namespace AZ