Added a frame delay before activating SSR, several visual quality improvements

Signed-off-by: dmcdiar <dmcdiar@amazon.com>
monroegm-disable-blank-issue-2
dmcdiar 4 years ago
parent 7213e2cd87
commit 7f4ab7a1c2

@ -34,10 +34,6 @@
}
],
"PassRequests": [
{
"Name": "ReflectionScreenSpaceBlurPass",
"TemplateName": "ReflectionScreenSpaceBlurPassTemplate"
},
{
"Name": "ReflectionScreenSpaceTracePass",
"TemplateName": "ReflectionScreenSpaceTracePassTemplate",
@ -57,18 +53,44 @@
}
},
{
"LocalSlot": "DepthStencilInput",
"LocalSlot": "SpecularF0Input",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "DepthStencilInput"
"Attachment": "SpecularF0Input"
}
},
{
"LocalSlot": "SpecularF0Input",
"LocalSlot": "ReflectionInputOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SpecularF0Input"
"Attachment": "ReflectionInputOutput"
}
}
]
},
{
"Name": "ReflectionScreenSpaceBlurPass",
"TemplateName": "ReflectionScreenSpaceBlurPassTemplate",
"Connections": [
{
"LocalSlot": "DepthInput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "DepthStencilInput"
}
},
{
"LocalSlot": "ScreenSpaceReflectionInputOutput",
"AttachmentRef": {
"Pass": "ReflectionScreenSpaceTracePass",
"Attachment": "ScreenSpaceReflectionOutput"
}
},
{
"LocalSlot": "DownsampledDepthInputOutput",
"AttachmentRef": {
"Pass": "ReflectionScreenSpaceTracePass",
"Attachment": "DownsampledDepthOutput"
}
}
]
@ -76,22 +98,19 @@
{
"Name": "ReflectionScreenSpaceCompositePass",
"TemplateName": "ReflectionScreenSpaceCompositePassTemplate",
"ExecuteAfter": [
"ReflectionScreenSpaceBlurPass"
],
"Connections": [
{
"LocalSlot": "TraceInput",
"LocalSlot": "ReflectionInput",
"AttachmentRef": {
"Pass": "ReflectionScreenSpaceTracePass",
"Attachment": "Output"
"Pass": "ReflectionScreenSpaceBlurPass",
"Attachment": "ScreenSpaceReflectionInputOutput"
}
},
{
"LocalSlot": "PreviousFrameBufferInput",
"LocalSlot": "DownsampledDepthInput",
"AttachmentRef": {
"Pass": "ReflectionScreenSpaceBlurPass",
"Attachment": "PreviousFrameInputOutput"
"Attachment": "DownsampledDepthInputOutput"
}
},
{
@ -115,6 +134,13 @@
"Attachment": "DepthStencilInput"
}
},
{
"LocalSlot": "PreviousFrameInputOutput",
"AttachmentRef": {
"Pass": "ReflectionScreenSpaceTracePass",
"Attachment": "PreviousFrameInputOutput"
}
},
{
"LocalSlot": "DepthStencilInput",
"AttachmentRef": {

@ -8,34 +8,19 @@
"PassClass": "ReflectionScreenSpaceBlurPass",
"Slots": [
{
"Name": "PreviousFrameInputOutput",
"SlotType": "InputOutput",
"Name": "DepthInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
}
],
"ImageAttachments": [
},
{
"Name": "PreviousFrameImage",
"SizeSource": {
"Source": {
"Pass": "Parent",
"Attachment": "SpecularInput"
}
},
"ImageDescriptor": {
"Format": "R16G16B16A16_FLOAT",
"SharedQueueMask": "Graphics"
},
"GenerateFullMipChain": true
}
],
"Connections": [
"Name": "ScreenSpaceReflectionInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "Shader"
},
{
"LocalSlot": "PreviousFrameInputOutput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "PreviousFrameImage"
}
"Name": "DownsampledDepthInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "DepthStencil"
}
]
}

