Files
o3de/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp
T
Ken Pruiksma 9df995dd26 Temporal anti-aliasing and constrast adaptive sharpening (#1161)
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.
2021-06-04 20:57:44 -05:00

435 lines
18 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 <Atom/RPI.Public/View.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Public/Culling.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Pass/Specific/SwapChainPass.h>
#include <Atom/RHI/DrawListTagRegistry.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Math/MatrixUtils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Atom_RPI_Traits_Platform.h>
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
#include <MaskedOcclusionCulling/MaskedOcclusionCulling.h>
#endif
namespace AZ
{
namespace RPI
{
// fixed-size software occlusion culling buffer
const uint32_t MaskedSoftwareOcclusionCullingWidth = 1920;
const uint32_t MaskedSoftwareOcclusionCullingHeight = 1080;
ViewPtr View::CreateView(const AZ::Name& name, UsageFlags usage)
{
View* view = aznew View(name, usage);
return ViewPtr(view);
}
View::View(const AZ::Name& name, UsageFlags usage)
: m_name(name)
, m_usageFlags(usage)
{
AZ_Assert(!name.IsEmpty(), "invalid name");
// Set default matrixes.
SetWorldToViewMatrix(AZ::Matrix4x4::CreateIdentity());
AZ::Matrix4x4 viewToClipMatrix;
AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, AZ::Constants::HalfPi, 1, 0.1f, 1000.f, true);
SetViewToClipMatrix(viewToClipMatrix);
Data::Asset<ShaderResourceGroupAsset> viewSrgAsset = RPISystemInterface::Get()->GetViewSrgAsset();
if (viewSrgAsset.IsReady())
{
m_shaderResourceGroup = ShaderResourceGroup::Create(viewSrgAsset);
}
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
m_maskedOcclusionCulling = MaskedOcclusionCulling::Create();
m_maskedOcclusionCulling->SetResolution(MaskedSoftwareOcclusionCullingWidth, MaskedSoftwareOcclusionCullingHeight);
#endif
}
View::~View()
{
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
if (m_maskedOcclusionCulling)
{
MaskedOcclusionCulling::Destroy(m_maskedOcclusionCulling);
m_maskedOcclusionCulling = nullptr;
}
#endif
}
void View::SetDrawListMask(const RHI::DrawListMask& drawListMask)
{
m_drawListMask = drawListMask;
m_drawListContext.Shutdown();
m_drawListContext.Init(m_drawListMask);
}
void View::Reset()
{
m_drawListMask.reset();
m_drawListContext.Shutdown();
m_passesByDrawList = nullptr;
}
RHI::ShaderResourceGroup* View::GetRHIShaderResourceGroup() const
{
return m_shaderResourceGroup->GetRHIShaderResourceGroup();
}
Data::Instance<RPI::ShaderResourceGroup> View::GetShaderResourceGroup()
{
return m_shaderResourceGroup;
}
void View::AddDrawPacket(const RHI::DrawPacket* drawPacket, float depth)
{
// This function is thread safe since DrawListContent has storage per thread for draw item data.
m_drawListContext.AddDrawPacket(drawPacket, depth);
}
void View::AddDrawPacket(const RHI::DrawPacket* drawPacket, Vector3 worldPosition)
{
Vector3 cameraToObject = worldPosition - m_position;
float depth = cameraToObject.Dot(-m_viewToWorldMatrix.GetBasisZAsVector3());
AddDrawPacket(drawPacket, depth);
}
void View::AddDrawItem(RHI::DrawListTag drawListTag, const RHI::DrawItemProperties& drawItemProperties)
{
m_drawListContext.AddDrawItem(drawListTag, drawItemProperties);
}
void View::SetWorldToViewMatrix(const AZ::Matrix4x4& worldToView)
{
m_viewToWorldMatrix = worldToView.GetInverseFast();
m_position = m_viewToWorldMatrix.GetTranslation();
m_worldToViewMatrix = worldToView;
m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix;
m_onWorldToViewMatrixChange.Signal(m_worldToViewMatrix);
m_onWorldToClipMatrixChange.Signal(m_worldToClipMatrix);
InvalidateSrg();
}
AZ::Transform View::GetCameraTransform() const
{
static const Quaternion yUpToZUp = Quaternion::CreateRotationX(-AZ::Constants::HalfPi);
return AZ::Transform::CreateFromQuaternionAndTranslation(
Quaternion::CreateFromMatrix4x4(m_viewToWorldMatrix) * yUpToZUp,
m_viewToWorldMatrix.GetTranslation()
).GetOrthogonalized();
}
void View::SetCameraTransform(const AZ::Matrix3x4& cameraTransform)
{
m_position = cameraTransform.GetTranslation();
// Before inverting the matrix we must first adjust from Z-up to Y-up. The camera world matrix
// is in a Z-up world and an identity matrix means that it faces along the positive-Y axis and Z is up.
// An identity view matrix on the other hand looks along the negative Z-axis.
// So we adjust for this by rotating the camera world matrix by 90 degrees around the X axis.
static AZ::Matrix3x4 zUpToYUp = AZ::Matrix3x4::CreateRotationX(AZ::Constants::HalfPi);
AZ::Matrix3x4 yUpWorld = cameraTransform * zUpToYUp;
float viewToWorldMatrixRaw[16] = {
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1 };
yUpWorld.StoreToRowMajorFloat12(viewToWorldMatrixRaw);
m_viewToWorldMatrix = AZ::Matrix4x4::CreateFromRowMajorFloat16(viewToWorldMatrixRaw);
m_worldToViewMatrix = m_viewToWorldMatrix.GetInverseFast();
m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix;
m_onWorldToViewMatrixChange.Signal(m_worldToViewMatrix);
m_onWorldToClipMatrixChange.Signal(m_worldToClipMatrix);
InvalidateSrg();
}
void View::SetViewToClipMatrix(const AZ::Matrix4x4& viewToClip)
{
m_viewToClipMatrix = viewToClip;
m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix;
// Update z depth constant simultaneously
// zNear -> n, zFar -> f
// A = f / (n - f), B = nf / (n - f)
// the formula of A and B should be the same as projection matrix's definition
// currently defined in MakePerspectiveFovMatrixRH in MatrixUtil.cpp
double A = m_viewToClipMatrix.GetElement(2, 2);
double B = m_viewToClipMatrix.GetElement(2, 3);
m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ.SetX(float(B / A));
m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ.SetY(float(B / (A + 1.0)));
m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ.SetZ(float((B * B) / (A * (A + 1.0))));
m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ.SetW(float(-B / (A * (A + 1.0))));
double tanHalfFovX = 1.0 / m_viewToClipMatrix.GetElement(0, 0);
double tanHalfFovY = 1.0 / m_viewToClipMatrix.GetElement(1, 1);
m_unprojectionConstants.SetX(float(2.0 * tanHalfFovX));
m_unprojectionConstants.SetY(float(-2.0 * tanHalfFovY));
m_unprojectionConstants.SetZ(float(-tanHalfFovX));
m_unprojectionConstants.SetW(float(tanHalfFovY));
m_onWorldToClipMatrixChange.Signal(m_worldToClipMatrix);
InvalidateSrg();
}
void View::SetClipSpaceOffset(float xOffset, float yOffset)
{
m_clipSpaceOffset.Set(xOffset, yOffset);
InvalidateSrg();
}
const AZ::Matrix4x4& View::GetWorldToViewMatrix() const
{
return m_worldToViewMatrix;
}
const AZ::Matrix4x4& View::GetViewToWorldMatrix() const
{
return m_viewToWorldMatrix;
}
const AZ::Matrix4x4& View::GetViewToClipMatrix() const
{
return m_viewToClipMatrix;
}
const AZ::Matrix4x4& View::GetWorldToClipMatrix() const
{
return m_worldToClipMatrix;
}
bool View::HasDrawListTag(RHI::DrawListTag drawListTag)
{
return drawListTag.IsValid() && m_drawListMask[drawListTag.GetIndex()];
}
RHI::DrawListView View::GetDrawList(RHI::DrawListTag drawListTag)
{
return m_drawListContext.GetList(drawListTag);
}
void View::FinalizeDrawLists()
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
m_drawListContext.FinalizeLists();
SortFinalizedDrawLists();
}
void View::SortFinalizedDrawLists()
{
RHI::DrawListsByTag& drawListsByTag = m_drawListContext.GetMergedDrawListsByTag();
for (size_t idx = 0; idx < drawListsByTag.size(); ++idx)
{
if (drawListsByTag[idx].size() > 1)
{
SortDrawList(drawListsByTag[idx], RHI::DrawListTag(idx));
}
}
}
void View::SortDrawList(RHI::DrawList& drawList, RHI::DrawListTag tag)
{
const Pass* passWithDrawListTag = (*m_passesByDrawList)[tag];
passWithDrawListTag->SortDrawList(drawList);
}
void View::ConnectWorldToViewMatrixChangedHandler(View::MatrixChangedEvent::Handler& handler)
{
handler.Connect(m_onWorldToViewMatrixChange);
}
void View::ConnectWorldToClipMatrixChangedHandler(View::MatrixChangedEvent::Handler& handler)
{
handler.Connect(m_onWorldToClipMatrixChange);
}
// [GFX TODO] This function needs unit tests and might need to be reworked
RHI::DrawItemSortKey View::GetSortKeyForPosition(const Vector3& positionInWorld) const
{
// We are using fixed-point depth representation for the u64 sort key
// Compute position in clip space
const Vector4 worldPosition4 = Vector4::CreateFromVector3(positionInWorld);
const Vector4 clipSpacePosition = m_worldToClipMatrix * worldPosition4;
// Get a depth value guaranteed to be in the range 0 to 1
float normalizedDepth = clipSpacePosition.GetZ() / clipSpacePosition.GetW();
normalizedDepth = (normalizedDepth + 1.0f) * 0.5f;
normalizedDepth = AZStd::clamp<float>(normalizedDepth, 0.f, 1.f);
// Convert the depth into a uint64
RHI::DrawItemSortKey sortKey = static_cast<RHI::DrawItemSortKey>(normalizedDepth * azlossy_cast<double>(std::numeric_limits<RHI::DrawItemSortKey>::max()));
return sortKey;
}
float View::CalculateSphereAreaInClipSpace(const AZ::Vector3& sphereWorldPosition, float sphereRadius) const
{
// Projection of a sphere to clip space
// Derived from https://www.iquilezles.org/www/articles/sphereproj/sphereproj.htm
if (sphereRadius <= 0.0f)
{
return 0.0f;
}
const AZ::Matrix4x4& worldToViewMatrix = GetWorldToViewMatrix();
const AZ::Matrix4x4& viewToClipMatrix = GetViewToClipMatrix();
// transform to camera space (eye space)
const Vector4 worldPosition4 = Vector4::CreateFromVector3(sphereWorldPosition);
const Vector4 viewSpacePosition = worldToViewMatrix * worldPosition4;
float zDist = -viewSpacePosition.GetZ(); // in our view space Z is negative in front of the camera
if (zDist < 0.0f)
{
// sphere center is behind camera.
if (zDist < -sphereRadius)
{
return 0.0f; // whole of sphere is behind camera so zero coverage
}
else
{
return 1.0f; // camera is inside sphere so treat as covering whole view
}
}
else
{
if (zDist < sphereRadius)
{
return 1.0f; // camera is inside sphere so treat as covering whole view
}
}
// Element 1,1 of the projection matrix is equal to : 1 / tan(fovY/2) AKA cot(fovY/2)
// See https://stackoverflow.com/questions/46182845/field-of-view-aspect-ratio-view-matrix-from-projection-matrix-hmd-ost-calib
float cotHalfFovY = viewToClipMatrix.GetElement(1, 1);
float radiusSq = sphereRadius * sphereRadius;
float depthSq = zDist * zDist;
float distanceSq = viewSpacePosition.GetAsVector3().GetLengthSq();
float cotHalfFovYSq = cotHalfFovY * cotHalfFovY;
float radiusSqSubDepthSq = radiusSq - depthSq;
const float epsilon = 0.00001f;
if (fabsf(radiusSqSubDepthSq) < epsilon)
{
// treat as covering entire view since we don't want to divide by zero
return 1.0f;
}
// This will return 1.0f when an area equal in size to the viewport height squared is covered.
// So to get actual pixels covered do : coverage * viewport-resolution-y * viewport-resolution-y
// The actual math computes the area of an ellipse as a percentage of the view area, see the paper above for the steps
// to simplify the equations into this calculation.
return -0.25f * cotHalfFovYSq * AZ::Constants::Pi * radiusSq * sqrt(fabsf((distanceSq - radiusSq)/radiusSqSubDepthSq))/radiusSqSubDepthSq;
}
void View::InvalidateSrg()
{
m_needBuildSrg = true;
}
void View::UpdateSrg()
{
if (m_needBuildSrg)
{
if (m_clipSpaceOffset.IsZero())
{
Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix;
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix);
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix);
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull());
}
else
{
// Offset the current and previous frame clip matricies
Matrix4x4 offsetViewToClipMatrix = m_viewToClipMatrix;
offsetViewToClipMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX());
offsetViewToClipMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY());
Matrix4x4 offsetViewToClipPrevMatrix = m_viewToClipPrevMatrix;
offsetViewToClipPrevMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX());
offsetViewToClipPrevMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY());
// Build other matricies dependent on the view to clip matricies
Matrix4x4 offsetWorldToClipMatrix = offsetViewToClipMatrix * m_worldToViewMatrix;
Matrix4x4 offsetWorldToClipPrevMatrix = offsetViewToClipPrevMatrix * m_worldToViewPrevMatrix;
Matrix4x4 offsetClipToViewMatrix = offsetViewToClipMatrix.GetInverseFull();
Matrix4x4 offsetClipToWorldMatrix = m_viewToWorldMatrix * offsetClipToViewMatrix;
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix);
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix);
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull());
}
m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position);
m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix);
m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull());
m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ);
m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants);
m_shaderResourceGroup->Compile();
m_needBuildSrg = false;
}
m_viewToClipPrevMatrix = m_viewToClipMatrix;
m_worldToViewPrevMatrix = m_worldToViewMatrix;
m_clipSpaceOffset.Set(0);
}
void View::BeginCulling()
{
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
m_maskedOcclusionCulling->ClearBuffer();
#endif
}
MaskedOcclusionCulling* View::GetMaskedOcclusionCulling()
{
return m_maskedOcclusionCulling;
}
} // namespace RPI
} // namespace AZ