Add **Contrast Adaptive Sharpening (CAS)** filter pass

#2423
This commit is contained in:
Muzz
2024-08-29 19:01:37 +02:00
committed by Wojtek Figat
parent 499ef51875
commit 09737023a1
8 changed files with 273 additions and 13 deletions

BIN
Content/Shaders/CAS.flax (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -198,6 +198,10 @@ void AntiAliasingSettings::BlendWith(AntiAliasingSettings& other, float weight)
BLEND_FLOAT(TAA_Sharpness);
BLEND_FLOAT(TAA_StationaryBlending);
BLEND_FLOAT(TAA_MotionBlending);
BLEND_FLOAT(CAS_SharpeningAmount);
BLEND_FLOAT(CAS_EdgeSharpening);
BLEND_FLOAT(CAS_MinEdgeThreshold);
BLEND_FLOAT(CAS_OverBlurLimit);
}
void PostFxMaterialsSettings::BlendWith(PostFxMaterialsSettings& other, float weight)

View File

@@ -1858,10 +1858,30 @@ API_ENUM(Attributes="Flags") enum class AntiAliasingSettingsOverride : int32
/// </summary>
TAA_MotionBlending = 1 << 4,
/// <summary>
/// Overrides <see cref="AntiAliasingSettings.CAS_SharpeningAmount"/> property.
/// </summary>
CAS_SharpeningAmount = 1 << 5,
/// <summary>
/// Overrides <see cref="AntiAliasingSettings.CAS_EdgeSharpening"/> property.
/// </summary>
CAS_EdgeSharpening = 1 << 6,
/// <summary>
/// Overrides <see cref="AntiAliasingSettings.CAS_MinEdgeThreshold"/> property.
/// </summary>
CAS_MinEdgeThreshold = 1 << 7,
/// <summary>
/// Overrides <see cref="AntiAliasingSettings.CAS_OverBlurLimit"/> property.
/// </summary>
CAS_OverBlurLimit = 1 << 8,
/// <summary>
/// All properties.
/// </summary>
All = Mode | TAA_JitterSpread | TAA_Sharpness | TAA_StationaryBlending | TAA_MotionBlending,
All = Mode | TAA_JitterSpread | TAA_Sharpness | TAA_StationaryBlending | TAA_MotionBlending | CAS_SharpeningAmount | CAS_EdgeSharpening | CAS_MinEdgeThreshold | CAS_OverBlurLimit,
};
/// <summary>
@@ -1909,6 +1929,30 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\"), VisibleIf(nameof(ShowTAASettings))")
float TAA_MotionBlending = 0.85f;
/// <summary>
/// The sharpening strength for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter.
/// </summary>
API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(10), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_SharpeningAmount), EditorDisplay(null, \"CAS Sharpening Amount\"), VisibleIf(nameof(ShowTAASettings), true)")
float CAS_SharpeningAmount = 0.0f;
/// <summary>
/// The edge sharpening strength for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter.
/// </summary>
API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(11), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_EdgeSharpening), EditorDisplay(null, \"CAS Edge Sharpening\"), VisibleIf(nameof(ShowTAASettings), true)")
float CAS_EdgeSharpening = 0.5f;
/// <summary>
/// The minimum edge threshold for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter.
/// </summary>
API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(12), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_MinEdgeThreshold), EditorDisplay(null, \"CAS Min Edge Threshold\"), VisibleIf(nameof(ShowTAASettings), true)")
float CAS_MinEdgeThreshold = 0.03f;
/// <summary>
/// The over-blur limit for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter.
/// </summary>
API_FIELD(Attributes = "Limit(0, 100f, 0.001f), EditorOrder(13), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_OverBlurLimit), EditorDisplay(null, \"CAS Over-blur Limit\"), VisibleIf(nameof(ShowTAASettings), true)")
float CAS_OverBlurLimit = 1.0f;
public:
/// <summary>
/// Blends the settings using given weight.

View File