@ -7,6 +7,11 @@
"Name": "ReflectionScreenSpaceBlurVerticalPassTemplate",
"PassClass": "ReflectionScreenSpaceBlurChildPass",
"Slots": [
{
"Name": "DepthInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "Input",
"SlotType": "InputOutput",
@ -16,6 +21,20 @@
"Name": "Output",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{
"Name": "DownsampledDepthOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "DepthStencil"
}
],
"Connections": [
{
"LocalSlot": "DepthInput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "DepthInput"
}
}
],
"PassData": {

@ -8,12 +8,12 @@
"PassClass": "ReflectionScreenSpaceCompositePass",
"Slots": [
{
"Name": "TraceInput",
"Name": "ReflectionInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "PreviousFrameBufferInput",
"Name": "DownsampledDepthInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
@ -37,6 +37,11 @@
]
}
},
{
"Name": "PreviousFrameInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "DepthStencilInput",
"SlotType": "Input",

@ -5,7 +5,7 @@
"ClassData": {
"PassTemplate": {
"Name": "ReflectionScreenSpaceTracePassTemplate",
"PassClass": "FullScreenTriangle",
"PassClass": "ReflectionScreenSpaceTracePass",
"Slots": [
{
"Name": "DepthStencilTextureInput",
@ -28,24 +28,52 @@
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "DepthStencilInput",
"Name": "ReflectionInputOutput",
"SlotType": "Input",
"ScopeAttachmentUsage": "DepthStencil",
"ImageViewDesc": {
"AspectFlags": [
"Stencil"
]
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "PreviousFrameInputOutput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "ScreenSpaceReflectionOutput",
"SlotType": "Output",
"ScopeAttachmentUsage": "RenderTarget",
"LoadStoreAction": {
"ClearValue": {
"Value": [
0.0,
0.0,
0.0,
0.0
]
},
"LoadAction": "Clear"
}
},
{
"Name": "Output",
"Name": "DownsampledDepthOutput",
"SlotType": "Output",
"ScopeAttachmentUsage": "RenderTarget"
"ScopeAttachmentUsage": "DepthStencil",
"LoadStoreAction": {
"ClearValue": {
"Type": "DepthStencil",
"Value": [
1.0,
{},
{},
{}
]
},
"LoadAction": "Clear"
}
}
],
"ImageAttachments": [
{
"Name": "TraceImage",
"Name": "ScreenSpaceReflectionImage",
"SizeSource": {
"Source": {
"Pass": "This",
@ -56,9 +84,40 @@
"HeightMultiplier": 0.5
}
},
"MultisampleSource": {
"Pass": "This",
"Attachment": "SpecularF0Input"
"ImageDescriptor": {
"Format": "R16G16B16A16_FLOAT",
"MipLevels": "5",
"SharedQueueMask": "Graphics"
}
},
{
"Name": "DownsampledDepthImage",
"SizeSource": {
"Source": {
"Pass": "Parent",
"Attachment": "DepthStencilInput"
},
"Multipliers": {
"WidthMultiplier": 0.5,
"HeightMultiplier": 0.5
}
},
"FormatSource": {
"Pass": "Parent",
"Attachment": "DepthStencilInput"
},
"ImageDescriptor": {
"MipLevels": "5",
"SharedQueueMask": "Graphics"
}
},
{
"Name": "PreviousFrameImage",
"SizeSource": {
"Source": {
"Pass": "Parent",
"Attachment": "SpecularInput"
}
},
"ImageDescriptor": {
"Format": "R16G16B16A16_FLOAT",
@ -68,15 +127,28 @@
],
"Connections": [
{
"LocalSlot": "Output",
"LocalSlot": "ScreenSpaceReflectionOutput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "ScreenSpaceReflectionImage"
}
},
{
"LocalSlot": "DownsampledDepthOutput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "DownsampledDepthImage"
}
},
{
"LocalSlot": "PreviousFrameInputOutput",
"AttachmentRef": {
"Pass": "This",
"Attachment": "TraceImage"
"Attachment": "PreviousFrameImage"
}
}
],
"PassData":
{
"PassData": {
"$type": "FullscreenTrianglePassData",
"ShaderAsset": {
"FilePath": "Shaders/Reflections/ReflectionScreenSpaceTrace.shader"

@ -6,11 +6,31 @@
*
*/
// 7-tap Gaussian Kernel (Sigma 1.1)
static const uint GaussianKernelSize = 7;
static const int2 TexelOffsetsV[GaussianKernelSize] = {{0, -3}, {0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}};
static const int2 TexelOffsetsH[GaussianKernelSize] = {{-3, 0}, {-2, 0}, {-1, 0}, {0, 0}, {1, 0}, {2, 0}, {3, 0}};
static const float TexelWeights[GaussianKernelSize] = {0.010805f, 0.074929f, 0.238727f, 0.351078f, 0.238727f, 0.074929f, 0.010805f};
// Gaussian Kernel Radius 9, Sigma 1.8
static const uint GaussianKernelSize = 19;
static const int2 TexelOffsetsV[GaussianKernelSize] = {{0, -9}, {0, -8}, {0, -7}, {0, -6}, {0, -5}, {0, -4}, {0, -3}, {0, -2}, {0, -1}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}, {0, 8}, {0, 9}};
static const int2 TexelOffsetsH[GaussianKernelSize] = {{-9, 0}, {-8, 0}, {-7, 0}, {-6, 0}, {-5, 0}, {-4, 0}, {-3, 0}, {-2, 0}, {-1, 0}, {0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}};
static const float TexelWeights[GaussianKernelSize] = {
0.0000011022801820635918f,
0.000014295732881160677f,
0.0001370168487067367f,
0.0009708086495991633f,
0.005086391900047703f,
0.019711193240183777f,
0.056512463228943335f,
0.11989501853796679f,
0.18826323520204147f,
0.21881694875889543f,
0.18826323520204147f,
0.11989501853796679f,
0.056512463228943335f,
0.019711193240183777f,
0.005086391900047703f,
0.0009708086495991633f,
0.0001370168487067367f,
0.000014295732881160677f,
0.0000011022801820635918f
};
float3 GaussianFilter(uint2 screenCoords, int2 texelOffsets[GaussianKernelSize], RWTexture2D<float4> inputImage)
{

@ -10,12 +10,13 @@
#include <viewsrg.srgi>
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
#include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
#include <Atom/Features/SrgSemantics.azsli>
#include <Atom/RPI/Math.azsli>
#include "ReflectionScreenSpaceBlurCommon.azsli"
ShaderResourceGroup PassSrg : SRG_PerPass
{
Texture2DMS<float> m_depth;
RWTexture2D<float4> m_input;
RWTexture2D<float4> m_output;
uint m_imageWidth;
@ -26,13 +27,39 @@ ShaderResourceGroup PassSrg : SRG_PerPass
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
// Pixel Shader
struct PSOutput
{
float4 m_color : SV_Target0;
float m_depth : SV_Depth;
};
PSOutput MainPS(VSOutput IN)
{
// vertical blur uses coordinates from the mip0 input image
uint2 coords = IN.m_position.xy * PassSrg::m_outputScale;
float3 result = GaussianFilter(coords, TexelOffsetsV, PassSrg::m_input);
uint2 halfResCoords = IN.m_position.xy * PassSrg::m_outputScale;
float3 result = GaussianFilter(halfResCoords, TexelOffsetsV, PassSrg::m_input);
// downsample depth, using fullscreen image coordinates
float downsampledDepth = 0;
if (PassSrg::m_input[halfResCoords].w > 0.0f)
{
uint2 fullScreenCoords = halfResCoords * 2;
for (int y = -2; y < 2; ++y)
{
for (int x = -2; x < 2; ++x)
{
float depth = PassSrg::m_depth.Load(fullScreenCoords + int2(x, y), 0).r;
if (depth > downsampledDepth)
{
downsampledDepth = depth;
}
}
}
}
PSOutput OUT;
OUT.m_color = float4(result, 1.0f);
OUT.m_depth = downsampledDepth;
return OUT;
}

@ -10,7 +10,8 @@
{
"Depth" :
{
"Enable" : false
"Enable" : true, // required to bind the depth buffer SRV
"CompareFunc" : "Always"
}
},

@ -12,17 +12,19 @@
#include <Atom/RPI/Math.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
#include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#include <Atom/Features/PBR/LightingUtils.azsli>
#include <Atom/Features/PBR/Microfacet/Fresnel.azsli>
ShaderResourceGroup PassSrg : SRG_PerPass
{
Texture2DMS<float4> m_trace;
Texture2D<float4> m_previousFrame;
Texture2D<float4> m_reflection;
Texture2D<float> m_downsampledDepth;
Texture2DMS<float4> m_normal; // RGB10 = Normal (Encoded), A2 = Flags
Texture2DMS<float4> m_specularF0; // RGB8 = SpecularF0, A8 = Roughness
Texture2DMS<float> m_depth;
Texture2D<float4> m_previousFrame;
Sampler LinearSampler
{
@ -40,6 +42,49 @@ ShaderResourceGroup PassSrg : SRG_PerPass
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
float3 SampleReflection(float2 reflectionUV, float mip, float depth, float3 normal, uint2 invDimensions)
{
const float DepthTolerance = 0.001f;
// attempt to trivially accept the downsampled reflection texel
float downsampledDepth = PassSrg::m_downsampledDepth.SampleLevel(PassSrg::LinearSampler, reflectionUV, floor(mip)).r;
if (abs(depth - downsampledDepth) <= DepthTolerance)
{
// use this reflection sample
float3 reflection = PassSrg::m_reflection.SampleLevel(PassSrg::LinearSampler, reflectionUV, mip).rgb;
return reflection;
}
// neighborhood search surrounding the downsampled texel, searching for the closest matching depth
float closestDepthDelta = 1.0f;
int2 closestOffsetUV = float2(0.0f, 0.0f);
for (int y = -4; y <= 4; ++y)
{
for (int x = -4; x <= 4; ++x)
{
float2 offsetUV = float2(x * invDimensions.x, y * invDimensions.y);
float downsampledDepth = PassSrg::m_downsampledDepth.SampleLevel(PassSrg::LinearSampler, reflectionUV + offsetUV, floor(mip)).r;
float depthDelta = abs(depth - downsampledDepth);
if (depthDelta <= DepthTolerance)
{
// depth is within tolerance, use this texel
float3 reflection = PassSrg::m_reflection.SampleLevel(PassSrg::LinearSampler, reflectionUV + offsetUV, mip).rgb;
return reflection;
}
if (closestDepthDelta > depthDelta)
{
closestDepthDelta = depthDelta;
closestOffsetUV = offsetUV;
}
}
}
float3 reflection = PassSrg::m_reflection.SampleLevel(PassSrg::LinearSampler, reflectionUV + closestOffsetUV, mip).rgb;
return reflection;
}
// Pixel Shader
PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
{
@ -52,11 +97,21 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
// compute trace image coordinates for the half-res image
float2 traceCoords = screenCoords * 0.5f;
// load trace data and check w-component to see if there was a hit
float4 traceData = PassSrg::m_trace.Load(traceCoords, sampleIndex);
if (traceData.w <= 0.0f)
// check reflection data mip0 to see if there was a hit
float4 reflectionData = PassSrg::m_reflection.Load(uint3(traceCoords, 0));
if (reflectionData.w <= 0.0f)
{
// fallback to the cubemap reflections currently in the reflection buffer
discard;
}
// load specular and roughness
float4 specularF0 = PassSrg::m_specularF0.Load(screenCoords, sampleIndex);
float roughness = specularF0.a;
const float MaxRoughness = 0.5f;
if (roughness > MaxRoughness)
{
// no hit, fallback to the cubemap reflections currently in the reflection buffer
// fallback to the cubemap reflections currently in the reflection buffer
discard;
}
@ -65,8 +120,9 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
float depth = PassSrg::m_depth.Load(screenCoords, sampleIndex).r;
float2 ndcPos = float2(UV.x, 1.0f - UV.y) * 2.0f - 1.0f;
float4 projectedPos = float4(ndcPos, depth, 1.0f);
float4 positionWS = mul(ViewSrg::m_viewProjectionInverseMatrix, projectedPos);
positionWS /= positionWS.w;
float4 positionVS = mul(ViewSrg::m_projectionMatrixInverse, projectedPos);
positionVS /= positionVS.w;
float3 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS).xyz;
// compute ray from camera to surface position
float3 cameraToPositionWS = normalize(positionWS.xyz - ViewSrg::m_worldPosition);
@ -74,42 +130,16 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
// retrieve surface normal
float4 encodedNormal = PassSrg::m_normal.Load(screenCoords, sampleIndex);
float3 normalWS = DecodeNormalSignedOctahedron(encodedNormal.rgb);
// compute surface specular
float4 specularF0 = PassSrg::m_specularF0.Load(screenCoords, sampleIndex);
float roughness = specularF0.a;
float NdotV = dot(normalWS, -cameraToPositionWS);
float3 specular = FresnelSchlickWithRoughness(NdotV, specularF0.rgb, roughness);
// reconstruct the world space position of the trace coordinates
float2 traceUV = saturate(traceData.xy / dimensions);
float traceDepth = PassSrg::m_depth.Load(traceData.xy, sampleIndex).r;
float2 traceNDC = float2(traceUV.x, 1.0f - traceUV.y) * 2.0f - 1.0f;
float4 traceProjectedPos = float4(traceNDC, traceDepth, 1.0f);
float4 tracePositionVS = mul(ViewSrg::m_projectionMatrixInverse, traceProjectedPos);
tracePositionVS /= tracePositionVS.w;
float4 tracePositionWS = mul(ViewSrg::m_viewMatrixInverse, tracePositionVS);
// reproject to the previous frame image coordinates
float4 tracePrevNDC = mul(ViewSrg::m_viewProjectionPrevMatrix, tracePositionWS);
tracePrevNDC /= tracePrevNDC.w;
float2 tracePrevUV = float2(tracePrevNDC.x, -1.0f * tracePrevNDC.y) * 0.5f + 0.5f;
// compute the roughness mip to use in the previous frame image
// compute the roughness mip to use in the reflection image
// remap the roughness mip into a lower range to more closely match the material roughness values
const float MaxRoughness = 0.5f;
float mip = saturate(roughness / MaxRoughness) * PassSrg::m_maxMipLevel;
// sample reflection value from the roughness mip
float4 reflectionColor = float4(PassSrg::m_previousFrame.SampleLevel(PassSrg::LinearSampler, tracePrevUV, mip).rgb, 1.0f);
// fade rays close to screen edge
const float ScreenFadeDistance = 0.95f;
float2 fadeAmount = max(max(0.0f, traceUV - ScreenFadeDistance), max(0.0f, 1.0f - traceUV - ScreenFadeDistance));
fadeAmount /= (1.0f - ScreenFadeDistance);
float alpha = 1.0f - max(fadeAmount.x, fadeAmount.y);
// sample reflection color from the mip chain
float3 reflectionColor = SampleReflection(IN.m_texCoord, mip, depth, normalWS, 1.0f / dimensions);
PSOutput OUT;
OUT.m_color = float4(reflectionColor.rgb * specular, alpha);
OUT.m_color = float4(reflectionColor, reflectionData.w);
return OUT;
}

@ -10,7 +10,7 @@
#include <viewsrg.srgi>
#include <Atom/Features/PostProcessing/FullscreenVertexUtil.azsli>
#include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
#include <Atom/Features/MatrixUtility.azsli>
#include <Atom/Features/PBR/LightingUtils.azsli>
#include <Atom/Features/PBR/Microfacet/Fresnel.azsli>
@ -20,6 +20,18 @@ ShaderResourceGroup PassSrg : SRG_PerPass
Texture2DMS<float> m_depth;
Texture2DMS<float4> m_normal; // RGB10 = Normal (Encoded), A2 = Flags
Texture2DMS<float4> m_specularF0; // RGB8 = SpecularF0, A8 = Roughness
Texture2DMS<float4> m_reflection;
Texture2D<float4> m_previousFrame;
Sampler LinearSampler
{
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};
}
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
@ -49,6 +61,12 @@ VSOutput MainVS(VSInput input)
}
// Pixel Shader
struct PSOutput
{
float4 m_color : SV_Target0;
float m_depth : SV_Depth;
};
PSOutput MainPS(VSOutput IN)
{
// compute screen coords based on a half-res render target
@ -83,16 +101,83 @@ PSOutput MainPS(VSOutput IN)
// reflect view ray around surface normal
float3 reflectDirVS = normalize(reflect(cameraToPositionVS, normalVS));
// check to see if the reflected direction is approaching the camera
float rdotv = dot(reflectDirVS, -cameraToPositionVS);
bool fallbackEdge = false;
if (rdotv >= -0.05f)
{
if (rdotv >= 0.0f)
{
// ray points back to camera, fallback to cubemaps
discard;
}
// ray is approaching the camera direction, but not there yet - trace the reflection and set this
// as a non-reflected pixel, which will prevent artifacts at the boundary
fallbackEdge = true;
}
// trace screenspace rays against the depth buffer to find the screenspace intersection coordinates
float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f);
float2 hitCoords = float2(0.0f, 0.0f);
if (TraceRayScreenSpace(positionVS, reflectDirVS, dimensions, hitCoords))
{
float rdotv = dot(reflectDirVS, cameraToPositionVS);
result = float4(hitCoords, 0.0f, rdotv);
// reconstruct the world space position of the trace coordinates
float2 traceUV = saturate(hitCoords / dimensions);
float traceDepth = PassSrg::m_depth.Load(hitCoords, 0).r;
float2 traceNDC = float2(traceUV.x, 1.0f - traceUV.y) * 2.0f - 1.0f;
float4 traceProjectedPos = float4(traceNDC, traceDepth, 1.0f);
float4 tracePositionVS = mul(ViewSrg::m_projectionMatrixInverse, traceProjectedPos);
tracePositionVS /= tracePositionVS.w;
float4 tracePositionWS = mul(ViewSrg::m_viewMatrixInverse, tracePositionVS);
// reproject to the previous frame image coordinates
float4 tracePrevNDC = mul(ViewSrg::m_viewProjectionPrevMatrix, tracePositionWS);
tracePrevNDC /= tracePrevNDC.w;
float2 tracePrevUV = float2(tracePrevNDC.x, -1.0f * tracePrevNDC.y) * 0.5f + 0.5f;
// sample the previous frame image
result.rgb = PassSrg::m_previousFrame.SampleLevel(PassSrg::LinearSampler, tracePrevUV, 0).rgb;
// apply surface specular
float3 specularF0 = PassSrg::m_specularF0.Load(screenCoords, 0).rgb;
result.rgb *= specularF0;
// fade rays close to screen edge
const float ScreenFadeDistance = 0.95f;
float2 fadeAmount = max(max(0.0f, traceUV - ScreenFadeDistance), max(0.0f, 1.0f - traceUV - ScreenFadeDistance));
fadeAmount /= (1.0f - ScreenFadeDistance);
result.a = fallbackEdge ? 0.0f : 1.0f - max(fadeAmount.x, fadeAmount.y);
}
else
{
// ray miss, add in the IBL/probe reflections from the specular pass
float4 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS);
float3 cameraToPositionWS = normalize(positionWS - ViewSrg::m_worldPosition);
float3 reflectDirWS = normalize(reflect(cameraToPositionWS, normalWS));
result.rgb += PassSrg::m_reflection.Load(screenCoords, 0).rgb;
result.a = fallbackEdge ? 0.0f : 1.0f;
}
// downsample depth
float downsampledDepth = 0.0f;
for (int y = -2; y < 2; ++y)
{
for (int x = -2; x < 2; ++x)
{
float depth = PassSrg::m_depth.Load(screenCoords + int2(x, y), 0).r;
// take the closest depth sample (larger depth value due to reverse depth)
if (depth > downsampledDepth)
{
downsampledDepth = depth;
}
}
}
PSOutput OUT;
OUT.m_color = result;
OUT.m_depth = downsampledDepth;
return OUT;
}

@ -10,7 +10,8 @@
{
"Depth" :
{
"Enable" : false
"Enable" : true, // required to bind the depth buffer SRV
"CompareFunc" : "Always"
}
},

@ -100,6 +100,7 @@
#include <DiffuseGlobalIllumination/DiffuseProbeGridRenderPass.h>
#include <DiffuseGlobalIllumination/DiffuseProbeGridFeatureProcessor.h>
#include <DiffuseGlobalIllumination/DiffuseGlobalIlluminationFeatureProcessor.h>
#include <ReflectionScreenSpace/ReflectionScreenSpaceTracePass.h>
#include <ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.h>
#include <ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.h>
#include <ReflectionScreenSpace/ReflectionScreenSpaceCompositePass.h>
@ -292,6 +293,7 @@ namespace AZ
passSystem->AddPassCreator(Name("DeferredFogPass"), &DeferredFogPass::Create);
// Add Reflection passes
passSystem->AddPassCreator(Name("ReflectionScreenSpaceTracePass"), &Render::ReflectionScreenSpaceTracePass::Create);
passSystem->AddPassCreator(Name("ReflectionScreenSpaceBlurPass"), &Render::ReflectionScreenSpaceBlurPass::Create);
passSystem->AddPassCreator(Name("ReflectionScreenSpaceBlurChildPass"), &Render::ReflectionScreenSpaceBlurChildPass::Create);
passSystem->AddPassCreator(Name("ReflectionScreenSpaceCompositePass"), &Render::ReflectionScreenSpaceCompositePass::Create);

@ -7,7 +7,7 @@
*/
#include "ReflectionCopyFrameBufferPass.h"
#include "ReflectionScreenSpaceBlurPass.h"
#include "ReflectionScreenSpaceTracePass.h"
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#include <Atom/RPI.Public/Pass/PassFilter.h>
@ -28,16 +28,16 @@ namespace AZ
void ReflectionCopyFrameBufferPass::BuildInternal()
{
RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(AZ::Name("ReflectionScreenSpaceBlurPass"), GetRenderPipeline());
RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(AZ::Name("ReflectionScreenSpaceTracePass"), GetRenderPipeline());
RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow
{
Render::ReflectionScreenSpaceBlurPass* blurPass = azrtti_cast<ReflectionScreenSpaceBlurPass*>(pass);
Data::Instance<RPI::AttachmentImage>& frameBufferAttachment = blurPass->GetFrameBufferImageAttachment();
Render::ReflectionScreenSpaceTracePass* tracePass = azrtti_cast<ReflectionScreenSpaceTracePass*>(pass);
Data::Instance<RPI::AttachmentImage>& frameBufferAttachment = tracePass->GetPreviousFrameImageAttachment();
RPI::PassAttachmentBinding& outputBinding = GetOutputBinding(0);
AttachImageToSlot(outputBinding.m_name, frameBufferAttachment);
return RPI::PassFilterExecutionFlow::StopVisitingPasses;
return RPI::PassFilterExecutionFlow::StopVisitingPasses;
});
FullscreenTrianglePass::BuildInternal();

@ -12,6 +12,7 @@
#include <Atom/RHI/FrameGraphAttachmentInterface.h>
#include <Atom/RHI.Reflect/ImageViewDescriptor.h>
#include <Atom/RPI.Reflect/Pass/FullscreenTrianglePassData.h>
#include <Atom/RPI.Reflect/Pass/PassName.h>
#include <Atom/RPI.Public/Pass/PassDefines.h>
#include <Atom/RPI.Public/Pass/PassFactory.h>
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
@ -79,7 +80,7 @@ namespace AZ
horizontalBlurChildDesc.m_passTemplate = blurHorizontalPassTemplate;
// add child passes to perform the vertical and horizontal Gaussian blur for each roughness mip level
for (uint32_t mip = 0; mip < m_numBlurMips; ++mip)
for (uint32_t mip = 0; mip < NumMipLevels - 1; ++mip)
{
// create Vertical blur child passes
{
@ -114,35 +115,15 @@ namespace AZ
RemoveChildren();
m_flags.m_createChildren = true;
Data::Instance<RPI::AttachmentImagePool> pool = RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
// retrieve the image attachment from the pass
AZ_Assert(m_ownedAttachments.size() == 1, "ReflectionScreenSpaceBlurPass must have exactly one ImageAttachment defined");
RPI::Ptr<RPI::PassAttachment> reflectionImageAttachment = m_ownedAttachments[0];
// update the image attachment descriptor to sync up size and format
reflectionImageAttachment->Update();
// change the lifetime since we want it to live between frames
reflectionImageAttachment->m_lifetime = RHI::AttachmentLifetimeType::Imported;
// set the bind flags
RHI::ImageDescriptor& imageDesc = reflectionImageAttachment->m_descriptor.m_image;
imageDesc.m_bindFlags |= RHI::ImageBindFlags::Color | RHI::ImageBindFlags::ShaderReadWrite;
// create the image attachment
RHI::ClearValue clearValue = RHI::ClearValue::CreateVector4Float(0, 0, 0, 0);
m_frameBufferImageAttachment = RPI::AttachmentImage::Create(*pool.get(), imageDesc, Name(reflectionImageAttachment->m_path.GetCStr()), &clearValue, nullptr);
reflectionImageAttachment->m_path = m_frameBufferImageAttachment->GetAttachmentId();
reflectionImageAttachment->m_importedResource = m_frameBufferImageAttachment;
uint32_t mipLevels = reflectionImageAttachment->m_descriptor.m_image.m_mipLevels;
// retrieve the reflection, downsampled normal, and downsampled depth attachments
RPI::PassAttachment* reflectionImageAttachment = GetInputOutputBinding(0).m_attachment.get();
RHI::Size imageSize = reflectionImageAttachment->m_descriptor.m_image.m_size;
RPI::PassAttachment* downsampledDepthImageAttachment = GetInputOutputBinding(1).m_attachment.get();
// create transient attachments, one for each blur mip level
AZStd::vector<RPI::PassAttachment*> transientPassAttachments;
for (uint32_t mip = 1; mip <= mipLevels - 1; ++mip)
for (uint32_t mip = 1; mip <= NumMipLevels - 1; ++mip)
{
RHI::Size mipSize = imageSize.GetReducedMip(mip);
@ -160,8 +141,6 @@ namespace AZ
m_ownedAttachments.push_back(transientPassAttachment);
}
m_numBlurMips = mipLevels - 1;
// call ParentPass::BuildInternal() first to configure the slots and auto-add the empty bindings,
// then we will assign attachments to the bindings
ParentPass::BuildInternal();
@ -170,13 +149,27 @@ namespace AZ
uint32_t attachmentIndex = 0;
for (auto& verticalBlurChildPass : m_verticalBlurChildPasses)
{
// mip0 source input
RPI::PassAttachmentBinding& inputAttachmentBinding = verticalBlurChildPass->GetInputOutputBinding(0);
inputAttachmentBinding.SetAttachment(reflectionImageAttachment);
inputAttachmentBinding.m_connectedBinding = &GetInputOutputBinding(0);
// mipN transient output
RPI::PassAttachmentBinding& outputAttachmentBinding = verticalBlurChildPass->GetInputOutputBinding(1);
outputAttachmentBinding.SetAttachment(transientPassAttachments[attachmentIndex]);
// setup downsampled depth output
// Note: this is a vertical pass output only, and each vertical child pass writes a specific mip level
uint32_t mipLevel = attachmentIndex + 1;
// downsampled depth output
RPI::PassAttachmentBinding& downsampledDepthAttachmentBinding = verticalBlurChildPass->GetInputOutputBinding(2);
RHI::ImageViewDescriptor downsampledDepthOutputViewDesc;
downsampledDepthOutputViewDesc.m_mipSliceMin = static_cast<int16_t>(mipLevel);
downsampledDepthOutputViewDesc.m_mipSliceMax = static_cast<int16_t>(mipLevel);
downsampledDepthAttachmentBinding.m_unifiedScopeDesc.SetAsImage(downsampledDepthOutputViewDesc);
downsampledDepthAttachmentBinding.SetAttachment(downsampledDepthImageAttachment);
attachmentIndex++;
}

@ -29,12 +29,8 @@ namespace AZ
//! Creates a new pass without a PassTemplate
static RPI::Ptr<ReflectionScreenSpaceBlurPass> Create(const RPI::PassDescriptor& descriptor);
//! Returns the frame buffer image attachment used by the ReflectionFrameBufferCopy pass
//! to store the previous frame image
Data::Instance<RPI::AttachmentImage>& GetFrameBufferImageAttachment() { return m_frameBufferImageAttachment; }
//! Returns the number of mip levels in the blur
uint32_t GetNumBlurMips() const { return m_numBlurMips; }
//! The total number of mip levels in the blur (including mip0)
static const uint32_t NumMipLevels = 5;
private:
explicit ReflectionScreenSpaceBlurPass(const RPI::PassDescriptor& descriptor);
@ -47,9 +43,6 @@ namespace AZ
AZStd::vector<RPI::Ptr<RPI::FullscreenTrianglePass>> m_verticalBlurChildPasses;
AZStd::vector<RPI::Ptr<RPI::FullscreenTrianglePass>> m_horizontalBlurChildPasses;
Data::Instance<RPI::AttachmentImage> m_frameBufferImageAttachment;
uint32_t m_numBlurMips = 0;
};
} // namespace RPI
} // namespace AZ

@ -26,6 +26,19 @@ namespace AZ
{
}
bool ReflectionScreenSpaceCompositePass::IsEnabled() const
{
// delay for a few frames to ensure that the previous frame texture is populated
static const uint32_t FrameDelay = 10;
if (m_frameDelayCount < FrameDelay)
{
m_frameDelayCount++;
return false;
}
return true;
}
void ReflectionScreenSpaceCompositePass::CompileResources([[maybe_unused]] const RHI::FrameGraphCompileContext& context)
{
if (!m_shaderResourceGroup)
@ -33,22 +46,8 @@ namespace AZ
return;
}
RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassName(AZ::Name("ReflectionScreenSpaceBlurPass"), GetRenderPipeline());
RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [this](RPI::Pass* pass) -> RPI::PassFilterExecutionFlow
{
Render::ReflectionScreenSpaceBlurPass* blurPass = azrtti_cast<ReflectionScreenSpaceBlurPass*>(pass);
// compute the max mip level based on the available mips in the previous frame image, and capping it
// to stay within a range that has reasonable data
const uint32_t MaxNumRoughnessMips = 8;
uint32_t maxMipLevel = AZStd::min(MaxNumRoughnessMips, blurPass->GetNumBlurMips()) - 1;
auto constantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_maxMipLevel"));
m_shaderResourceGroup->SetConstant(constantIndex, maxMipLevel);
return RPI::PassFilterExecutionFlow::StopVisitingPasses;
});
auto constantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_maxMipLevel"));
m_shaderResourceGroup->SetConstant(constantIndex, ReflectionScreenSpaceBlurPass::NumMipLevels - 1);
FullscreenTrianglePass::CompileResources(context);
}

@ -34,6 +34,9 @@ namespace AZ
// Pass Overrides...
void CompileResources(const RHI::FrameGraphCompileContext& context) override;
bool IsEnabled() const override;
mutable uint32_t m_frameDelayCount = 0;
};
} // namespace RPI
} // namespace AZ

