Fixes for SSAO and SSR

This commit is contained in:
Wojtek Figat
2026-01-20 18:19:34 +01:00
parent 0c5cb59875
commit cc5e4c19e1
9 changed files with 43 additions and 38 deletions

View File

@@ -704,6 +704,12 @@ float RenderTools::GetDepthBounds(const RenderView& view, const Float3& point, b
return Math::Clamp(depth, 0.0f, 1.0f);
}
float RenderTools::GetDepthBounds(const RenderView& view, float viewDistance, bool near)
{
Float3 point = view.Position + view.Direction * (viewDistance * 2.0f);
return GetDepthBounds(view, point, near);
}
Float3 RenderTools::GetColorQuantizationError(PixelFormat format)
{
Float3 mantissaBits;

View File

@@ -155,6 +155,7 @@ public:
static Float2 GetDepthBounds(const RenderView& view, const BoundingBox& bounds);
static Float2 GetDepthBounds(const RenderView& view, const OrientedBoundingBox& bounds);
static float GetDepthBounds(const RenderView& view, const Float3& point, bool near);
static float GetDepthBounds(const RenderView& view, float viewDistance, bool near);
static constexpr float DepthBoundMaxBackground = 1.0f - 0.0000001f; // Skip background/sky pixels from shading
// Calculates error for a given render target format to reduce floating-point precision artifacts via QuantizeColor (from Noise.hlsl).

View File

@@ -270,12 +270,12 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext)
// Cache data
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
float m_sizeX = (float)renderContext.Buffers->GetWidth();
float m_sizeY = (float)renderContext.Buffers->GetHeight();
float m_halfSizeX = (m_sizeX + 1) / 2;
float m_halfSizeY = (m_sizeY + 1) / 2;
int32 m_sizeX = renderContext.Buffers->GetWidth();
int32 m_sizeY = renderContext.Buffers->GetHeight();
int32 m_halfSizeX = (m_sizeX + 1) / 2;
int32 m_halfSizeY = (m_sizeY + 1) / 2;
GPUTexture* depthBuffer = aoSettings.DepthResolution == ResolutionMode::Full ? renderContext.Buffers->DepthBuffer : renderContext.Buffers->RequestHalfResDepth(context);
GPUTextureView* depthBufferApply = _depthBounds ? renderContext.Buffers->DepthBuffer ->ViewReadOnlyDepth() : nullptr;
GPUTextureView* depthBufferApply = _depthBounds ? renderContext.Buffers->DepthBuffer->ViewReadOnlyDepth() : nullptr;
// Request temporary buffers
GPUTexture* m_halfDepths[4];
@@ -287,20 +287,20 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext)
for (int i = 0; i < 4; i++)
{
#if SSAO_DEPTH_MIPS_ENABLE_AT_QUALITY_PRESET < 99
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, 0, SSAO_DEPTH_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews);
tempDesc = GPUTextureDescription::New2D(m_halfSizeX, m_halfSizeY, 0, SSAO_DEPTH_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews);
#else
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, SSAO_DEPTH_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget);
tempDesc = GPUTextureDescription::New2D(m_halfSizeX, m_halfSizeY, SSAO_DEPTH_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget);
#endif
m_halfDepths[i] = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(m_halfDepths[i], "SSAO.HalfDepth");
}
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, SSAO_AO_RESULT_FORMAT);
tempDesc = GPUTextureDescription::New2D(m_halfSizeX, m_halfSizeY, SSAO_AO_RESULT_FORMAT);
m_pingPongHalfResultA = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(m_pingPongHalfResultA, "SSAO.ResultsHalfA");
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, SSAO_AO_RESULT_FORMAT);
tempDesc = GPUTextureDescription::New2D(m_halfSizeX, m_halfSizeY, SSAO_AO_RESULT_FORMAT);
m_pingPongHalfResultB = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(m_pingPongHalfResultA, "SSAO.ResultsHalfB");
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, SSAO_AO_RESULT_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget, 4);
tempDesc = GPUTextureDescription::New2D(m_halfSizeX, m_halfSizeY, SSAO_AO_RESULT_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget, 4);
m_finalResults = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(m_finalResults, "SSAO.Results");
}
@@ -318,11 +318,12 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext)
GBufferPass::SetInputs(view, _constantsBufferData.GBuffer);
Matrix::Transpose(view.View, _constantsBufferData.ViewMatrix);
_constantsBufferData.ViewportPixelSize = Float2(1.0f / m_sizeX, 1.0f / m_sizeY);
_constantsBufferData.HalfViewportPixelSize = Float2(1.0f / m_halfSizeX, 1.0f / m_halfSizeY);
_constantsBufferData.ViewportPixelSize = Float2(1.0f / (float)m_sizeX, 1.0f / (float)m_sizeY);
_constantsBufferData.HalfViewportPixelSize = Float2(1.0f / (float)m_halfSizeX, 1.0f / (float)m_halfSizeY);
_constantsBufferData.Viewport2xPixelSize = Float2(_constantsBufferData.ViewportPixelSize.X * 2.0f, _constantsBufferData.ViewportPixelSize.Y * 2.0f);
_constantsBufferData.Viewport2xPixelSize_x_025 = Float2(_constantsBufferData.Viewport2xPixelSize.X * 0.25f, _constantsBufferData.Viewport2xPixelSize.Y * 0.25f);
_constantsBufferData.InputDepthScale = aoSettings.DepthResolution == ResolutionMode::Full ? 2 : 1;
const float tanHalfFOVY = 1.0f / proj.Values[1][1];
@@ -375,7 +376,7 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext)
// Bind scene depth buffer and set proper viewport
context->BindSR(SSAO_TEXTURE_SLOT0, depthBuffer);
context->SetViewportAndScissors(m_halfSizeX, m_halfSizeY);
context->SetViewportAndScissors((float)m_halfSizeX, (float)m_halfSizeY);
// Prepare depth in half resolution
{
@@ -437,7 +438,7 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext)
// Generate SSAO (interleaved in checkerboard pattern)
{
context->SetViewportAndScissors(m_halfSizeX, m_halfSizeY);
context->SetViewportAndScissors((float)m_halfSizeX, (float)m_halfSizeY);
for (int32 pass = 0; pass < 4; pass++)
{
if (settings.SkipHalfPixels && ((pass == 1) || (pass == 2)))
@@ -526,10 +527,9 @@ void AmbientOcclusionPass::Render(RenderContext& renderContext)
}
// Apply
Float3 depthBoundPoint = renderContext.View.Position + renderContext.View.Direction * (aoSettings.FadeOutDistance * 2.0f);
context->SetDepthBounds(0.0f, RenderTools::GetDepthBounds(renderContext.View, depthBoundPoint, false));
context->SetDepthBounds(0.0f, RenderTools::GetDepthBounds(renderContext.View, aoSettings.FadeOutDistance, false));
context->BindSR(SSAO_TEXTURE_SLOT0, m_finalResults->ViewArray());
context->SetViewportAndScissors(m_sizeX, m_sizeY);
context->SetViewportAndScissors((float)m_sizeX, (float)m_sizeY);
context->SetState(settings.SkipHalfPixels ? _psApplyHalf : _psApply);
context->SetRenderTarget(depthBufferApply, renderContext.Buffers->GBuffer0->View());
context->DrawFullscreenTriangle();

View File

@@ -31,6 +31,9 @@ private:
int32 PassIndex;
float EffectMaxDistance;
Float3 Padding0;
uint32 InputDepthScale;
Float2 Viewport2xPixelSize;
Float2 Viewport2xPixelSize_x_025;

View File

@@ -362,15 +362,6 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
{
ScreenSpaceReflectionsPass::Instance()->Render(renderContext, *reflectionsBuffer, lightBuffer);
context->SetViewportAndScissors(renderContext.Task->GetViewport());
/*
// DEBUG_CODE
context->RestoreViewport();
context->SetRenderTarget(output);
context->Draw(reflectionsRT);
return;
// DEBUG_CODE
*/
}
if (renderContext.View.Mode == ViewMode::Reflections)

View File

@@ -166,14 +166,14 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
const int32 traceHeight = RenderTools::GetResolution(height, settings.RayTracePassResolution);
const int32 resolveWidth = RenderTools::GetResolution(width, settings.ResolvePassResolution);
const int32 resolveHeight = RenderTools::GetResolution(height, settings.ResolvePassResolution);
const int32 colorBufferWidth = width / 2;
const int32 colorBufferHeight = height / 2;
const int32 temporalWidth = width;
const int32 temporalHeight = height;
const int32 colorBufferWidth = RenderTools::GetResolution(width, ResolutionMode::Half);
const int32 colorBufferHeight = RenderTools::GetResolution(height, ResolutionMode::Half);
const int32 temporalWidth = resolveWidth;
const int32 temporalHeight = resolveHeight;
const auto colorBufferMips = MipLevelsCount(colorBufferWidth, colorBufferHeight);
// Prepare buffers
auto tempDesc = GPUTextureDescription::New2D(width / 2, height / 2, 0, PixelFormat::R11G11B10_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews);
auto tempDesc = GPUTextureDescription::New2D(colorBufferWidth, colorBufferHeight, 0, PixelFormat::R11G11B10_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews);
auto colorBuffer0 = RenderTargetPool::Get(tempDesc);
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
@@ -305,9 +305,8 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
// and improves resolve pass performance (faster color texture lookups, less cache misses)
// Also for high surface roughness values it adds more blur to the reflection tail which looks more realistic.
const auto filterMode = MultiScaler::FilterMode::GaussianBlur9;
// Downscale with gaussian blur
auto filterMode = PLATFORM_ANDROID || PLATFORM_IOS || PLATFORM_SWITCH ? MultiScaler::FilterMode::GaussianBlur5 : MultiScaler::FilterMode::GaussianBlur9;
for (int32 mipLevel = 1; mipLevel < colorBufferMips; mipLevel++)
{
const int32 mipWidth = Math::Max(colorBufferWidth >> mipLevel, 1);

View File

@@ -81,6 +81,9 @@ int2 PerPassFullResCoordOffset;
int PassIndex;
float EffectMaxDistance;
float3 Padding0;
uint InputDepthScale;
float2 Viewport2xPixelSize;
float2 Viewport2xPixelSize_x_025; // Viewport2xPixelSize * 0.25 (for fusing add+mul into mad)
@@ -213,7 +216,7 @@ void PS_PrepareDepths(in float4 inPos : SV_POSITION, out float out0 : SV_Target0
float b = depths.z;
float a = depths.w;
#else
int3 baseCoord = int3(int2(inPos.xy) * 2, 0);
int3 baseCoord = int3(int2(inPos.xy) * InputDepthScale, 0);
float c = g_DepthSource.Load(baseCoord, int2(0, 1)).x;
float d = g_DepthSource.Load(baseCoord, int2(1, 1)).x;
float b = g_DepthSource.Load(baseCoord, int2(1, 0)).x;
@@ -230,7 +233,7 @@ void PS_PrepareDepths(in float4 inPos : SV_POSITION, out float out0 : SV_Target0
META_PS(true, FEATURE_LEVEL_ES2)
void PS_PrepareDepthsHalf(in float4 inPos : SV_POSITION, out float out0 : SV_Target0, out float out1 : SV_Target1)
{
int3 baseCoord = int3(int2(inPos.xy) * 2, 0);
int3 baseCoord = int3(int2(inPos.xy) * InputDepthScale, 0);
float a = g_DepthSource.Load(baseCoord, int2(0, 0)).x;
float d = g_DepthSource.Load(baseCoord, int2(1, 1)).x;

View File

@@ -58,9 +58,11 @@ float3 ScreenSpaceReflectionDirection(float2 uv, GBufferSample gBuffer, float3 v
// 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)
{
#ifndef SSR_SKIP_INVALID_CHECK
// Reject invalid pixels
if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT || gBuffer.Roughness > roughnessThreshold || gBuffer.ViewPos.z > drawDistance)
return 0;
#endif
// Calculate view space normal vector
float3 normalVS = mul(gBuffer.Normal, (float3x3)viewMatrix);

View File

@@ -1,5 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#define SSR_SKIP_INVALID_CHECK 1
#include "./Flax/Common.hlsl"
#include "./Flax/LightingCommon.hlsl"
#include "./Flax/ReflectionsCommon.hlsl"
@@ -13,7 +15,7 @@
#define SSR_REDUCE_HIGHLIGHTS 1
// Enable/disable blurring SSR during sampling results and mixing with reflections buffer
#define SSR_MIX_BLUR 1
#define SSR_MIX_BLUR (!defined(PLATFORM_ANDROID) && !defined(PLATFORM_IOS) && !defined(PLATFORM_SWITCH))
META_CB_BEGIN(0, Data)
GBufferData GBuffer;
@@ -273,13 +275,11 @@ float4 PS_MixPass(Quad_VS2PS input) : SV_Target0
float4 ssr = Texture0.SampleLevel(SamplerLinearClamp, input.TexCoord, 0);
#if SSR_MIX_BLUR
ssr += Texture0.SampleLevel(SamplerLinearClamp, input.TexCoord + float2(0, SSRtexelSize.y), 0);
ssr += Texture0.SampleLevel(SamplerLinearClamp, input.TexCoord - float2(0, SSRtexelSize.y), 0);
ssr += Texture0.SampleLevel(SamplerLinearClamp, input.TexCoord + float2(SSRtexelSize.x, 0), 0);
ssr += Texture0.SampleLevel(SamplerLinearClamp, input.TexCoord - float2(SSRtexelSize.x, 0), 0);
ssr *= (1.0f / 5.0f);
#endif
ssr.a = saturate(ssr.a);