9df995dd26
First version of temporal antialiasing and contrast adaptive sharpening for GA. Works well in most cases but still has a few issues that will need additional time. This is only the passes and shaders with no exposure to the editor. TAA and CAS can be turned on by enabling their respective passes in the pipeline. All of the code has been previously reviewed in smaller PRs into the taa_staging branch: aws-lumberyard-dev#29 aws-lumberyard-dev#53 aws-lumberyard-dev#73 aws-lumberyard-dev#79 aws-lumberyard-dev#84 Main issues: - Bloom doesn't play nice with TAA and seems to greatly amplify any flickering - AuxGeom jitters with the camera, so TAA doesn't currently work well in editor - Transparencies don't have correct motion vectors. History rectification keeps this from looking too bad, but could still be improved - There is still more that could be done to inhibit flickering, usually from specular aliasing - Motion vectors aren't correct on POM unless PDO is turned on, which can result in some blurring during motion. - SSAO can contribute to flickering in its default half res configuration. Changing this to full res mitigates the problem. Squashed merge of the following: * [ATOM-13987] Initial checkin of Taa pass. * TAA pass setup WIP. (does not work yet due to pass configuration issues). * Taa WIP - Camera motion vectors fixed and hooked up. TAA does simple reprojection and rejection based on depth. * Small update to use lerp and add some comments. * Fix issue with attachments not being set up on bindings at initialization. Fixing issue with half-pixel offsets in TAA shader * - Motion vector passes now use the same output with mesh motion vectors overwriting camera motion vectors. - Taa pass now works with multiple pipelines. - Cleaned up TAA shader a bit. * Fixes from PR review. * Adding check for multiple attachments of the same name with different resources in Pass::ImportAttachments(). * Adding camera jitter with configurable position count. Updated TAA to blend in tonemapped space. * Fixes from PR review. Fixing camera motion vectors for background (infinite distance) * Updates to taa shader from PR review * Adding a rcp input color size. * Fix comment on PassAttachment::Update() * Updates for PR review. * Fixing missing const on the FrameAttachment* in Pass's call to FindAttachment() * Taa WIP - Adding filtering to both the current pixel and history. Adding rectification based on variance clipping. Adding some basic anti-flickering. Removing rejection based on depth. * Updates from PR code review. Mostly better commenting and naming. * Adding contrast adaptive sharpening based on AMD FidelityFX CAS to help with the softness added by TAA. * Changing to using luminance for sharpening instead of just green. Added some comments. * Moving Taa's NaN check to a better location. Disabling TAA and sharpening in prep for check in. * Updates from PR feedback.
248 lines
10 KiB
C++
248 lines
10 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
#include <PostProcessing/TaaPass.h>
|
|
|
|
#include <AzCore/Math/Random.h>
|
|
#include <Atom/RPI.Public/Image/AttachmentImagePool.h>
|
|
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
|
|
#include <Atom/RPI.Public/Pass/PassUtils.h>
|
|
#include <Atom/RPI.Public/RenderPipeline.h>
|
|
#include <Atom/RPI.Public/View.h>
|
|
#include <Atom/RPI.Reflect/Pass/PassName.h>
|
|
|
|
namespace AZ::Render
|
|
{
|
|
|
|
RPI::Ptr<TaaPass> TaaPass::Create(const RPI::PassDescriptor& descriptor)
|
|
{
|
|
RPI::Ptr<TaaPass> pass = aznew TaaPass(descriptor);
|
|
return pass;
|
|
}
|
|
|
|
TaaPass::TaaPass(const RPI::PassDescriptor& descriptor)
|
|
: Base(descriptor)
|
|
{
|
|
uint32_t numJitterPositions = 8;
|
|
|
|
const TaaPassData* taaPassData = RPI::PassUtils::GetPassData<TaaPassData>(descriptor);
|
|
if (taaPassData)
|
|
{
|
|
numJitterPositions = taaPassData->m_numJitterPositions;
|
|
}
|
|
|
|
// The coprimes 2, 3 are commonly used for halton sequences because they have an even distribution even for
|
|
// few samples. With larger primes you need to offset by some amount between each prime to have the same
|
|
// effect. We could allow this to be configurable in the future.
|
|
SetupSubPixelOffsets(2, 3, numJitterPositions);
|
|
}
|
|
|
|
void TaaPass::CompileResources(const RHI::FrameGraphCompileContext& context)
|
|
{
|
|
struct TaaConstants
|
|
{
|
|
AZStd::array<uint32_t, 2> m_size = { 1, 1 };
|
|
AZStd::array<float, 2> m_rcpSize = { 0.0, 0.0 };
|
|
|
|
AZStd::array<float, 4> m_weights1 = { 0.0 };
|
|
AZStd::array<float, 4> m_weights2 = { 0.0 };
|
|
AZStd::array<float, 4> m_weights3 = { 0.0 };
|
|
};
|
|
|
|
TaaConstants cb;
|
|
RHI::Size inputSize = m_lastFrameAccumulationBinding->m_attachment->m_descriptor.m_image.m_size;
|
|
cb.m_size[0] = inputSize.m_width;
|
|
cb.m_size[1] = inputSize.m_height;
|
|
cb.m_rcpSize[0] = 1.0f / inputSize.m_width;
|
|
cb.m_rcpSize[1] = 1.0f / inputSize.m_height;
|
|
|
|
Offset jitterOffset = m_subPixelOffsets.at(m_offsetIndex);
|
|
GenerateFilterWeights(Vector2(jitterOffset.m_xOffset, jitterOffset.m_yOffset));
|
|
cb.m_weights1 = { m_filterWeights[0], m_filterWeights[1], m_filterWeights[2], m_filterWeights[3] };
|
|
cb.m_weights2 = { m_filterWeights[4], m_filterWeights[5], m_filterWeights[6], m_filterWeights[7] };
|
|
cb.m_weights3 = { m_filterWeights[8], 0.0f, 0.0f, 0.0f };
|
|
|
|
m_shaderResourceGroup->SetConstant(m_constantDataIndex, cb);
|
|
|
|
|
|
Base::CompileResources(context);
|
|
}
|
|
|
|
void TaaPass::FrameBeginInternal(FramePrepareParams params)
|
|
{
|
|
RHI::Size inputSize = m_inputColorBinding->m_attachment->m_descriptor.m_image.m_size;
|
|
Vector2 rcpInputSize = Vector2(1.0 / inputSize.m_width, 1.0 / inputSize.m_height);
|
|
|
|
RPI::ViewPtr view = GetRenderPipeline()->GetDefaultView();
|
|
m_offsetIndex = (m_offsetIndex + 1) % m_subPixelOffsets.size();
|
|
Offset offset = m_subPixelOffsets.at(m_offsetIndex);
|
|
view->SetClipSpaceOffset(offset.m_xOffset * rcpInputSize.GetX(), offset.m_yOffset * rcpInputSize.GetY());
|
|
|
|
m_lastFrameAccumulationBinding->SetAttachment(m_accumulationAttachments[m_accumulationOuptutIndex]);
|
|
m_accumulationOuptutIndex ^= 1; // swap which attachment is the output and last frame
|
|
|
|
UpdateAttachmentImage(m_accumulationAttachments[m_accumulationOuptutIndex]);
|
|
m_outputColorBinding->SetAttachment(m_accumulationAttachments[m_accumulationOuptutIndex]);
|
|
|
|
Base::FrameBeginInternal(params);
|
|
}
|
|
|
|
void TaaPass::ResetInternal()
|
|
{
|
|
m_accumulationAttachments[0].reset();
|
|
m_accumulationAttachments[1].reset();
|
|
|
|
m_inputColorBinding = nullptr;
|
|
m_lastFrameAccumulationBinding = nullptr;
|
|
m_outputColorBinding = nullptr;
|
|
|
|
Base::ResetInternal();
|
|
}
|
|
|
|
void TaaPass::BuildAttachmentsInternal()
|
|
{
|
|
m_accumulationAttachments[0] = FindAttachment(Name("Accumulation1"));
|
|
m_accumulationAttachments[1] = FindAttachment(Name("Accumulation2"));
|
|
|
|
bool hasAttachments = m_accumulationAttachments[0] || m_accumulationAttachments[1];
|
|
AZ_Error("TaaPass", hasAttachments, "TaaPass must have Accumulation1 and Accumulation2 ImageAttachments defined.");
|
|
|
|
if (hasAttachments)
|
|
{
|
|
// Make sure the attachments have images when the pass first loads.
|
|
for (auto i : { 0, 1 })
|
|
{
|
|
if (!m_accumulationAttachments[i]->m_importedResource)
|
|
{
|
|
UpdateAttachmentImage(m_accumulationAttachments[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_inputColorBinding = FindAttachmentBinding(Name("InputColor"));
|
|
AZ_Error("TaaPass", m_inputColorBinding, "TaaPass requires a slot for InputColor.");
|
|
m_lastFrameAccumulationBinding = FindAttachmentBinding(Name("LastFrameAccumulation"));
|
|
AZ_Error("TaaPass", m_lastFrameAccumulationBinding, "TaaPass requires a slot for LastFrameAccumulation.");
|
|
m_outputColorBinding = FindAttachmentBinding(Name("OutputColor"));
|
|
AZ_Error("TaaPass", m_outputColorBinding, "TaaPass requires a slot for OutputColor.");
|
|
|
|
// Set up the attachment for last frame accumulation and output color if it's never been done to
|
|
// ensure SRG indices are set up correctly by the pass system.
|
|
if (m_lastFrameAccumulationBinding->m_attachment == nullptr)
|
|
{
|
|
m_lastFrameAccumulationBinding->SetAttachment(m_accumulationAttachments[0]);
|
|
m_outputColorBinding->SetAttachment(m_accumulationAttachments[1]);
|
|
}
|
|
|
|
Base::BuildAttachmentsInternal();
|
|
}
|
|
|
|
void TaaPass::UpdateAttachmentImage(RPI::Ptr<RPI::PassAttachment>& attachment)
|
|
{
|
|
if (!attachment)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// update the image attachment descriptor to sync up size and format
|
|
attachment->Update(true);
|
|
RHI::ImageDescriptor& imageDesc = attachment->m_descriptor.m_image;
|
|
RPI::AttachmentImage* currentImage = azrtti_cast<RPI::AttachmentImage*>(attachment->m_importedResource.get());
|
|
|
|
if (attachment->m_importedResource && imageDesc.m_size == currentImage->GetDescriptor().m_size)
|
|
{
|
|
// If there's a resource already and the size didn't change, just keep using the old AttachmentImage.
|
|
return;
|
|
}
|
|
|
|
Data::Instance<RPI::AttachmentImagePool> pool = RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool();
|
|
|
|
// set the bind flags
|
|
imageDesc.m_bindFlags |= RHI::ImageBindFlags::Color | RHI::ImageBindFlags::ShaderReadWrite;
|
|
|
|
// The ImageViewDescriptor must be specified to make sure the frame graph compiler doesn't treat this as a transient image.
|
|
RHI::ImageViewDescriptor viewDesc = RHI::ImageViewDescriptor::Create(imageDesc.m_format, 0, 0);
|
|
viewDesc.m_aspectFlags = RHI::ImageAspectFlags::Color;
|
|
viewDesc.m_overrideBindFlags = RHI::ImageBindFlags::ShaderReadWrite;
|
|
|
|
// The full path name is needed for the attachment image so it's not deduplicated from accumulation images in different pipelines.
|
|
AZStd::string imageName = RPI::ConcatPassString(GetPathName(), attachment->m_path);
|
|
auto attachmentImage = RPI::AttachmentImage::Create(*pool.get(), imageDesc, Name(imageName), nullptr, &viewDesc);
|
|
|
|
attachment->m_path = attachmentImage->GetAttachmentId();
|
|
attachment->m_importedResource = attachmentImage;
|
|
}
|
|
|
|
void TaaPass::SetupSubPixelOffsets(uint32_t haltonX, uint32_t haltonY, uint32_t length)
|
|
{
|
|
m_subPixelOffsets.resize(length);
|
|
HaltonSequence<2> sequence = HaltonSequence<2>({haltonX, haltonY});
|
|
sequence.FillHaltonSequence(m_subPixelOffsets.begin(), m_subPixelOffsets.end());
|
|
|
|
// Adjust to the -1.0 to 1.0 range. This is done because the view needs offsets in clip
|
|
// space and is one less calculation that would need to be done in FrameBeginInternal()
|
|
AZStd::for_each(m_subPixelOffsets.begin(), m_subPixelOffsets.end(),
|
|
[](Offset& offset)
|
|
{
|
|
offset.m_xOffset = 2.0f * offset.m_xOffset - 1.0f;
|
|
offset.m_yOffset = 2.0f * offset.m_yOffset - 1.0f;
|
|
}
|
|
);
|
|
}
|
|
|
|
// Approximation of a Blackman Harris window function of width 3.3.
|
|
// https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window
|
|
static float BlackmanHarris(AZ::Vector2 uv)
|
|
{
|
|
return expf(-2.29f * (uv.GetX() * uv.GetX() + uv.GetY() * uv.GetY()));
|
|
}
|
|
|
|
// Generates filter weights for the 3x3 neighborhood of a pixel. Since jitter positions are the
|
|
// same for every pixel we can calculate this once here and upload to the SRG.
|
|
// Jitter weights are based on a window function centered at the pixel center (we use Blackman-Harris).
|
|
// As the jitter position moves around, some neighborhood locations decrease in weight, and others
|
|
// increase in weight based on their distance from the center of the pixel.
|
|
void TaaPass::GenerateFilterWeights(AZ::Vector2 jitterOffset)
|
|
{
|
|
static const AZStd::array<Vector2, 9> pixelOffsets =
|
|
{
|
|
// Center
|
|
Vector2(0.0f, 0.0f),
|
|
// Cross
|
|
Vector2( 1.0f, 0.0f),
|
|
Vector2( 0.0f, 1.0f),
|
|
Vector2(-1.0f, 0.0f),
|
|
Vector2( 0.0f, -1.0f),
|
|
// Diagonals
|
|
Vector2( 1.0f, 1.0f),
|
|
Vector2( 1.0f, -1.0f),
|
|
Vector2(-1.0f, 1.0f),
|
|
Vector2(-1.0f, -1.0f),
|
|
};
|
|
|
|
float sum = 0.0f;
|
|
for (uint32_t i = 0; i < 9; ++i)
|
|
{
|
|
m_filterWeights[i] = BlackmanHarris(pixelOffsets[i] + jitterOffset);
|
|
sum += m_filterWeights[i];
|
|
}
|
|
|
|
// Normalize the weight so the sum of all weights is 1.0.
|
|
float normalization = 1.0f / sum;
|
|
for (uint32_t i = 0; i < 9; ++i)
|
|
{
|
|
m_filterWeights[i] *= normalization;
|
|
}
|
|
}
|
|
|
|
} // namespace AZ::Render
|