@ -0,0 +1,58 @@
/*
* 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
*
*/
#include "ReflectionScreenSpaceTracePass.h"
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
#include <Atom/RPI.Public/Pass/PassFilter.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
namespace AZ
{
namespace Render
{
RPI::Ptr<ReflectionScreenSpaceTracePass> ReflectionScreenSpaceTracePass::Create(const RPI::PassDescriptor& descriptor)
{
RPI::Ptr<ReflectionScreenSpaceTracePass> pass = aznew ReflectionScreenSpaceTracePass(descriptor);
return AZStd::move(pass);
}
ReflectionScreenSpaceTracePass::ReflectionScreenSpaceTracePass(const RPI::PassDescriptor& descriptor)
: RPI::FullscreenTrianglePass(descriptor)
{
}
void ReflectionScreenSpaceTracePass::BuildInternal()
{
Data::Instance<RPI::AttachmentImagePool> pool = RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
// retrieve the previous frame image attachment from the pass
AZ_Assert(m_ownedAttachments.size() == 3, "ReflectionScreenSpaceTracePass must have the following attachment images defined: ReflectionImage, DownSampledDepthImage, and PreviousFrameImage");
RPI::Ptr<RPI::PassAttachment> previousFrameImageAttachment = m_ownedAttachments[2];
// update the image attachment descriptor to sync up size and format
previousFrameImageAttachment->Update();
// change the lifetime since we want it to live between frames
previousFrameImageAttachment->m_lifetime = RHI::AttachmentLifetimeType::Imported;
// set the bind flags
RHI::ImageDescriptor& imageDesc = previousFrameImageAttachment->m_descriptor.m_image;
imageDesc.m_bindFlags |= RHI::ImageBindFlags::Color | RHI::ImageBindFlags::ShaderReadWrite;
// create the image attachment
RHI::ClearValue clearValue = RHI::ClearValue::CreateVector4Float(0, 0, 0, 0);
m_previousFrameImageAttachment = RPI::AttachmentImage::Create(*pool.get(), imageDesc, Name(previousFrameImageAttachment->m_path.GetCStr()), &clearValue, nullptr);
previousFrameImageAttachment->m_path = m_previousFrameImageAttachment->GetAttachmentId();
previousFrameImageAttachment->m_importedResource = m_previousFrameImageAttachment;
}
} // namespace RPI
} // namespace AZ