@@ -65,10 +65,10 @@ void FXAA::Dispose()
void FXAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output)
{
auto context = GPUDevice::Instance->GetMainContext();
context->SetRenderTarget(output);
if (checkIfSkipPass())
{
// Resources are missing. Do not perform rendering, just copy input frame.
context->SetRenderTarget(output);
context->Draw(input);
return;
}
@@ -83,7 +83,6 @@ void FXAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureVie
context->BindSR(0, input);
// Render
context->SetRenderTarget(output);
const auto qualityLevel = Math::Clamp(static_cast<int32>(Graphics::AAQuality), 0, static_cast<int32>(Quality::MAX) - 1);
context->SetState(_psFXAA.Get(qualityLevel));
context->DrawFullscreenTriangle();

View File

@@ -0,0 +1,97 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "ContrastAdaptiveSharpeningPass.h"
#include "RenderList.h"
#include "Engine/Content/Assets/Shader.h"
#include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
GPU_CB_STRUCT(Data {
Float2 InputSizeInv;
Float2 Padding;
float SharpeningAmount;
float EdgeSharpening;
float MinEdgeThreshold;
float OverBlurLimit;
});
String ContrastAdaptiveSharpeningPass::ToString() const
{
return TEXT("ContrastAdaptiveSharpening");
}
void ContrastAdaptiveSharpeningPass::Dispose()
{
RendererPass::Dispose();
SAFE_DELETE_GPU_RESOURCE(_psCAS);
_shader = nullptr;
}
bool ContrastAdaptiveSharpeningPass::setupResources()
{
// Lazy-load shader
if (!_shader)
{
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/CAS"));
if (!_shader)
return false;
#if COMPILE_WITH_DEV_ENV
_shader.Get()->OnReloading.Bind<ContrastAdaptiveSharpeningPass, &ContrastAdaptiveSharpeningPass::OnShaderReloading>(this);
#endif
}
if (!_shader->IsLoaded())
return true;
const auto shader = _shader->GetShader();
// Validate shader constant buffer size
if (shader->GetCB(0)->GetSize() != sizeof(Data))
{
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data);
return true;
}
// Create pipeline stage
auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
psDesc.PS = shader->GetPS("PS_CAS");
_psCAS = GPUDevice::Instance->CreatePipelineState();
if (_psCAS->Init(psDesc))
return true;
return false;
}
bool ContrastAdaptiveSharpeningPass::CanRender(const RenderContext& renderContext)
{
const AntiAliasingSettings& antiAliasing = renderContext.List->Settings.AntiAliasing;
return EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::AntiAliasing) &&
antiAliasing.CAS_SharpeningAmount > ZeroTolerance &&
!checkIfSkipPass();
}
void ContrastAdaptiveSharpeningPass::Render(const RenderContext& renderContext, GPUTexture* input, GPUTextureView* output)
{
ASSERT_LOW_LAYER(CanRender(renderContext));
PROFILE_GPU_CPU("Contrast Adaptive Sharpening");
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
const AntiAliasingSettings& antiAliasing = renderContext.List->Settings.AntiAliasing;
Data data;
data.InputSizeInv = Float2::One / input->Size();
data.SharpeningAmount = antiAliasing.CAS_SharpeningAmount;
data.EdgeSharpening = antiAliasing.CAS_EdgeSharpening;
data.MinEdgeThreshold = antiAliasing.CAS_MinEdgeThreshold;
data.OverBlurLimit = antiAliasing.CAS_OverBlurLimit;
const auto cb = _shader->GetShader()->GetCB(0);
context->UpdateCB(cb, &data);
context->BindCB(0, cb);
context->BindSR(0, input);
context->SetState(_psCAS);
context->SetRenderTarget(output);
context->DrawFullscreenTriangle();
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "RendererPass.h"
#include "Engine/Graphics/PostProcessSettings.h"
/// <summary>
/// Contrast Adaptive Sharpening (CAS) provides a mixed ability to sharpen and optionally scale an image. Based on AMD FidelityFX implementation.
/// </summary>
class ContrastAdaptiveSharpeningPass : public RendererPass<ContrastAdaptiveSharpeningPass>
{
private:
bool _loadShader = true;
AssetReference<Shader> _shader;
GPUPipelineState* _psCAS = nullptr;
public:
bool CanRender(const RenderContext& renderContext);
void Render(const RenderContext& renderContext, GPUTexture* input, GPUTextureView* output);
private:
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj)
{
_psCAS->ReleaseGPU();
invalidateResources();
}
#endif
public:
// [RendererPass]
String ToString() const override;
void Dispose() override;
protected:
// [RendererPass]
bool setupResources() override;
};

View File

