From fc31565f127a98ac3f666b9225d63bc5a736671f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 19 Jul 2021 14:02:46 +0200 Subject: [PATCH] Reimplement Temporal AA with less ghosting and better quality --- Content/Shaders/TAA.flax | 4 +- Source/Engine/Graphics/RenderView.cpp | 11 ++- Source/Engine/Graphics/RenderView.h | 2 - Source/Engine/Renderer/AntiAliasing/TAA.cpp | 77 +++++++++++++++----- Source/Engine/Renderer/AntiAliasing/TAA.h | 3 +- Source/Engine/Renderer/Renderer.cpp | 9 +-- Source/Shaders/TAA.shader | 79 ++++++++++++++++++++- 7 files changed, 153 insertions(+), 32 deletions(-) diff --git a/Content/Shaders/TAA.flax b/Content/Shaders/TAA.flax index f14be47f6..0a359eca5 100644 --- a/Content/Shaders/TAA.flax +++ b/Content/Shaders/TAA.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18053d25260b9636af403508225c1a7934ddf92a60c52c426b70c057ac53e559 -size 712 +oid sha256:5ac1ebf8bf16576e25b2dfd8e063c788f4b6a160786a97038405c6b01bd001a2 +size 3371 diff --git a/Source/Engine/Graphics/RenderView.cpp b/Source/Engine/Graphics/RenderView.cpp index b270ba69a..c9da7a1b4 100644 --- a/Source/Engine/Graphics/RenderView.cpp +++ b/Source/Engine/Graphics/RenderView.cpp @@ -31,8 +31,15 @@ void RenderView::Prepare(RenderContext& renderContext) taaJitter = Vector2(jitterX * 2.0f / width, jitterY * 2.0f / height); // Modify projection matrix - Projection.Values[2][0] += taaJitter.X; - Projection.Values[2][1] += taaJitter.Y; + if (IsOrthographicProjection()) + { + // TODO: jitter otho matrix in a proper way + } + else + { + Projection.Values[2][0] += taaJitter.X; + Projection.Values[2][1] += taaJitter.Y; + } // Update matrices Matrix::Invert(Projection, IP); diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index 22acbfb52..88cbe07bd 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -202,7 +202,6 @@ public: /// /// Determines whether view is perspective projection or orthographic. /// - /// True if view is perspective, otherwise false if view is orthographic. FORCE_INLINE bool IsPerspectiveProjection() const { return Projection.M44 < 1.0f; @@ -211,7 +210,6 @@ public: /// /// Determines whether view is orthographic projection or perspective. /// - /// True if view is orthographic, otherwise false if view is perspective. FORCE_INLINE bool IsOrthographicProjection() const { return Projection.M44 >= 1.0f; diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.cpp b/Source/Engine/Renderer/AntiAliasing/TAA.cpp index e61132b42..c3121d5be 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/TAA.cpp @@ -11,31 +11,47 @@ #include "Engine/Renderer/RenderList.h" #include "Engine/Engine/Engine.h" +PACK_STRUCT(struct Data + { + Vector2 ScreenSizeInv; + Vector2 JitterInv; + float Sharpness; + float StationaryBlending; + float MotionBlending; + float Dummy0; + }); + bool TAA::Init() { - // Create pipeline state - //_psTAA.CreatePipelineStates(); - - // Load shader _shader = Content::LoadAsyncInternal(TEXT("Shaders/TAA")); if (_shader == nullptr) return true; #if COMPILE_WITH_DEV_ENV _shader.Get()->OnReloading.Bind(this); #endif - return false; } bool TAA::setupResources() { - // Check shader if (!_shader->IsLoaded()) + return true; + const auto shader = _shader->GetShader(); + if (shader->GetCB(0)->GetSize() != sizeof(Data)) { + REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); return true; } - const auto shader = _shader->GetShader(); - + if (!_psTAA) + _psTAA = GPUDevice::Instance->CreatePipelineState(); + GPUPipelineState::Description psDesc; + if (!_psTAA->IsValid()) + { + psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + psDesc.PS = shader->GetPS("PS"); + if (_psTAA->Init(psDesc)) + return true; + } return false; } @@ -44,8 +60,7 @@ void TAA::Dispose() // Base RendererPass::Dispose(); - // Cleanup - _psTAA = nullptr; + SAFE_DELETE_GPU_RESOURCE(_psTAA); _shader = nullptr; } @@ -59,7 +74,7 @@ void TAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView auto context = GPUDevice::Instance->GetMainContext(); // Ensure to have valid data - //if (checkIfSkipPass()) + if (checkIfSkipPass()) { // Resources are missing. Do not perform rendering, just copy source frame. context->SetRenderTarget(output); @@ -73,7 +88,7 @@ void TAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView // Get history buffers bool resetHistory = renderContext.Task->IsCameraCut; renderContext.Buffers->LastFrameTemporalAA = Engine::FrameCount; - const auto tempDesc = GPUTextureDescription::New2D((int32)renderContext.View.ScreenSize.X, (int32)renderContext.View.ScreenSize.Y, input->Format()); + const auto tempDesc = GPUTextureDescription::New2D(input->Width(), input->Height(), input->Format()); if (renderContext.Buffers->TemporalAA == nullptr) { // Missing temporal buffer @@ -94,14 +109,44 @@ void TAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView float blendStrength = 1.0f; if (resetHistory) { - PROFILE_GPU_CPU("Reset History"); - +#if 0 + context->CopyTexture(inputHistory, 0, 0, 0, 0, input, 0); +#else context->SetRenderTarget(inputHistory->View()); context->Draw(input); context->ResetRenderTarget(); - +#endif blendStrength = 0.0f; } - // ... + // Bind input + Data data; + data.ScreenSizeInv.X = renderContext.View.ScreenSize.Z; + data.ScreenSizeInv.Y = renderContext.View.ScreenSize.W; + data.JitterInv.X = renderContext.View.TemporalAAJitter.X / (float)tempDesc.Width; + data.JitterInv.Y = renderContext.View.TemporalAAJitter.Y / (float)tempDesc.Height; + data.Sharpness = settings.TAA_Sharpness; + data.StationaryBlending = settings.TAA_StationaryBlending * blendStrength; + data.MotionBlending = settings.TAA_MotionBlending * blendStrength; + const auto cb = _shader->GetShader()->GetCB(0); + context->UpdateCB(cb, &data); + context->BindCB(0, cb); + context->BindSR(0, input); + context->BindSR(1, inputHistory); + context->BindSR(2, renderContext.Buffers->MotionVectors); + context->BindSR(3, renderContext.Buffers->DepthBuffer); + + // Render + context->SetRenderTarget(output); + context->SetState(_psTAA); + context->DrawFullscreenTriangle(); + + // Update the history + { + RenderTargetPool::Release(inputHistory); + context->ResetRenderTarget(); + context->SetRenderTarget(outputHistory->View()); + context->Draw(output); + renderContext.Buffers->TemporalAA = outputHistory; + } } diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.h b/Source/Engine/Renderer/AntiAliasing/TAA.h index 904153f7c..c4022a147 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.h +++ b/Source/Engine/Renderer/AntiAliasing/TAA.h @@ -37,7 +37,7 @@ private: #if COMPILE_WITH_DEV_ENV void OnShaderReloading(Asset* obj) { - _psTAA = nullptr; + _psTAA->ReleaseGPU(); invalidateResources(); } #endif @@ -49,7 +49,6 @@ public: { return TEXT("TAA"); } - bool Init() override; void Dispose() override; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index f2abe867a..8c82f9792 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -294,13 +294,8 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) task->CollectPostFxVolumes(renderContext); renderContext.List->BlendSettings(); auto aaMode = (renderContext.View.Flags & ViewFlags::AntiAliasing) != 0 ? renderContext.List->Settings.AntiAliasing.Mode : AntialiasingMode::None; - if (view.IsOrthographicProjection() && aaMode == AntialiasingMode::TemporalAntialiasing) - aaMode = AntialiasingMode::None; // TODO: support TAA in ortho projection -#if USE_EDITOR - // Disable temporal AA effect in editor without play mode enabled to hide minor artifacts on objects moving and lack of valid motion vectors - if (!Editor::IsPlayMode && aaMode == AntialiasingMode::TemporalAntialiasing) - aaMode = AntialiasingMode::FastApproximateAntialiasing; -#endif + if (aaMode == AntialiasingMode::TemporalAntialiasing && view.IsOrthographicProjection()) + aaMode = AntialiasingMode::None; // TODO: support TAA in ortho projection (see RenderView::Prepare to jitter projection matrix better) renderContext.List->Settings.AntiAliasing.Mode = aaMode; // Prepare diff --git a/Source/Shaders/TAA.shader b/Source/Shaders/TAA.shader index de78707ac..509ca5d76 100644 --- a/Source/Shaders/TAA.shader +++ b/Source/Shaders/TAA.shader @@ -2,9 +2,86 @@ #include "./Flax/Common.hlsl" +META_CB_BEGIN(0, Data) +float2 ScreenSizeInv; +float2 JitterInv; +float Sharpness; +float StationaryBlending; +float MotionBlending; +float Dummy0; +META_CB_END + +Texture2D Input : register(t0); +Texture2D InputHistory : register(t1); +Texture2D MotionVectors : register(t2); +Texture2D Depth : register(t3); + +// [Pedersen, 2016, "Temporal Reprojection Anti-Aliasing in INSIDE"] +float4 ClipToAABB(float4 color, float4 minimum, float4 maximum) +{ + float4 center = (maximum + minimum) * 0.5; + float4 extents = (maximum - minimum) * 0.5; + float4 shift = color - center; + float4 absUnit = abs(shift / max(extents, 0.0001)); + float maxUnit = max(max(absUnit.x, absUnit.y), absUnit.z); + return maxUnit > 1.0 ? center + (shift / maxUnit) : color; +} + // Pixel Shader for Temporal Anti-Aliasing META_PS(true, FEATURE_LEVEL_ES2) float4 PS(Quad_VS2PS input) : SV_Target0 { - return float4(0, 0, 0, 0); + // Find the closest pixel in 3x3 neighborhood + float bestDepth = 1; + float2 bestUV = float2(0, 0); + float4 neighborhoodMin = 100000; + float4 neighborhoodMax = -10000; + float4 current; + float4 neighborhoodSum = 0; + for (int x = -1; x <= 1; ++x) + { + for (int y = -1; y <= 1; ++y) + { + float2 sampleUV = input.TexCoord + float2(x, y) * ScreenSizeInv; + + float4 neighbor = SAMPLE_RT_LINEAR(Input, sampleUV); + neighborhoodMin = min(neighborhoodMin, neighbor); + neighborhoodMax = max(neighborhoodMax, neighbor); + if (x == 0 && y == 0) + current = neighbor; + neighborhoodSum += neighbor; + + float depth = SAMPLE_RT(Depth, sampleUV).r; + if (depth < bestDepth) + { + bestDepth = depth; + bestUV = sampleUV; + } + } + } + float2 velocity = SAMPLE_RT_LINEAR(MotionVectors, bestUV).xy; + float velocityLength = length(velocity); + float2 prevUV = input.TexCoord + velocity; + + // Apply sharpening + float4 neighborhoodAvg = neighborhoodSum / 9.0; + current += (current - neighborhoodAvg) * Sharpness; + + // Sample history by clamp it to the nearby colros range to reduce artifacts + float4 history = SAMPLE_RT_LINEAR(InputHistory, prevUV); + float lumaOffset = abs(Luminance(neighborhoodAvg) - Luminance(current)); + float aabbMargin = lerp(4.0, 0.25, saturate(velocityLength * 100.0)) * lumaOffset; + history = ClipToAABB(history, neighborhoodMin - aabbMargin, neighborhoodMax + aabbMargin); + //history = clamp(history, neighborhoodMin, neighborhoodMax); + + // Calculate history blending factor + float motion = saturate(velocityLength * 600.0f); + float blendfactor = lerp(StationaryBlending, MotionBlending, motion); + blendfactor = any(abs(prevUV * 2 - 1) >= 1.0f) ? 0.0f : blendfactor; + + // Perform linear accumulation of the previous samples with a current one + float4 color = lerp(current, history, blendfactor); + color = clamp(color, 0, HDR_CLAMP_MAX); + + return color; }