@ -0,0 +1,43 @@
/*
* 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
#include <Atom/RPI.Public/Pass/Pass.h>
#include <Atom/RPI.Public/Pass/FullscreenTrianglePass.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Shader/Shader.h>
namespace AZ
{
namespace Render
{
//! This pass traces screenspace reflections from the previous frame image.
class ReflectionScreenSpaceTracePass
: public RPI::FullscreenTrianglePass
{
AZ_RPI_PASS(DiffuseProbeGridDownsamplePass);
public:
AZ_RTTI(Render::ReflectionScreenSpaceTracePass, "{70FD45E9-8363-4AA1-A514-3C24AC975E53}", FullscreenTrianglePass);
AZ_CLASS_ALLOCATOR(Render::ReflectionScreenSpaceTracePass, SystemAllocator, 0);
//! Creates a new pass without a PassTemplate
static RPI::Ptr<ReflectionScreenSpaceTracePass> Create(const RPI::PassDescriptor& descriptor);
Data::Instance<RPI::AttachmentImage>& GetPreviousFrameImageAttachment() { return m_previousFrameImageAttachment; }
private:
explicit ReflectionScreenSpaceTracePass(const RPI::PassDescriptor& descriptor);
// Pass behavior overrides...
virtual void BuildInternal() override;
Data::Instance<RPI::AttachmentImage> m_previousFrameImageAttachment;
};
} // namespace RPI
} // namespace AZ

@ -275,6 +275,8 @@ set(FILES
Source/RayTracing/RayTracingPassData.h
Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp
Source/ReflectionProbe/ReflectionProbe.cpp
Source/ReflectionScreenSpace/ReflectionScreenSpaceTracePass.cpp
Source/ReflectionScreenSpace/ReflectionScreenSpaceTracePass.h
Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.cpp
Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.h
Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.cpp

Loading…
Cancel
Save