@@ -22,6 +22,7 @@
#include "VolumetricFogPass.h"
#include "HistogramPass.h"
#include "AtmospherePreCompute.h"
#include "ContrastAdaptiveSharpeningPass.h"
#include "GlobalSignDistanceFieldPass.h"
#include "GI/GlobalSurfaceAtlasPass.h"
#include "GI/DynamicDiffuseGlobalIllumination.h"
@@ -126,21 +127,47 @@ void RendererService::Dispose()
void RenderAntiAliasingPass(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output, const Viewport& outputViewport)
{
auto context = GPUDevice::Instance->GetMainContext();
context->SetViewportAndScissors(outputViewport);
const auto aaMode = renderContext.List->Settings.AntiAliasing.Mode;
if (aaMode == AntialiasingMode::FastApproximateAntialiasing)
if (ContrastAdaptiveSharpeningPass::Instance()->CanRender(renderContext))
{
FXAA::Instance()->Render(renderContext, input, output);
}
else if (aaMode == AntialiasingMode::SubpixelMorphologicalAntialiasing)
{
SMAA::Instance()->Render(renderContext, input, output);
if (aaMode == AntialiasingMode::FastApproximateAntialiasing ||
aaMode == AntialiasingMode::SubpixelMorphologicalAntialiasing)
{
// AA -> CAS -> Output
auto tmpImage = RenderTargetPool::Get(input->GetDescription());
RENDER_TARGET_POOL_SET_NAME(tmpImage, "TmpImage");
context->SetViewportAndScissors((float)input->Width(), (float)input->Height());
if (aaMode == AntialiasingMode::FastApproximateAntialiasing)
FXAA::Instance()->Render(renderContext, input, tmpImage->View());
else
SMAA::Instance()->Render(renderContext, input, tmpImage->View());
context->ResetSR();
context->ResetRenderTarget();
context->SetViewportAndScissors(outputViewport);
ContrastAdaptiveSharpeningPass::Instance()->Render(renderContext, tmpImage, output);
RenderTargetPool::Release(tmpImage);
}
else
{
// CAS -> Output
context->SetViewportAndScissors(outputViewport);
ContrastAdaptiveSharpeningPass::Instance()->Render(renderContext, input, output);
}
}
else
{
PROFILE_GPU("Copy frame");
context->SetRenderTarget(output);
context->Draw(input);
// AA -> Output
context->SetViewportAndScissors(outputViewport);
if (aaMode == AntialiasingMode::FastApproximateAntialiasing)
FXAA::Instance()->Render(renderContext, input, output);
else if (aaMode == AntialiasingMode::SubpixelMorphologicalAntialiasing)
SMAA::Instance()->Render(renderContext, input, output);
else
{
PROFILE_GPU("Copy frame");
context->SetRenderTarget(output);
context->Draw(input);
}
}
}

47
Source/Shaders/CAS.shader Normal file
View File

@@ -0,0 +1,47 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "./Flax/Common.hlsl"
META_CB_BEGIN(0, Data)
float2 InputSizeInv;
float2 Padding;
float SharpeningAmount;
float EdgeSharpening;
float MinEdgeThreshold;
float OverBlurLimit;
META_CB_END
Texture2D Input : register(t0);
// Pixel Shader for Contrast Adaptive Sharpening (CAS) filter. Based on AMD FidelityFX implementation.
META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_CAS(Quad_VS2PS input) : SV_Target0
{
// Sample the color texture
float4 color = Input.SampleLevel(SamplerLinearClamp, input.TexCoord, 0);
// Sample neighboring pixels
float3 blurred = color.rgb;
float3 edges = 0.0;
for (int x = -2; x <= 2; x++)
{
for (int y = -2; y <= 2; y++)
{
float2 uv = float2(x, y) * InputSizeInv + input.TexCoord;
float3 neighbor = Input.SampleLevel(SamplerLinearClamp, uv, 0).rgb;
blurred += neighbor;
edges += abs(neighbor - color.rgb);
}
}
blurred /= 25.0;
edges /= 25.0;
// Sharpen based on edge detection
float edgeAmount = saturate((dot(edges, edges) - MinEdgeThreshold) / (0.001 + dot(edges, edges)));
float sharpen = (1.0 - edgeAmount) * SharpeningAmount + edgeAmount * EdgeSharpening;
float3 sharpened = color.rgb + (color.rgb - blurred) * sharpen;
// Limit sharpening to avoid over-blurring
sharpened = lerp(color.rgb, sharpened, saturate(OverBlurLimit / (OverBlurLimit + dot(abs(sharpened - color.rgb), float3(1.0, 1.0, 1.0)))));
return float4(sharpened, color.a);
}