Optimize Screen Space Reflections tracing with Hierarchical Z-Buffer

Improve SSR resolve filter quality and adjust scalability.
This commit is contained in:
2026-01-26 15:22:39 +01:00
parent da8376bba1
commit 143d714037
15 changed files with 482 additions and 145 deletions
@@ -116,7 +116,7 @@ void PS_Forward(
Texture2D sceneColorTexture = MATERIAL_REFLECTIONS_SSR_COLOR; Texture2D sceneColorTexture = MATERIAL_REFLECTIONS_SSR_COLOR;
float2 screenUV = materialInput.SvPosition.xy * ScreenSize.zw; float2 screenUV = materialInput.SvPosition.xy * ScreenSize.zw;
float stepSize = ScreenSize.z; // 1 / screenWidth float stepSize = ScreenSize.z; // 1 / screenWidth
float maxSamples = 48; float maxSamples = 50;
float worldAntiSelfOcclusionBias = 0.1f; float worldAntiSelfOcclusionBias = 0.1f;
float brdfBias = 0.82f; float brdfBias = 0.82f;
float drawDistance = 5000.0f; float drawDistance = 5000.0f;
+38
View File
@@ -64,6 +64,7 @@ void RenderBuffers::ReleaseUnusedMemory()
UPDATE_LAZY_KEEP_RT(TemporalSSR); UPDATE_LAZY_KEEP_RT(TemporalSSR);
UPDATE_LAZY_KEEP_RT(TemporalAA); UPDATE_LAZY_KEEP_RT(TemporalAA);
UPDATE_LAZY_KEEP_RT(HalfResDepth); UPDATE_LAZY_KEEP_RT(HalfResDepth);
UPDATE_LAZY_KEEP_RT(HiZ);
UPDATE_LAZY_KEEP_RT(LuminanceMap); UPDATE_LAZY_KEEP_RT(LuminanceMap);
#undef UPDATE_LAZY_KEEP_RT #undef UPDATE_LAZY_KEEP_RT
for (int32 i = CustomBuffers.Count() - 1; i >= 0; i--) for (int32 i = CustomBuffers.Count() - 1; i >= 0; i--)
@@ -112,6 +113,42 @@ GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context)
return HalfResDepth; return HalfResDepth;
} }
GPUTexture* RenderBuffers::RequestHiZ(GPUContext* context, bool fullRes, int32 mipLevels)
{
// Skip if already done in the current frame
const auto currentFrame = Engine::FrameCount;
if (LastFrameHiZ == currentFrame)
return HiZ;
LastFrameHiZ = currentFrame;
// Allocate or resize buffer (with full mip-chain)
// TODO: migrate to inverse depth and try using r16 again as default (should have no artifacts anymore)
auto format = PLATFORM_ANDROID || PLATFORM_IOS || PLATFORM_SWITCH ? PixelFormat::R16_UInt : PixelFormat::R32_Float;
auto width = fullRes ? _width : Math::Max(_width >> 1, 1);
auto height = fullRes ? _height : Math::Max(_height >> 1, 1);
auto desc = GPUTextureDescription::New2D(width, height, mipLevels, format, GPUTextureFlags::ShaderResource);
bool useCompute = false; // TODO: impl Compute Shader for downscaling depth to HiZ with a single dispatch (eg. FidelityFX Single Pass Downsampler)
if (useCompute)
desc.Flags |= GPUTextureFlags::UnorderedAccess;
else
desc.Flags |= GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews;
if (HiZ && HiZ->GetDescription() != desc)
{
RenderTargetPool::Release(HiZ);
HiZ = nullptr;
}
if (HiZ == nullptr)
{
HiZ = RenderTargetPool::Get(desc);
RENDER_TARGET_POOL_SET_NAME(HiZ, "HiZ");
}
// Downscale
MultiScaler::Instance()->BuildHiZ(context, DepthBuffer, HiZ);
return HiZ;
}
PixelFormat RenderBuffers::GetOutputFormat() const PixelFormat RenderBuffers::GetOutputFormat() const
{ {
auto colorFormat = GraphicsSettings::Get()->RenderColorFormat; auto colorFormat = GraphicsSettings::Get()->RenderColorFormat;
@@ -244,6 +281,7 @@ void RenderBuffers::Release()
UPDATE_LAZY_KEEP_RT(TemporalSSR); UPDATE_LAZY_KEEP_RT(TemporalSSR);
UPDATE_LAZY_KEEP_RT(TemporalAA); UPDATE_LAZY_KEEP_RT(TemporalAA);
UPDATE_LAZY_KEEP_RT(HalfResDepth); UPDATE_LAZY_KEEP_RT(HalfResDepth);
UPDATE_LAZY_KEEP_RT(HiZ);
UPDATE_LAZY_KEEP_RT(LuminanceMap); UPDATE_LAZY_KEEP_RT(LuminanceMap);
#undef UPDATE_LAZY_KEEP_RT #undef UPDATE_LAZY_KEEP_RT
CustomBuffers.ClearDelete(); CustomBuffers.ClearDelete();
+15 -5
View File
@@ -43,6 +43,12 @@ API_CLASS() class FLAXENGINE_API RenderBuffers : public ScriptingObject
String ToString() const override; String ToString() const override;
}; };
private:
GPUTexture* HalfResDepth = nullptr;
GPUTexture* HiZ = nullptr;
uint64 LastFrameHalfResDepth = 0;
uint64 LastFrameHiZ = 0;
protected: protected:
int32 _width = 0; int32 _width = 0;
int32 _height = 0; int32 _height = 0;
@@ -85,11 +91,6 @@ public:
float MaxDistance; float MaxDistance;
} VolumetricFogData; } VolumetricFogData;
// Helper buffer with half-resolution depth buffer shared by effects (eg. SSR, Motion Blur). Valid only during frame rendering and on request (see RequestHalfResDepth).
// Should be released if not used for a few frames.
GPUTexture* HalfResDepth = nullptr;
uint64 LastFrameHalfResDepth = 0;
// Helper target for the temporal SSR. // Helper target for the temporal SSR.
// Should be released if not used for a few frames. // Should be released if not used for a few frames.
GPUTexture* TemporalSSR = nullptr; GPUTexture* TemporalSSR = nullptr;
@@ -122,6 +123,15 @@ public:
/// <returns>The half-res depth buffer.</returns> /// <returns>The half-res depth buffer.</returns>
GPUTexture* RequestHalfResDepth(GPUContext* context); GPUTexture* RequestHalfResDepth(GPUContext* context);
/// <summary>
/// Requests the Hierarchical Z-Buffer (closest) to be prepared for the current frame.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="fullRes">Generates the full-resolution buffer, otherwise HiZ starts at half-res of the original Depth Buffer.</param>
/// <param name="mipLevels">Maximum amount of mip levels to generate. Value 0 generates a full mip chain down to 1x1.</param>
/// <returns>The HiZ depth buffer.</returns>
GPUTexture* RequestHiZ(GPUContext* context, bool fullRes = false, int32 mipLevels = 0);
public: public:
/// <summary> /// <summary>
/// Gets the buffers width (in pixels). /// Gets the buffers width (in pixels).
@@ -411,6 +411,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
else else
{ {
// Combine reflections and light buffer (additive mode) // Combine reflections and light buffer (additive mode)
PROFILE_GPU("Combine");
if (_depthBounds) if (_depthBounds)
{ {
context->SetRenderTarget(depthBufferRTV, lightBuffer); context->SetRenderTarget(depthBufferRTV, lightBuffer);
@@ -22,19 +22,21 @@
#define TEXTURE1 5 #define TEXTURE1 5
#define TEXTURE2 6 #define TEXTURE2 6
#define SSR_USE_HZB 1
GPU_CB_STRUCT(Data { GPU_CB_STRUCT(Data {
ShaderGBufferData GBuffer; ShaderGBufferData GBuffer;
float MaxColorMiplevel; float MaxColorMiplevel;
float TraceSizeMax; float TraceSizeMax;
float MaxTraceSamples; float MaxTraceSamples;
float RoughnessFade; float RoughnessFade;
Float2 SSRtexelSize; Float2 SSRTexelSize;
float TemporalTime; float TemporalTime;
float BRDFBias; float BRDFBias;
float WorldAntiSelfOcclusionBias; float WorldAntiSelfOcclusionBias;
float EdgeFadeFactor; float EdgeFadeFactor;
float TemporalResponse; float TemporalResponse;
float Dummy0; uint32 DepthMips;
float RayTraceStep; float RayTraceStep;
float TemporalEffect; float TemporalEffect;
float Intensity; float Intensity;
@@ -157,10 +159,10 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
const auto colorBufferMips = MipLevelsCount(colorBufferWidth, colorBufferHeight); const auto colorBufferMips = MipLevelsCount(colorBufferWidth, colorBufferHeight);
// Prepare buffers // Prepare buffers
auto tempDesc = GPUTextureDescription::New2D(colorBufferWidth, colorBufferHeight, 0, PixelFormat::R11G11B10_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews);
GPUTexture* colorBuffer0, *colorBuffer1; GPUTexture* colorBuffer0, *colorBuffer1;
if (settings.UseColorBufferMips) if (settings.UseColorBufferMips)
{ {
auto tempDesc = GPUTextureDescription::New2D(colorBufferWidth, colorBufferHeight, 0, PixelFormat::R11G11B10_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews);
colorBuffer0 = RenderTargetPool::Get(tempDesc); colorBuffer0 = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(colorBuffer0, "SSR.ColorBuffer0"); RENDER_TARGET_POOL_SET_NAME(colorBuffer0, "SSR.ColorBuffer0");
// TODO: maybe allocate colorBuffer1 smaller because mip0 is not used (the same as PostProcessingPass for Bloom), keep in sync to use the same buffer in frame // TODO: maybe allocate colorBuffer1 smaller because mip0 is not used (the same as PostProcessingPass for Bloom), keep in sync to use the same buffer in frame
@@ -170,27 +172,33 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
else else
{ {
// Single mip // Single mip
tempDesc.MipLevels = 1; auto tempDesc = GPUTextureDescription::New2D(colorBufferWidth, colorBufferHeight, 1, PixelFormat::R11G11B10_Float);
tempDesc.Flags &= ~GPUTextureFlags::PerMipViews;
colorBuffer0 = RenderTargetPool::Get(tempDesc); colorBuffer0 = RenderTargetPool::Get(tempDesc);
colorBuffer1 = nullptr; colorBuffer1 = nullptr;
} }
tempDesc = GPUTextureDescription::New2D(traceWidth, traceHeight, PixelFormat::R16G16B16A16_Float); GPUTexture* traceBuffer, *resolveBuffer;
auto traceBuffer = RenderTargetPool::Get(tempDesc); {
RENDER_TARGET_POOL_SET_NAME(traceBuffer, "SSR.TraceBuffer"); auto tempDesc = GPUTextureDescription::New2D(traceWidth, traceHeight, PixelFormat::R16G16B16A16_Float);
tempDesc = GPUTextureDescription::New2D(resolveWidth, resolveHeight, PixelFormat::R16G16B16A16_Float); traceBuffer = RenderTargetPool::Get(tempDesc);
auto resolveBuffer = RenderTargetPool::Get(tempDesc); RENDER_TARGET_POOL_SET_NAME(traceBuffer, "SSR.TraceBuffer");
RENDER_TARGET_POOL_SET_NAME(resolveBuffer, "SSR.ResolveBuffer"); tempDesc.Width = resolveWidth;
tempDesc.Height = resolveHeight;
resolveBuffer = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(resolveBuffer, "SSR.ResolveBuffer");
}
// Pick effect settings // Pick effect settings
int32 maxTraceSamples = 60; int32 maxTraceSamples = 60;
int32 resolveSamples = settings.ResolveSamples;
switch (Graphics::SSRQuality) switch (Graphics::SSRQuality)
{ {
case Quality::Low: case Quality::Low:
maxTraceSamples = 20; maxTraceSamples = 40;
resolveSamples = Math::Min(resolveSamples, 2);
break; break;
case Quality::Medium: case Quality::Medium:
maxTraceSamples = 55; maxTraceSamples = 55;
resolveSamples = Math::Min(resolveSamples, 4);
break; break;
case Quality::High: case Quality::High:
maxTraceSamples = 70; maxTraceSamples = 70;
@@ -199,7 +207,6 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
maxTraceSamples = 120; maxTraceSamples = 120;
break; break;
} }
const int32 resolveSamples = settings.ResolveSamples;
int32 resolvePassIndex = 0; int32 resolvePassIndex = 0;
if (resolveSamples >= 8) if (resolveSamples >= 8)
resolvePassIndex = 3; resolvePassIndex = 3;
@@ -214,12 +221,12 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
data.RoughnessFade = Math::Saturate(settings.RoughnessThreshold); data.RoughnessFade = Math::Saturate(settings.RoughnessThreshold);
data.MaxTraceSamples = static_cast<float>(maxTraceSamples); data.MaxTraceSamples = static_cast<float>(maxTraceSamples);
data.BRDFBias = settings.BRDFBias; data.BRDFBias = settings.BRDFBias;
data.WorldAntiSelfOcclusionBias = settings.WorldAntiSelfOcclusionBias; data.WorldAntiSelfOcclusionBias = settings.WorldAntiSelfOcclusionBias * (int32)settings.DepthResolution;
data.EdgeFadeFactor = settings.EdgeFadeFactor; data.EdgeFadeFactor = settings.EdgeFadeFactor;
data.SSRtexelSize = Float2(1.0f / (float)traceWidth, 1.0f / (float)traceHeight); data.SSRTexelSize = Float2(1.0f / (float)traceWidth, 1.0f / (float)traceHeight);
data.TraceSizeMax = (float)Math::Max(traceWidth, traceHeight); data.TraceSizeMax = (float)Math::Max(traceWidth, traceHeight);
data.MaxColorMiplevel = settings.UseColorBufferMips ? (float)colorBufferMips - 2.0f : 0.0f; data.MaxColorMiplevel = settings.UseColorBufferMips ? (float)(colorBufferMips - 2) : 0.0f;
data.RayTraceStep = static_cast<float>(settings.DepthResolution) / (float)width; data.RayTraceStep = (float)settings.DepthResolution / (float)width;
data.Intensity = settings.Intensity; data.Intensity = settings.Intensity;
data.FadeOutDistance = Math::Max(settings.FadeOutDistance, 100.0f); data.FadeOutDistance = Math::Max(settings.FadeOutDistance, 100.0f);
data.TemporalResponse = settings.TemporalResponse; data.TemporalResponse = settings.TemporalResponse;
@@ -245,8 +252,16 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
} }
} }
// Check if resize depth // Prepare depth buffer
#if SSR_USE_HZB
int32 hzbMips = settings.DepthResolution == ResolutionMode::Full ? 5 : 4; // Using lower mips in tracing introduces blocky artifacts
bool hzbFullRes = settings.DepthResolution == ResolutionMode::Full;
GPUTexture* depthBufferTrace = buffers->RequestHiZ(context, hzbFullRes, hzbMips);
data.DepthMips = hzbMips - 1; // Offset to improve SSR range
#else
GPUTexture* depthBufferTrace = settings.DepthResolution == ResolutionMode::Half ? buffers->RequestHalfResDepth(context) : buffers->DepthBuffer; GPUTexture* depthBufferTrace = settings.DepthResolution == ResolutionMode::Half ? buffers->RequestHalfResDepth(context) : buffers->DepthBuffer;
data.DepthMips = 1;
#endif
// Prepare constants // Prepare constants
context->UpdateCB(cb, &data); context->UpdateCB(cb, &data);
@@ -259,16 +274,19 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
context->BindSR(3, depthBufferTrace); context->BindSR(3, depthBufferTrace);
// Combine pass // Combine pass
context->BindSR(TEXTURE0, lightBuffer); {
context->BindSR(TEXTURE1, reflectionsRT); PROFILE_GPU("Combine");
context->BindSR(TEXTURE2, _preIntegratedGF->GetTexture()); context->BindSR(TEXTURE0, lightBuffer);
context->SetViewportAndScissors((float)colorBufferWidth, (float)colorBufferHeight); context->BindSR(TEXTURE1, reflectionsRT);
context->SetRenderTarget(colorBuffer0->View(0)); context->BindSR(TEXTURE2, _preIntegratedGF->GetTexture());
context->SetState(_psCombinePass); context->SetViewportAndScissors((float)colorBufferWidth, (float)colorBufferHeight);
context->DrawFullscreenTriangle(); context->SetRenderTarget(colorBuffer0->View(0));
context->UnBindSR(TEXTURE1); context->SetState(_psCombinePass);
context->UnBindSR(TEXTURE2); context->DrawFullscreenTriangle();
context->ResetRenderTarget(); context->UnBindSR(TEXTURE1);
context->UnBindSR(TEXTURE2);
context->ResetRenderTarget();
}
// Blur Pass // Blur Pass
if (settings.UseColorBufferMips) if (settings.UseColorBufferMips)
@@ -298,37 +316,44 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
RenderTargetPool::Release(colorBuffer1); RenderTargetPool::Release(colorBuffer1);
// Ray Trace Pass // Ray Trace Pass
context->SetViewportAndScissors((float)traceWidth, (float)traceHeight);
context->SetRenderTarget(*traceBuffer);
context->BindSR(TEXTURE0, colorBuffer0->View());
if (useGlobalSurfaceAtlas)
{ {
context->BindSR(7, bindingDataSDF.Texture ? bindingDataSDF.Texture->ViewVolume() : nullptr); PROFILE_GPU("RayTrace");
context->BindSR(8, bindingDataSDF.TextureMip ? bindingDataSDF.TextureMip->ViewVolume() : nullptr); context->SetViewportAndScissors((float)traceWidth, (float)traceHeight);
context->BindSR(9, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr); context->SetRenderTarget(*traceBuffer);
context->BindSR(10, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr); context->BindSR(TEXTURE0, colorBuffer0->View());
context->BindSR(11, bindingDataSurfaceAtlas.Objects ? bindingDataSurfaceAtlas.Objects->View() : nullptr); if (useGlobalSurfaceAtlas)
context->BindSR(12, bindingDataSurfaceAtlas.AtlasDepth->View()); {
context->BindSR(13, bindingDataSurfaceAtlas.AtlasLighting->View()); context->BindSR(7, bindingDataSDF.Texture ? bindingDataSDF.Texture->ViewVolume() : nullptr);
context->BindSR(8, bindingDataSDF.TextureMip ? bindingDataSDF.TextureMip->ViewVolume() : nullptr);
context->BindSR(9, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr);
context->BindSR(10, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr);
context->BindSR(11, bindingDataSurfaceAtlas.Objects ? bindingDataSurfaceAtlas.Objects->View() : nullptr);
context->BindSR(12, bindingDataSurfaceAtlas.AtlasDepth->View());
context->BindSR(13, bindingDataSurfaceAtlas.AtlasLighting->View());
}
context->SetState(_psRayTracePass.Get(useGlobalSurfaceAtlas ? 1 : 0));
context->DrawFullscreenTriangle();
context->ResetRenderTarget();
RenderTargetPool::Release(colorBuffer0);
} }
context->SetState(_psRayTracePass.Get(useGlobalSurfaceAtlas ? 1 : 0));
context->DrawFullscreenTriangle();
context->ResetRenderTarget();
RenderTargetPool::Release(colorBuffer0);
// Resolve Pass // Resolve Pass
context->SetViewportAndScissors((float)resolveWidth, (float)resolveHeight); {
context->SetRenderTarget(resolveBuffer->View()); PROFILE_GPU("Resolve");
context->BindSR(TEXTURE0, traceBuffer->View()); context->SetViewportAndScissors((float)resolveWidth, (float)resolveHeight);
context->SetState(_psResolvePass.Get(resolvePassIndex)); context->SetRenderTarget(resolveBuffer->View());
context->DrawFullscreenTriangle(); context->BindSR(TEXTURE0, traceBuffer->View());
context->ResetRenderTarget(); context->SetState(_psResolvePass.Get(resolvePassIndex));
RenderTargetPool::Release(traceBuffer); context->DrawFullscreenTriangle();
context->ResetRenderTarget();
RenderTargetPool::Release(traceBuffer);
}
// Temporal Pass // Temporal Pass
GPUTexture* reflectionsBuffer = resolveBuffer; GPUTexture* reflectionsBuffer = resolveBuffer;
if (useTemporal) if (useTemporal)
{ {
PROFILE_GPU("Temporal");
buffers->LastFrameTemporalSSR = Engine::FrameCount; buffers->LastFrameTemporalSSR = Engine::FrameCount;
bool resetHistory = false; bool resetHistory = false;
if (!buffers->TemporalSSR || buffers->TemporalSSR->Width() != resolveWidth || buffers->TemporalSSR->Height() != resolveHeight) if (!buffers->TemporalSSR || buffers->TemporalSSR->Width() != resolveWidth || buffers->TemporalSSR->Height() != resolveHeight)
@@ -336,7 +361,7 @@ GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPU
resetHistory = true; resetHistory = true;
if (buffers->TemporalSSR) if (buffers->TemporalSSR)
RenderTargetPool::Release(buffers->TemporalSSR); RenderTargetPool::Release(buffers->TemporalSSR);
tempDesc = GPUTextureDescription::New2D(resolveWidth, resolveHeight, PixelFormat::R16G16B16A16_Float); auto tempDesc = GPUTextureDescription::New2D(resolveWidth, resolveHeight, PixelFormat::R16G16B16A16_Float);
buffers->TemporalSSR = RenderTargetPool::Get(tempDesc); buffers->TemporalSSR = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(buffers->TemporalSSR, "SSR.TemporalSSR"); RENDER_TARGET_POOL_SET_NAME(buffers->TemporalSSR, "SSR.TemporalSSR");
} }
+60 -16
View File
@@ -2,8 +2,8 @@
#include "MultiScaler.h" #include "MultiScaler.h"
#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUContext.h"
#include "Engine/Content/Content.h"
GPU_CB_STRUCT(Data { GPU_CB_STRUCT(Data {
Float2 TexelSize; Float2 TexelSize;
@@ -18,10 +18,10 @@ String MultiScaler::ToString() const
bool MultiScaler::Init() bool MultiScaler::Init()
{ {
// Create pipeline states // Create pipeline states
_psHalfDepth = GPUDevice::Instance->CreatePipelineState();
_psBlur5.CreatePipelineStates(); _psBlur5.CreatePipelineStates();
_psBlur9.CreatePipelineStates(); _psBlur9.CreatePipelineStates();
_psBlur13.CreatePipelineStates(); _psBlur13.CreatePipelineStates();
_psHalfDepth.CreatePipelineStates();
_psUpscale = GPUDevice::Instance->CreatePipelineState(); _psUpscale = GPUDevice::Instance->CreatePipelineState();
// Load asset // Load asset
@@ -66,13 +66,20 @@ bool MultiScaler::setupResources()
if (_psUpscale->Init(psDesc)) if (_psUpscale->Init(psDesc))
return true; return true;
} }
if (!_psHalfDepth->IsValid()) if (!_psHalfDepth.IsValid())
{ {
psDesc.PS = shader->GetPS("PS_HalfDepth"); psDesc.PS = shader->GetPS("PS_HalfDepth", 0);
if (_psHalfDepth[0]->Init(psDesc))
return true;
psDesc.PS = shader->GetPS("PS_HalfDepth", 2);
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::Red;
if (_psHalfDepth[2]->Init(psDesc))
return true;
psDesc.PS = shader->GetPS("PS_HalfDepth", 1);
psDesc.DepthWriteEnable = true; psDesc.DepthWriteEnable = true;
psDesc.DepthEnable = true; psDesc.DepthEnable = true;
psDesc.DepthFunc = ComparisonFunc::Always; psDesc.DepthFunc = ComparisonFunc::Always;
if (_psHalfDepth->Init(psDesc)) if (_psHalfDepth[1]->Init(psDesc))
return true; return true;
} }
@@ -85,15 +92,15 @@ void MultiScaler::Dispose()
RendererPass::Dispose(); RendererPass::Dispose();
// Cleanup // Cleanup
SAFE_DELETE_GPU_RESOURCE(_psHalfDepth);
SAFE_DELETE_GPU_RESOURCE(_psUpscale); SAFE_DELETE_GPU_RESOURCE(_psUpscale);
_psBlur5.Delete(); _psBlur5.Delete();
_psBlur9.Delete(); _psBlur9.Delete();
_psBlur13.Delete(); _psBlur13.Delete();
_psHalfDepth.Delete();
_shader = nullptr; _shader = nullptr;
} }
void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* src, GPUTextureView* dst, GPUTextureView* tmp) void MultiScaler::Filter(FilterMode mode, GPUContext* context, int32 width, int32 height, GPUTextureView* src, GPUTextureView* dst, GPUTextureView* tmp)
{ {
PROFILE_GPU_CPU("MultiScaler Filter"); PROFILE_GPU_CPU("MultiScaler Filter");
@@ -152,18 +159,14 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32
context->ResetRenderTarget(); context->ResetRenderTarget();
} }
void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* srcDst, GPUTextureView* tmp) void MultiScaler::Filter(FilterMode mode, GPUContext* context, int32 width, int32 height, GPUTextureView* srcDst, GPUTextureView* tmp)
{ {
PROFILE_GPU_CPU("MultiScaler Filter"); PROFILE_GPU_CPU("MultiScaler Filter");
context->SetViewportAndScissors((float)width, (float)height); context->SetViewportAndScissors((float)width, (float)height);
// Check if has missing resources
if (checkIfSkipPass()) if (checkIfSkipPass())
{
// Skip
return; return;
}
// Select filter // Select filter
GPUPipelineStatePermutationsPs<2>* ps; GPUPipelineStatePermutationsPs<2>* ps;
@@ -211,11 +214,8 @@ void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32
void MultiScaler::DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstHeight, GPUTexture* src, GPUTextureView* dst) void MultiScaler::DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstHeight, GPUTexture* src, GPUTextureView* dst)
{ {
PROFILE_GPU_CPU("Downscale Depth"); PROFILE_GPU_CPU("Downscale Depth");
// Check if has missing resources
if (checkIfSkipPass()) if (checkIfSkipPass())
{ {
// Clear the output
context->ClearDepth(dst); context->ClearDepth(dst);
return; return;
} }
@@ -224,6 +224,7 @@ void MultiScaler::DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstH
Data data; Data data;
data.TexelSize.X = 1.0f / (float)src->Width(); data.TexelSize.X = 1.0f / (float)src->Width();
data.TexelSize.Y = 1.0f / (float)src->Height(); data.TexelSize.Y = 1.0f / (float)src->Height();
bool outputDepth = ((GPUTexture*)dst->GetParent())->IsDepthStencil();
auto cb = _shader->GetShader()->GetCB(0); auto cb = _shader->GetShader()->GetCB(0);
context->UpdateCB(cb, &data); context->UpdateCB(cb, &data);
context->BindCB(0, cb); context->BindCB(0, cb);
@@ -232,7 +233,7 @@ void MultiScaler::DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstH
context->SetViewportAndScissors((float)dstWidth, (float)dstHeight); context->SetViewportAndScissors((float)dstWidth, (float)dstHeight);
context->SetRenderTarget(dst, (GPUTextureView*)nullptr); context->SetRenderTarget(dst, (GPUTextureView*)nullptr);
context->BindSR(0, src); context->BindSR(0, src);
context->SetState(_psHalfDepth); context->SetState(_psHalfDepth[outputDepth ? 1 : 0]);
context->DrawFullscreenTriangle(); context->DrawFullscreenTriangle();
// Cleanup // Cleanup
@@ -240,6 +241,49 @@ void MultiScaler::DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstH
context->UnBindCB(0); context->UnBindCB(0);
} }
void MultiScaler::BuildHiZ(GPUContext* context, GPUTexture* srcDepth, GPUTexture* dstHiZ)
{
PROFILE_GPU_CPU("Build HiZ");
int32 dstWidth = dstHiZ->Width();
int32 dstHeight = dstHiZ->Height();
// Copy mip0
if (srcDepth->Size() == dstHiZ->Size() && srcDepth->Format() == dstHiZ->Format())
{
context->CopySubresource(dstHiZ, 0, srcDepth, 0);
}
else if (srcDepth->Size() == dstHiZ->Size())
{
context->Draw(dstHiZ, srcDepth);
}
else
{
context->SetViewportAndScissors((float)dstWidth, (float)dstHeight);
context->SetRenderTarget(dstHiZ->View());
context->BindSR(0, srcDepth);
context->SetState(_psHalfDepth[2]);
context->DrawFullscreenTriangle();
}
// Build mip chain
for (int32 mip = 1; mip < dstHiZ->MipLevels(); mip++)
{
const int32 mipWidth = Math::Max(dstWidth >> mip, 1);
const int32 mipHeight = Math::Max(dstHeight >> mip, 1);
context->ResetRenderTarget();
context->SetViewportAndScissors((float)mipWidth, (float)mipHeight);
context->SetRenderTarget(dstHiZ->View(0, mip));
context->BindSR(0, dstHiZ->View(0, mip - 1));
context->SetState(_psHalfDepth[2]);
context->DrawFullscreenTriangle();
}
context->ResetRenderTarget();
context->UnBindCB(0);
}
void MultiScaler::Upscale(GPUContext* context, const Viewport& viewport, GPUTexture* src, GPUTextureView* dst) void MultiScaler::Upscale(GPUContext* context, const Viewport& viewport, GPUTexture* src, GPUTextureView* dst)
{ {
PROFILE_GPU_CPU("Upscale"); PROFILE_GPU_CPU("Upscale");
+15 -11
View File
@@ -12,16 +12,14 @@
class MultiScaler : public RendererPass<MultiScaler> class MultiScaler : public RendererPass<MultiScaler>
{ {
private: private:
AssetReference<Shader> _shader; AssetReference<Shader> _shader;
GPUPipelineState* _psHalfDepth = nullptr;
GPUPipelineStatePermutationsPs<2> _psBlur5; GPUPipelineStatePermutationsPs<2> _psBlur5;
GPUPipelineStatePermutationsPs<2> _psBlur9; GPUPipelineStatePermutationsPs<2> _psBlur9;
GPUPipelineStatePermutationsPs<2> _psBlur13; GPUPipelineStatePermutationsPs<2> _psBlur13;
GPUPipelineStatePermutationsPs<3> _psHalfDepth;
GPUPipelineState* _psUpscale = nullptr; GPUPipelineState* _psUpscale = nullptr;
public: public:
/// <summary> /// <summary>
/// Filter mode /// Filter mode
/// </summary> /// </summary>
@@ -53,7 +51,7 @@ public:
/// <param name="src">The source texture.</param> /// <param name="src">The source texture.</param>
/// <param name="dst">The destination texture.</param> /// <param name="dst">The destination texture.</param>
/// <param name="tmp">The temporary texture (should have the same size as destination texture).</param> /// <param name="tmp">The temporary texture (should have the same size as destination texture).</param>
void Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* src, GPUTextureView* dst, GPUTextureView* tmp); void Filter(FilterMode mode, GPUContext* context, int32 width, int32 height, GPUTextureView* src, GPUTextureView* dst, GPUTextureView* tmp);
/// <summary> /// <summary>
/// Performs texture filtering. /// Performs texture filtering.
@@ -64,18 +62,26 @@ public:
/// <param name="height">The output height.</param> /// <param name="height">The output height.</param>
/// <param name="srcDst">The source and destination texture.</param> /// <param name="srcDst">The source and destination texture.</param>
/// <param name="tmp">The temporary texture (should have the same size as destination texture).</param> /// <param name="tmp">The temporary texture (should have the same size as destination texture).</param>
void Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* srcDst, GPUTextureView* tmp); void Filter(FilterMode mode, GPUContext* context, int32 width, int32 height, GPUTextureView* srcDst, GPUTextureView* tmp);
/// <summary> /// <summary>
/// Downscales the depth buffer (to half resolution). /// Downscales the depth buffer (to half resolution). Uses `min` operator (`max` for inverted depth) to output the furthest depths for conservative usage.
/// </summary> /// </summary>
/// <param name="context">The context.</param> /// <param name="context">The context.</param>
/// <param name="dstWidth">The width of the destination texture (in pixels).</param> /// <param name="dstWidth">The width of the destination texture (in pixels).</param>
/// <param name="dstHeight">The height of the destination texture (in pixels).</param> /// <param name="dstHeight">The height of the destination texture (in pixels).</param>
/// <param name="src">The source texture.</param> /// <param name="src">The source texture (has to have ShaderResource flag).</param>
/// <param name="dst">The destination texture.</param> /// <param name="dst">The destination texture (has to have DepthStencil or RenderTarget flag).</param>
void DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstHeight, GPUTexture* src, GPUTextureView* dst); void DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstHeight, GPUTexture* src, GPUTextureView* dst);
/// <summary>
/// Generates the Hierarchical Z-Buffer (HiZ). Uses `min` operator (`max` for inverted depth) to output the furthest depths for conservative usage.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="srcDepth">The source depth buffer texture (has to have ShaderResource flag).</param>
/// <param name="dstHiZ">The destination HiZ texture (has to have DepthStencil or RenderTarget flag).</param>
void BuildHiZ(GPUContext* context, GPUTexture* srcDepth, GPUTexture* dstHiZ);
/// <summary> /// <summary>
/// Upscales the texture. /// Upscales the texture.
/// </summary> /// </summary>
@@ -86,7 +92,6 @@ public:
void Upscale(GPUContext* context, const Viewport& viewport, GPUTexture* src, GPUTextureView* dst); void Upscale(GPUContext* context, const Viewport& viewport, GPUTexture* src, GPUTextureView* dst);
public: public:
// [RendererPass] // [RendererPass]
String ToString() const override; String ToString() const override;
bool Init() override; bool Init() override;
@@ -94,17 +99,16 @@ public:
#if COMPILE_WITH_DEV_ENV #if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj) void OnShaderReloading(Asset* obj)
{ {
_psHalfDepth->ReleaseGPU();
_psUpscale->ReleaseGPU(); _psUpscale->ReleaseGPU();
_psBlur5.Release(); _psBlur5.Release();
_psBlur9.Release(); _psBlur9.Release();
_psBlur13.Release(); _psBlur13.Release();
_psHalfDepth.Release();
invalidateResources(); invalidateResources();
} }
#endif #endif
protected: protected:
// [RendererPass] // [RendererPass]
bool setupResources() override; bool setupResources() override;
}; };
@@ -443,6 +443,11 @@ String ShadersCompilation::ResolveShaderPath(StringView path)
// Hard-coded redirect to platform-specific includes // Hard-coded redirect to platform-specific includes
result = Globals::StartupFolder / TEXT("Source/Platforms"); result = Globals::StartupFolder / TEXT("Source/Platforms");
} }
else if (projectName.StartsWith(StringView(TEXT("FlaxThirdParty"))))
{
// Hard-coded redirect to third-party-specific includes
result = Globals::StartupFolder / TEXT("Source/ThirdParty");
}
else else
{ {
HashSet<const ProjectInfo*> projects; HashSet<const ProjectInfo*> projects;
+16 -10
View File
@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
#include "./Flax/Common.hlsl" #include "./Flax/Common.hlsl"
#include "./Flax/Gather.hlsl"
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
float2 TexelSize; float2 TexelSize;
@@ -14,19 +15,24 @@ Texture2D Input : register(t0);
// Pixel Shader for depth buffer downscale (to half res) // Pixel Shader for depth buffer downscale (to half res)
META_PS(true, FEATURE_LEVEL_ES2) META_PS(true, FEATURE_LEVEL_ES2)
float PS_HalfDepth(Quad_VS2PS input) : SV_Depth META_PERMUTATION_1(OUTPUT_DEPTH=0)
{ META_PERMUTATION_1(OUTPUT_DEPTH=1)
#if CAN_USE_GATHER META_PERMUTATION_1(HZB_CLOSEST=2)
float4 depths = Input.GatherRed(SamplerPointClamp, input.TexCoord); float PS_HalfDepth(Quad_VS2PS input)
#if OUTPUT_DEPTH
: SV_Depth
#else #else
float4 depths; : SV_Target0
depths.x = Input.SampleLevel(SamplerPointClamp, input.TexCoord + float2(0, 1) * TexelSize, 0).r;
depths.y = Input.SampleLevel(SamplerPointClamp, input.TexCoord + float2(1, 1) * TexelSize, 0).r;
depths.z = Input.SampleLevel(SamplerPointClamp, input.TexCoord + float2(1, 0) * TexelSize, 0).r;
depths.w = Input.SampleLevel(SamplerPointClamp, input.TexCoord + float2(0, 0) * TexelSize, 0).r;
#endif #endif
{
// Load 4 depth values (2x2 quad)
float4 depths = TextureGatherRed(Input, SamplerPointClamp, input.TexCoord);
return max(depths.x, max(depths.y, max(depths.z, depths.w))) + 0.0001f; #if HZB_CLOSEST
return min(depths.x, min(depths.y, min(depths.z, depths.w)));
#else
return max(depths.x, max(depths.y, max(depths.z, depths.w)));
#endif
} }
// Pixel Shader for 5-tap gaussian blur // Pixel Shader for 5-tap gaussian blur
-3
View File
@@ -6,9 +6,6 @@
#include "./Flax/GBufferCommon.hlsl" #include "./Flax/GBufferCommon.hlsl"
#include "./Flax/Quaternion.hlsl" #include "./Flax/Quaternion.hlsl"
// Hit depth (view space) threshold to detect if sky was hit (value above it where 1.0f is default)
#define REFLECTIONS_HIT_THRESHOLD 0.9f
// Packed env probe data // Packed env probe data
struct EnvProbeData struct EnvProbeData
{ {
+31 -25
View File
@@ -5,6 +5,9 @@
#include "./Flax/Random.hlsl" #include "./Flax/Random.hlsl"
#include "./Flax/MonteCarlo.hlsl" #include "./Flax/MonteCarlo.hlsl"
#include "./Flax/GBufferCommon.hlsl" #include "./Flax/GBufferCommon.hlsl"
#if SSR_USE_HZB
#include "./FlaxThirdParty/FidelityFX/ffx_sssr.h"
#endif
// 1:-1 to 0:1 // 1:-1 to 0:1
float2 ClipToUv(float2 clipPos) float2 ClipToUv(float2 clipPos)
@@ -44,19 +47,22 @@ float RayAttenBorder(float2 pos, float value)
// Returns: xy: hitUV, z: hitMask, where hitUV is the result UV of hit pixel, hitMask is the normalized sample weight (0 if no hit). // Returns: xy: hitUV, z: hitMask, where hitUV is the result UV of hit pixel, hitMask is the normalized sample weight (0 if no hit).
float3 ScreenSpaceReflectionDirection(float2 uv, GBufferSample gBuffer, float3 viewPos, bool temporal = false, float temporalTime = 0.0f, float brdfBias = 0.82f) float3 ScreenSpaceReflectionDirection(float2 uv, GBufferSample gBuffer, float3 viewPos, bool temporal = false, float temporalTime = 0.0f, float brdfBias = 0.82f)
{ {
// Randomize it a little
float2 jitter = RandN2(uv + temporalTime); float2 jitter = RandN2(uv + temporalTime);
float2 Xi = jitter; float2 Xi = jitter;
Xi.y = lerp(Xi.y, 0.0, brdfBias); Xi.y = lerp(Xi.y, 0.0, brdfBias);
float3 H = temporal ? TangentToWorld(gBuffer.Normal, ImportanceSampleGGX(Xi, gBuffer.Roughness)) : gBuffer.Normal; float3 H = temporal ? TangentToWorld(gBuffer.Normal, ImportanceSampleGGX(Xi, gBuffer.Roughness)) : gBuffer.Normal;
float3 viewWS = normalize(gBuffer.WorldPos - viewPos); float3 viewWS = normalize(gBuffer.WorldPos - viewPos);
return reflect(viewWS, H.xyz); return reflect(viewWS, H.xyz);
} }
// Screen Space Reflection ray tracing utility. // Screen Space Reflection ray tracing utility.
// If SSR_USE_HZB is defined, it uses Hierarchical Z-Buffer for tracing against screen (assumes that depthBuffer is a HiZ with full mip-chain).
// Returns: xy: hitUV, z: hitMask, where hitUV is the result UV of hit pixel, hitMask is the normalized sample weight (0 if no hit). // Returns: xy: hitUV, z: hitMask, where hitUV is the result UV of hit pixel, hitMask is the normalized sample weight (0 if no hit).
float3 TraceScreenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D depthBuffer, float3 viewPos, float4x4 viewMatrix, float4x4 viewProjectionMatrix, float stepSize, float maxSamples = 20, bool temporal = false, float temporalTime = 0.0f, float worldAntiSelfOcclusionBias = 0.1f, float brdfBias = 0.82f, float drawDistance = 5000.0f, float roughnessThreshold = 0.4f, float edgeFade = 0.1f) float3 TraceScreenSpaceReflection(
#if SSR_USE_HZB
out bool uncertainHit, uint hzbMips,
#endif
float2 uv, GBufferSample gBuffer, Texture2D depthBuffer, float3 viewPos, float4x4 viewMatrix, float4x4 viewProjectionMatrix, float stepSize, float maxSamples = 50, bool temporal = false, float temporalTime = 0.0f, float worldAntiSelfOcclusionBias = 0.1f, float brdfBias = 0.82f, float drawDistance = 5000.0f, float roughnessThreshold = 0.4f, float edgeFade = 0.1f)
{ {
#ifndef SSR_SKIP_INVALID_CHECK #ifndef SSR_SKIP_INVALID_CHECK
// Reject invalid pixels // Reject invalid pixels
@@ -69,17 +75,19 @@ float3 TraceScreenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D de
float3 reflectVS = normalize(reflect(gBuffer.ViewPos, normalVS)); float3 reflectVS = normalize(reflect(gBuffer.ViewPos, normalVS));
if (gBuffer.ViewPos.z < 1.0 && reflectVS.z < 0.4) if (gBuffer.ViewPos.z < 1.0 && reflectVS.z < 0.4)
return 0; return 0;
// Calculate ray path in UV space (z is raw depth)
float3 reflectWS = ScreenSpaceReflectionDirection(uv, gBuffer, viewPos, temporal, temporalTime, brdfBias); float3 reflectWS = ScreenSpaceReflectionDirection(uv, gBuffer, viewPos, temporal, temporalTime, brdfBias);
#if SSR_USE_HZB
worldAntiSelfOcclusionBias *= 10.0f; // Higher bias for HZB trace to reduce artifacts
#endif
float3 startWS = gBuffer.WorldPos + gBuffer.Normal * worldAntiSelfOcclusionBias; float3 startWS = gBuffer.WorldPos + gBuffer.Normal * worldAntiSelfOcclusionBias;
float3 startUV = ProjectWorldToUv(startWS, viewProjectionMatrix); float3 startUV = ProjectWorldToUv(startWS, viewProjectionMatrix);
float3 endUV = ProjectWorldToUv(startWS + reflectWS, viewProjectionMatrix); float3 endUV = ProjectWorldToUv(startWS + reflectWS, viewProjectionMatrix);
float3 rayUV = endUV - startUV; float3 rayUV = endUV - startUV;
float2 rayUVAbs = abs(rayUV.xy); float2 rayUVAbs = abs(rayUV.xy);
rayUV *= stepSize / max(rayUVAbs.x, rayUVAbs.y); rayUV *= stepSize / max(rayUVAbs.x, rayUVAbs.y);
float3 startUv = startUV + rayUV * 2; float3 startUv = startUV + rayUV * 2;
float3 currOffset = startUv; float3 currOffset = startUv;
float3 rayStep = rayUV * 2; float3 rayStep = rayUV * 2;
@@ -89,26 +97,30 @@ float3 TraceScreenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D de
float numSamples = min(maxSamples, samplesToEdge.x); float numSamples = min(maxSamples, samplesToEdge.x);
rayStep *= samplesToEdge.x / numSamples; rayStep *= samplesToEdge.x / numSamples;
// Calculate depth difference error
float depthDiffError = 1.3f * abs(rayStep.z);
// Ray trace // Ray trace
float depthDiffError = 1.3f * abs(rayStep.z);
#if SSR_USE_HZB
bool validHit = false;
uint2 depthBufferSize;
depthBuffer.GetDimensions(depthBufferSize.x, depthBufferSize.y);
float3 hit = FFX_SSSR_HierarchicalRaymarch(depthBuffer, hzbMips, depthDiffError, uncertainHit, startUV, rayUV, depthBufferSize, 0, numSamples, validHit);
if (!validHit)
return 0;
currOffset = hit;
#else
float currSampleIndex = 0; float currSampleIndex = 0;
float currSample, depthDiff;
LOOP LOOP
while (currSampleIndex < numSamples) while (currSampleIndex < numSamples)
{ {
// Sample depth buffer and calculate depth difference // Sample depth buffer and calculate depth difference
currSample = SAMPLE_RT(depthBuffer, currOffset.xy).r; float currSample = SAMPLE_RT(depthBuffer, currOffset.xy).r;
depthDiff = currOffset.z - currSample; float depthDiff = currOffset.z - currSample;
// Check intersection // Check intersection
if (depthDiff >= 0) if (depthDiff >= 0)
{ {
if (depthDiff < depthDiffError) if (depthDiff < depthDiffError)
{
break; break;
}
currOffset -= rayStep; currOffset -= rayStep;
rayStep *= 0.5; rayStep *= 0.5;
} }
@@ -117,25 +129,19 @@ float3 TraceScreenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D de
currOffset += rayStep; currOffset += rayStep;
currSampleIndex++; currSampleIndex++;
} }
// Check if has valid result after ray tracing
if (currSampleIndex >= numSamples) if (currSampleIndex >= numSamples)
{ return 0; // All samples done but no result
// All samples done but no result #endif
return 0;
}
float2 hitUV = currOffset.xy;
// Fade rays close to screen edge // Fade rays close to screen edge
const float fadeStart = 0.9f; const float fadeStart = 0.9f;
const float fadeEnd = 1.0f; const float fadeEnd = 1.0f;
const float fadeDiffRcp = 1.0f / (fadeEnd - fadeStart); const float fadeDiffRcp = 1.0f / (fadeEnd - fadeStart);
float2 boundary = abs(hitUV - float2(0.5f, 0.5f)) * 2.0f; float2 boundary = abs(currOffset.xy - float2(0.5f, 0.5f)) * 2.0f;
float fadeOnBorder = 1.0f - saturate((boundary.x - fadeStart) * fadeDiffRcp); float fadeOnBorder = 1.0f - saturate((boundary.x - fadeStart) * fadeDiffRcp);
fadeOnBorder *= 1.0f - saturate((boundary.y - fadeStart) * fadeDiffRcp); fadeOnBorder *= 1.0f - saturate((boundary.y - fadeStart) * fadeDiffRcp);
fadeOnBorder = smoothstep(0.0f, 1.0f, fadeOnBorder); fadeOnBorder = smoothstep(0.0f, 1.0f, fadeOnBorder);
fadeOnBorder *= RayAttenBorder(hitUV, edgeFade); fadeOnBorder *= RayAttenBorder(currOffset.xy, edgeFade);
// Fade rays on high roughness // Fade rays on high roughness
float roughnessFade = saturate((roughnessThreshold - gBuffer.Roughness) * 20); float roughnessFade = saturate((roughnessThreshold - gBuffer.Roughness) * 20);
@@ -144,5 +150,5 @@ float3 TraceScreenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D de
float distanceFade = saturate((drawDistance - gBuffer.ViewPos.z) / drawDistance); float distanceFade = saturate((drawDistance - gBuffer.ViewPos.z) / drawDistance);
// Output: xy: hitUV, z: hitMask // Output: xy: hitUV, z: hitMask
return float3(hitUV, fadeOnBorder * roughnessFade * distanceFade); return float3(currOffset.xy, fadeOnBorder * roughnessFade * distanceFade);
} }
+52 -23
View File
@@ -1,7 +1,14 @@
// Copyright (c) Wojciech Figat. All rights reserved. // Copyright (c) Wojciech Figat. All rights reserved.
// Skips additional check in TraceScreenSpaceReflection for material that is already done by PS_RayTracePass
#define SSR_SKIP_INVALID_CHECK 1 #define SSR_SKIP_INVALID_CHECK 1
// Uses more-optimized Hierarchical Z-Buffer tracing rather than naive Depth Buffer tracing
#define SSR_USE_HZB 1
// Enable/disable luminance filter to reduce reflections highlights
#define SSR_REDUCE_HIGHLIGHTS 1
#include "./Flax/Common.hlsl" #include "./Flax/Common.hlsl"
#include "./Flax/LightingCommon.hlsl" #include "./Flax/LightingCommon.hlsl"
#include "./Flax/ReflectionsCommon.hlsl" #include "./Flax/ReflectionsCommon.hlsl"
@@ -11,8 +18,7 @@
#include "./Flax/GlobalSignDistanceField.hlsl" #include "./Flax/GlobalSignDistanceField.hlsl"
#include "./Flax/GI/GlobalSurfaceAtlas.hlsl" #include "./Flax/GI/GlobalSurfaceAtlas.hlsl"
// Enable/disable luminance filter to reduce reflections highlights #define SSR_USE_SDF (USE_GLOBAL_SURFACE_ATLAS && CAN_USE_GLOBAL_SURFACE_ATLAS)
#define SSR_REDUCE_HIGHLIGHTS 1
META_CB_BEGIN(0, Data) META_CB_BEGIN(0, Data)
GBufferData GBuffer; GBufferData GBuffer;
@@ -20,13 +26,13 @@ float MaxColorMiplevel;
float TraceSizeMax; float TraceSizeMax;
float MaxTraceSamples; float MaxTraceSamples;
float RoughnessFade; float RoughnessFade;
float2 SSRtexelSize; float2 SSRTexelSize;
float TemporalTime; float TemporalTime;
float BRDFBias; float BRDFBias;
float WorldAntiSelfOcclusionBias; float WorldAntiSelfOcclusionBias;
float EdgeFadeFactor; float EdgeFadeFactor;
float TemporalResponse; float TemporalResponse;
float Dummy0; uint DepthMips;
float RayTraceStep; float RayTraceStep;
float TemporalEffect; float TemporalEffect;
float Intensity; float Intensity;
@@ -104,14 +110,31 @@ float4 PS_RayTracePass(Quad_VS2PS input) : SV_Target0
GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord); GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord);
// Reject invalid pixels // Reject invalid pixels
BRANCH
if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT || gBuffer.Roughness > RoughnessFade || gBuffer.ViewPos.z > FadeOutDistance) if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT || gBuffer.Roughness > RoughnessFade || gBuffer.ViewPos.z > FadeOutDistance)
return base; return base;
// Trace depth buffer to find intersection // Trace depth buffer to find intersection
float3 screenHit = TraceScreenSpaceReflection(input.TexCoord, gBuffer, Depth, gBufferData.ViewPos, ViewMatrix, ViewProjectionMatrix, RayTraceStep, MaxTraceSamples, TemporalEffect, TemporalTime, WorldAntiSelfOcclusionBias, BRDFBias, FadeOutDistance, RoughnessFade, EdgeFadeFactor); bool uncertainHit = false;
float4 result = base; float3 screenHit = TraceScreenSpaceReflection(
#if SSR_USE_HZB
uncertainHit, DepthMips,
#endif
input.TexCoord, gBuffer, Depth, gBufferData.ViewPos, ViewMatrix, ViewProjectionMatrix, RayTraceStep, MaxTraceSamples, TemporalEffect, TemporalTime, WorldAntiSelfOcclusionBias, BRDFBias, FadeOutDistance, RoughnessFade, EdgeFadeFactor);
float4 result = base;
#if SSR_USE_SDF
if (screenHit.z > 0 && !uncertainHit) // Only use certain SSR hits when SDF tracing is enabled
#else
if (screenHit.z > 0) if (screenHit.z > 0)
#endif
{ {
if (uncertainHit)
{
// Jitter edges of uncertain hits (when ray goes behind the object)
screenHit.xy += RandN2(input.TexCoord + TemporalTime) * SSRTexelSize;
}
// Sample color buffer mip that matches roughness of the surface to get blurred reflections
float3 viewVector = normalize(gBufferData.ViewPos - gBuffer.WorldPos); float3 viewVector = normalize(gBufferData.ViewPos - gBuffer.WorldPos);
float NdotV = saturate(dot(gBuffer.Normal, viewVector)); float NdotV = saturate(dot(gBuffer.Normal, viewVector));
float coneTangent = lerp(0.0, gBuffer.Roughness * 5 * (1.0 - BRDFBias), pow(NdotV, 1.5) * sqrt(gBuffer.Roughness)); float coneTangent = lerp(0.0, gBuffer.Roughness * 5 * (1.0 - BRDFBias), pow(NdotV, 1.5) * sqrt(gBuffer.Roughness));
@@ -119,21 +142,28 @@ float4 PS_RayTracePass(Quad_VS2PS input) : SV_Target0
float mip = clamp(log2(intersectionCircleRadius * TraceSizeMax), 0.0, MaxColorMiplevel); float mip = clamp(log2(intersectionCircleRadius * TraceSizeMax), 0.0, MaxColorMiplevel);
float3 sampleColor = Texture0.SampleLevel(SamplerLinearClamp, screenHit.xy, mip).rgb; float3 sampleColor = Texture0.SampleLevel(SamplerLinearClamp, screenHit.xy, mip).rgb;
result = float4(sampleColor, screenHit.z); result = float4(sampleColor, screenHit.z);
if (screenHit.z >= REFLECTIONS_HIT_THRESHOLD)
#if SSR_USE_SDF
// Skip SDF tracing if SSR hit is very certain
BRANCH
if (result.a > 0.95f)
return result; return result;
#endif
} }
// Fallback to Global SDF and Global Surface Atlas tracing // Fallback to Global SDF and Global Surface Atlas tracing
#if USE_GLOBAL_SURFACE_ATLAS && CAN_USE_GLOBAL_SURFACE_ATLAS #if SSR_USE_SDF
// Calculate reflection direction (the same TraceScreenSpaceReflection) // Calculate reflection direction (the same TraceScreenSpaceReflection)
float3 reflectWS = ScreenSpaceReflectionDirection(input.TexCoord, gBuffer, gBufferData.ViewPos, TemporalEffect, TemporalTime, BRDFBias); float3 reflectWS = ScreenSpaceReflectionDirection(input.TexCoord, gBuffer, gBufferData.ViewPos, TemporalEffect, TemporalTime, BRDFBias);
// Raytrace Global SDF
GlobalSDFTrace sdfTrace; GlobalSDFTrace sdfTrace;
float maxDistance = GLOBAL_SDF_WORLD_SIZE; float maxDistance = GLOBAL_SDF_WORLD_SIZE;
sdfTrace.Init(gBuffer.WorldPos, reflectWS, 0.0f, maxDistance); sdfTrace.Init(gBuffer.WorldPos, reflectWS, 0.0f, maxDistance);
GlobalSDFHit sdfHit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, sdfTrace, 2.0f); GlobalSDFHit sdfHit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, sdfTrace, 2.0f);
if (sdfHit.IsHit()) if (sdfHit.IsHit())
{ {
// Sample Global Surface Atlas
float3 hitPosition = sdfHit.GetHitPosition(sdfTrace); float3 hitPosition = sdfHit.GetHitPosition(sdfTrace);
float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(GlobalSDF, sdfHit); float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(GlobalSDF, sdfHit);
float4 surfaceAtlas = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, RWGlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hitPosition, -reflectWS, surfaceThreshold); float4 surfaceAtlas = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, RWGlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hitPosition, -reflectWS, surfaceThreshold);
@@ -159,28 +189,27 @@ float4 PS_ResolvePass(Quad_VS2PS input) : SV_Target0
static const float2 Offsets[8] = static const float2 Offsets[8] =
{ {
float2( 0, 0), float2( 0, 0),
float2( 2, -2), float2( 1, -1),
float2(-2, -2), float2(-1, -1),
float2( 0, 2), float2( 0, 1),
float2(-2, 0), float2(-1, 0),
float2( 0, -2), float2( 0, -1),
float2( 2, 0), float2( 1, 0),
float2( 2, 2), float2( 1, 1),
}; };
float2 uv = input.TexCoord;
// Inputs: // Inputs:
// Texture0 - ray trace buffer (xy: HDR color, z: weight) // Texture0 - ray trace buffer (xy: HDR color, z: weight)
// Sample GBuffer // Sample GBuffer
GBufferData gBufferData = GetGBufferData(); GBufferData gBufferData = GetGBufferData();
GBufferSample gBuffer = SampleGBuffer(gBufferData, uv); GBufferSample gBuffer = SampleGBuffer(gBufferData, input.TexCoord);
BRANCH
if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT)
return 0; return 0;
// Randomize it a little // Randomize it a little
float2 random = RandN2(uv + TemporalTime); float2 random = RandN2(input.TexCoord + TemporalTime);
float2 blueNoise = random.xy * 2.0 - 1.0; float2 blueNoise = random.xy * 2.0 - 1.0;
float2x2 offsetRotationMatrix = float2x2(blueNoise.x, blueNoise.y, -blueNoise.y, blueNoise.x); float2x2 offsetRotationMatrix = float2x2(blueNoise.x, blueNoise.y, -blueNoise.y, blueNoise.x);
@@ -189,9 +218,9 @@ float4 PS_ResolvePass(Quad_VS2PS input) : SV_Target0
UNROLL UNROLL
for (int i = 0; i < RESOLVE_SAMPLES; i++) for (int i = 0; i < RESOLVE_SAMPLES; i++)
{ {
float2 offsetUV = Offsets[i] * SSRtexelSize; float2 offsetUV = Offsets[i] * SSRTexelSize;
offsetUV = mul(offsetRotationMatrix, offsetUV); offsetUV = mul(offsetRotationMatrix, offsetUV);
float4 value = Texture0.SampleLevel(SamplerLinearClamp, uv + offsetUV, 0); float4 value = Texture0.SampleLevel(SamplerLinearClamp, input.TexCoord + offsetUV, 0);
#if SSR_REDUCE_HIGHLIGHTS #if SSR_REDUCE_HIGHLIGHTS
value.rgb /= 1 + Luminance(value.rgb); value.rgb /= 1 + Luminance(value.rgb);
#endif #endif
@@ -224,8 +253,8 @@ float4 PS_TemporalPass(Quad_VS2PS input) : SV_Target0
float2 velocity = Texture2.SampleLevel(SamplerLinearClamp, uv, 0).xy; float2 velocity = Texture2.SampleLevel(SamplerLinearClamp, uv, 0).xy;
float2 prevUV = uv - velocity; float2 prevUV = uv - velocity;
float4 current = Texture0.SampleLevel(SamplerLinearClamp, uv, 0); float4 current = Texture0.SampleLevel(SamplerLinearClamp, uv, 0);
float2 du = float2(SSRtexelSize.x, 0.0); float2 du = float2(SSRTexelSize.x, 0.0);
float2 dv = float2(0.0, SSRtexelSize.y); float2 dv = float2(0.0, SSRTexelSize.y);
// Sample pixels around // Sample pixels around
float4 currentTopLeft = Texture0.SampleLevel(SamplerLinearClamp, uv.xy - dv - du, 0); float4 currentTopLeft = Texture0.SampleLevel(SamplerLinearClamp, uv.xy - dv - du, 0);
+22
View File
@@ -0,0 +1,22 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using Flax.Build;
using Flax.Build.NativeCpp;
/// <summary>
/// https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK
/// </summary>
public class FidelityFX : HeaderOnlyModule
{
/// <inheritdoc />
public override void Init()
{
base.Init();
LicenseType = LicenseTypes.MIT;
LicenseFilePath = "license.txt";
// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";
}
}
+131
View File
@@ -0,0 +1,131 @@
/**********************************************************************
Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
********************************************************************/
#ifndef FFX_SSSR
#define FFX_SSSR
#define FFX_SSSR_FLOAT_MAX 3.402823466e+38
void FFX_SSSR_InitialAdvanceRay(float3 origin, float3 direction, float3 inv_direction, float2 current_mip_resolution, float2 current_mip_resolution_inv, float2 floor_offset, float2 uv_offset, out float3 position, out float current_t) {
float2 current_mip_position = current_mip_resolution * origin.xy;
// Intersect ray with the half box that is pointing away from the ray origin.
float2 xy_plane = floor(current_mip_position) + floor_offset;
xy_plane = xy_plane * current_mip_resolution_inv + uv_offset;
// o + d * t = p' => t = (p' - o) / d
float2 t = xy_plane * inv_direction.xy - origin.xy * inv_direction.xy;
current_t = min(t.x, t.y);
position = origin + current_t * direction;
}
bool FFX_SSSR_AdvanceRay(float3 origin, float3 direction, float3 inv_direction, float2 current_mip_position, float2 current_mip_resolution_inv, float2 floor_offset, float2 uv_offset, float surface_z, inout float3 position, inout float current_t) {
// Create boundary planes
float2 xy_plane = floor(current_mip_position) + floor_offset;
xy_plane = xy_plane * current_mip_resolution_inv + uv_offset;
float3 boundary_planes = float3(xy_plane, surface_z);
// Intersect ray with the half box that is pointing away from the ray origin.
// o + d * t = p' => t = (p' - o) / d
float3 t = boundary_planes * inv_direction - origin * inv_direction;
// Prevent using z plane when shooting out of the depth buffer.
#ifdef FFX_SSSR_INVERTED_DEPTH_RANGE
t.z = direction.z < 0 ? t.z : FFX_SSSR_FLOAT_MAX;
#else
t.z = direction.z > 0 ? t.z : FFX_SSSR_FLOAT_MAX;
#endif
// Choose nearest intersection with a boundary.
float t_min = min(min(t.x, t.y), t.z);
#ifdef FFX_SSSR_INVERTED_DEPTH_RANGE
// Larger z means closer to the camera.
bool above_surface = surface_z < position.z;
#else
// Smaller z means closer to the camera.
bool above_surface = surface_z > position.z;
#endif
// Decide whether we are able to advance the ray until we hit the xy boundaries or if we had to clamp it at the surface.
// We use the asuint comparison to avoid NaN / Inf logic, also we actually care about bitwise equality here to see if t_min is the t.z we fed into the min3 above.
bool skipped_tile = asuint(t_min) != asuint(t.z) && above_surface;
// Make sure to only advance the ray if we're still above the surface.
current_t = above_surface ? t_min : current_t;
// Advance ray
position = origin + current_t * direction;
return skipped_tile;
}
float2 FFX_SSSR_GetMipResolution(float2 screen_dimensions, int mip_level) {
return screen_dimensions * pow(0.5, mip_level);
}
// Requires origin and direction of the ray to be in screen space [0, 1] x [0, 1]
float3 FFX_SSSR_HierarchicalRaymarch(Texture2D depthBuffer, uint hzbMips, float depthDiffError, out bool uncertainHit, float3 origin, float3 direction, float2 screen_size, int most_detailed_mip, uint max_traversal_intersections, out bool valid_hit) {
const float3 inv_direction = select(direction != 0, 1.0 / direction, FFX_SSSR_FLOAT_MAX);
// Start on mip with highest detail.
int current_mip = most_detailed_mip;
// Could recompute these every iteration, but it's faster to hoist them out and update them.
float2 current_mip_resolution = FFX_SSSR_GetMipResolution(screen_size, current_mip);
float2 current_mip_resolution_inv = rcp(current_mip_resolution);
// Offset to the bounding boxes uv space to intersect the ray with the center of the next pixel.
// This means we ever so slightly over shoot into the next region.
float2 uv_offset = 0.005 * exp2(most_detailed_mip) / screen_size;
uv_offset = select(direction.xy < 0, -uv_offset, uv_offset);
// Offset applied depending on current mip resolution to move the boundary to the left/right upper/lower border depending on ray direction.
float2 floor_offset = select(direction.xy < 0, 0, 1);
// Initially advance ray to avoid immediate self intersections.
float current_t;
float3 position;
FFX_SSSR_InitialAdvanceRay(origin, direction, inv_direction, current_mip_resolution, current_mip_resolution_inv, floor_offset, uv_offset, position, current_t);
uint overDiffError = 0;
int i = 0;
while (i < max_traversal_intersections && current_mip >= most_detailed_mip) {
float2 current_mip_position = current_mip_resolution * position.xy;
float surface_z = depthBuffer.Load(int3(current_mip_position, current_mip)).x;
if (position.z - surface_z > depthDiffError) overDiffError++; // Count number of times we were under the depth by more than the allowed error
bool skipped_tile = FFX_SSSR_AdvanceRay(origin, direction, inv_direction, current_mip_position, current_mip_resolution_inv, floor_offset, uv_offset, surface_z, position, current_t);
++i;
if (!skipped_tile || current_mip < hzbMips) // Never go too low depth resolution to avoid blocky artifacts
{
current_mip += skipped_tile ? 1 : -1;
current_mip_resolution *= skipped_tile ? 0.5 : 2;
current_mip_resolution_inv *= skipped_tile ? 2 : 0.5;
}
}
valid_hit = (i <= max_traversal_intersections);
uncertainHit = valid_hit && overDiffError > 3; // If we went over under the surface to detect uncertain hits
return position;
}
#endif //FFX_SSSR
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.