Optimize SSR a bit more

This commit is contained in:
2026-01-20 22:05:52 +01:00
parent cc5e4c19e1
commit d7458d81a5
6 changed files with 118 additions and 127 deletions
+36 -10
View File
@@ -18,6 +18,8 @@ GPU_CB_STRUCT(Data {
ShaderEnvProbeData PData;
Matrix WVP;
ShaderGBufferData GBuffer;
Float2 SSRTexelSize;
Float2 Dummy0;
});
#if GENERATE_GF_CACHE
@@ -229,6 +231,7 @@ void ReflectionsPass::Dispose()
SAFE_DELETE_GPU_RESOURCE(_psProbe);
SAFE_DELETE_GPU_RESOURCE(_psProbeInside);
SAFE_DELETE_GPU_RESOURCE(_psCombinePass);
SAFE_DELETE_GPU_RESOURCE(_psDrawSSR);
_shader = nullptr;
_boxModel = nullptr;
_sphereModel = nullptr;
@@ -279,8 +282,12 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
PROFILE_GPU_CPU("Reflections");
// Setup data
const int32 width = renderContext.Buffers->GetWidth();
const int32 height = renderContext.Buffers->GetHeight();
Data data;
GBufferPass::SetInputs(view, data.GBuffer);
auto& ssrSettings = renderContext.List->Settings.ScreenSpaceReflections;
data.SSRTexelSize = Float2(1.0f / (float)RenderTools::GetResolution(width, ssrSettings.ResolvePassResolution), 1.0f / (float)RenderTools::GetResolution(height, ssrSettings.ResolvePassResolution));
// Bind GBuffer inputs
GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer;
@@ -358,14 +365,40 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
}
// Screen Space Reflections pass
GPUTexture* ssrBuffer = nullptr;
if (useSSR)
{
ScreenSpaceReflectionsPass::Instance()->Render(renderContext, *reflectionsBuffer, lightBuffer);
ssrBuffer = ScreenSpaceReflectionsPass::Instance()->Render(renderContext, *reflectionsBuffer, lightBuffer);
// Restore
context->BindSR(0, renderContext.Buffers->GBuffer0);
context->BindSR(1, renderContext.Buffers->GBuffer1);
context->BindSR(2, renderContext.Buffers->GBuffer2);
context->BindSR(3, renderContext.Buffers->DepthBuffer);
context->SetViewportAndScissors(renderContext.Task->GetViewport());
}
if (renderContext.View.Mode == ViewMode::Reflections)
{
// If SSR is in use, then draw it with alpha blending into reflections buffer
if (ssrBuffer)
{
if (!_psDrawSSR)
{
_psDrawSSR = GPUDevice::Instance->CreatePipelineState();
auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
psDesc.BlendMode = BlendingMode::AlphaBlend;
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
psDesc.PS = GPUDevice::Instance->QuadShader->GetPS("PS_CopyLinear");
_psDrawSSR->Init(psDesc);
}
context->SetRenderTarget(*reflectionsBuffer);
context->BindSR(0, ssrBuffer);
context->SetState(_psDrawSSR);
context->DrawFullscreenTriangle();
context->ResetRenderTarget();
}
// Override light buffer with the reflections buffer
context->SetRenderTarget(lightBuffer);
context->Draw(reflectionsBuffer);
@@ -376,21 +409,14 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
context->SetRenderTarget(lightBuffer);
context->BindCB(0, cb);
if (probesCount == 0 || !renderProbes)
{
context->UpdateCB(cb, &data);
}
if (useSSR)
{
context->BindSR(0, renderContext.Buffers->GBuffer0);
context->BindSR(1, renderContext.Buffers->GBuffer1);
context->BindSR(2, renderContext.Buffers->GBuffer2);
context->BindSR(3, renderContext.Buffers->DepthBuffer);
}
context->BindSR(5, reflectionsBuffer);
context->BindSR(6, _preIntegratedGF->GetTexture());
context->BindSR(7, ssrBuffer);
context->SetState(_psCombinePass);
context->DrawFullscreenTriangle();
}
RenderTargetPool::Release(ssrBuffer);
RenderTargetPool::Release(reflectionsBuffer);
}
+1
View File
@@ -20,6 +20,7 @@ private:
GPUPipelineState* _psProbe = nullptr;
GPUPipelineState* _psProbeInside = nullptr;
GPUPipelineState* _psCombinePass = nullptr;
GPUPipelineState* _psDrawSSR = nullptr;
AssetReference<Model> _sphereModel;
AssetReference<Model> _boxModel;
@@ -6,16 +6,16 @@
#include "RenderList.h"
#include "GlobalSignDistanceFieldPass.h"
#include "GI/GlobalSurfaceAtlasPass.h"
#include "Utils/MultiScaler.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Platform/Window.h"
#include "Engine/Content/Content.h"
#include "Engine/Graphics/Graphics.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Platform/Window.h"
#include "Utils/MultiScaler.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/RenderTask.h"
// Shader input texture slots mapping
#define TEXTURE0 4
@@ -57,7 +57,6 @@ bool ScreenSpaceReflectionsPass::Init()
_psResolvePass.CreatePipelineStates();
_psCombinePass = GPUDevice::Instance->CreatePipelineState();
_psTemporalPass = GPUDevice::Instance->CreatePipelineState();
_psMixPass = GPUDevice::Instance->CreatePipelineState();
// Load assets
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/SSR"));
@@ -75,11 +74,8 @@ bool ScreenSpaceReflectionsPass::Init()
bool ScreenSpaceReflectionsPass::setupResources()
{
// Wait for the assets
if (!_preIntegratedGF->IsLoaded())
return true;
// Shader
if (!_shader->IsLoaded())
return true;
const auto shader = _shader->GetShader();
@@ -109,13 +105,6 @@ bool ScreenSpaceReflectionsPass::setupResources()
if (_psTemporalPass->Init(psDesc))
return true;
}
if (!_psMixPass->IsValid())
{
psDesc.BlendMode = BlendingMode::AlphaBlend;
psDesc.PS = shader->GetPS("PS_MixPass");
if (_psMixPass->Init(psDesc))
return true;
}
return false;
}
@@ -128,24 +117,23 @@ void ScreenSpaceReflectionsPass::Dispose()
// Cleanup
SAFE_DELETE_GPU_RESOURCE(_psCombinePass);
SAFE_DELETE_GPU_RESOURCE(_psTemporalPass);
SAFE_DELETE_GPU_RESOURCE(_psMixPass);
_psRayTracePass.Delete();
_psResolvePass.Delete();
_shader = nullptr;
_preIntegratedGF = nullptr;
}
void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* reflectionsRT, GPUTextureView* lightBuffer)
GPUTexture* ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* reflectionsRT, GPUTextureView* lightBuffer)
{
// Skip pass if resources aren't ready
if (checkIfSkipPass())
return;
return nullptr;
const RenderView& view = renderContext.View;
RenderBuffers* buffers = renderContext.Buffers;
// TODO: add support for SSR in ortho projection
if (view.IsOrthographicProjection())
return;
return nullptr;
PROFILE_GPU_CPU("Screen Space Reflections");
@@ -155,30 +143,40 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
const auto shader = _shader->GetShader();
auto cb = shader->GetCB(0);
auto& settings = renderContext.List->Settings.ScreenSpaceReflections;
const bool useTemporal = settings.TemporalEffect && !renderContext.Task->IsCameraCut;
const bool useTemporal = settings.TemporalEffect && !renderContext.Task->IsCameraCut && renderContext.List->Setup.UseMotionVectors;
// Prepare resolutions for passes
const int32 width = buffers->GetWidth();
const int32 height = buffers->GetHeight();
if (width < 4 || height < 4)
return;
return nullptr;
const int32 traceWidth = RenderTools::GetResolution(width, settings.RayTracePassResolution);
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 = 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(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
auto colorBuffer1 = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(colorBuffer1, "SSR.ColorBuffer1");
GPUTexture* colorBuffer0, *colorBuffer1;
if (settings.UseColorBufferMips)
{
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
colorBuffer1 = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(colorBuffer1, "SSR.ColorBuffer1");
}
else
{
// Single mip
tempDesc.MipLevels = 1;
tempDesc.Flags &= ~GPUTextureFlags::PerMipViews;
colorBuffer0 = RenderTargetPool::Get(tempDesc);
colorBuffer1 = nullptr;
}
tempDesc = GPUTextureDescription::New2D(traceWidth, traceHeight, PixelFormat::R16G16B16A16_Float);
auto traceBuffer = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(traceBuffer, "SSR.TraceBuffer");
@@ -220,7 +218,7 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
data.BRDFBias = settings.BRDFBias;
data.WorldAntiSelfOcclusionBias = settings.WorldAntiSelfOcclusionBias;
data.EdgeFadeFactor = settings.EdgeFadeFactor;
data.SSRtexelSize = Vector2(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.MaxColorMiplevel = settings.UseColorBufferMips ? (float)colorBufferMips - 2.0f : 0.0f;
data.RayTraceStep = static_cast<float>(settings.DepthResolution) / (float)width;
@@ -228,24 +226,7 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
data.FadeOutDistance = Math::Max(settings.FadeOutDistance, 100.0f);
data.TemporalResponse = settings.TemporalResponse;
data.TemporalEffect = useTemporal ? 1.0f : 0.0f;
if (useTemporal)
{
data.TemporalTime = RenderTools::ComputeTemporalTime();
buffers->LastFrameTemporalSSR = Engine::FrameCount;
if (!buffers->TemporalSSR || buffers->TemporalSSR->Width() != temporalWidth || buffers->TemporalSSR->Height() != temporalHeight)
{
// Wrong size temporal buffer
if (buffers->TemporalSSR)
RenderTargetPool::Release(buffers->TemporalSSR);
tempDesc = GPUTextureDescription::New2D(temporalWidth, temporalHeight, PixelFormat::R16G16B16A16_Float);
buffers->TemporalSSR = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(buffers->TemporalSSR, "SSR.TemporalSSR");
}
}
else
{
data.TemporalTime = 0;
}
data.TemporalTime = useTemporal ? RenderTools::ComputeTemporalTime() : 0;
Matrix::Transpose(view.View, data.ViewMatrix);
Matrix::Transpose(view.ViewProjection(), data.ViewProjectionMatrix);
@@ -298,7 +279,6 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
context->ResetRenderTarget();
// Blur Pass
GPUTexture* blurPassBuffer;
if (settings.UseColorBufferMips)
{
// Note: using color buffer mips maps helps with reducing artifacts
@@ -322,20 +302,13 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
// Restore state
context->BindCB(0, cb);
context->BindSR(0, buffers->GBuffer0);
// Use color buffer with full mip chain
blurPassBuffer = colorBuffer0;
}
else
{
// Don't use color buffer with mip maps
blurPassBuffer = colorBuffer0;
}
RenderTargetPool::Release(colorBuffer1);
// Ray Trace Pass
context->SetViewportAndScissors((float)traceWidth, (float)traceHeight);
context->SetRenderTarget(*traceBuffer);
context->BindSR(TEXTURE0, blurPassBuffer->View());
context->BindSR(TEXTURE0, colorBuffer0->View());
if (useGlobalSurfaceAtlas)
{
context->BindSR(7, bindingDataSDF.Texture ? bindingDataSDF.Texture->ViewVolume() : nullptr);
@@ -349,6 +322,7 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
context->SetState(_psRayTracePass.Get(useGlobalSurfaceAtlas ? 1 : 0));
context->DrawFullscreenTriangle();
context->ResetRenderTarget();
RenderTargetPool::Release(colorBuffer0);
// Resolve Pass
context->SetViewportAndScissors((float)resolveWidth, (float)resolveHeight);
@@ -357,50 +331,49 @@ void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTexture
context->SetState(_psResolvePass.Get(resolvePassIndex));
context->DrawFullscreenTriangle();
context->ResetRenderTarget();
RenderTargetPool::Release(traceBuffer);
// Temporal Pass
GPUTexture* reflectionsBuffer = resolveBuffer;
if (useTemporal)
{
tempDesc = GPUTextureDescription::New2D(temporalWidth, temporalHeight, PixelFormat::R16G16B16A16_Float);
auto newTemporal = RenderTargetPool::Get(tempDesc);
buffers->LastFrameTemporalSSR = Engine::FrameCount;
bool resetHistory = false;
if (!buffers->TemporalSSR || buffers->TemporalSSR->Width() != resolveWidth || buffers->TemporalSSR->Height() != resolveHeight)
{
resetHistory = true;
if (buffers->TemporalSSR)
RenderTargetPool::Release(buffers->TemporalSSR);
tempDesc = GPUTextureDescription::New2D(resolveWidth, resolveHeight, PixelFormat::R16G16B16A16_Float);
buffers->TemporalSSR = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(buffers->TemporalSSR, "SSR.TemporalSSR");
}
auto newTemporal = RenderTargetPool::Get(buffers->TemporalSSR->GetDescription());
RENDER_TARGET_POOL_SET_NAME(newTemporal, "SSR.TemporalSSR");
const auto oldTemporal = buffers->TemporalSSR;
const auto motionVectors = buffers->MotionVectors;
context->SetViewportAndScissors((float)temporalWidth, (float)temporalHeight);
context->SetRenderTarget(newTemporal->View());
context->BindSR(TEXTURE0, resolveBuffer);
context->BindSR(TEXTURE1, oldTemporal);
context->BindSR(TEXTURE2, motionVectors && motionVectors->IsAllocated() ? motionVectors->View() : nullptr);
context->SetState(_psTemporalPass);
context->DrawFullscreenTriangle();
if (resetHistory)
{
context->Draw(newTemporal, resolveBuffer);
}
else
{
context->SetRenderTarget(newTemporal->View());
context->BindSR(TEXTURE0, resolveBuffer);
context->BindSR(TEXTURE1, buffers->TemporalSSR);
context->BindSR(TEXTURE2, buffers->MotionVectors && buffers->MotionVectors->IsAllocated() ? buffers->MotionVectors->View() : nullptr);
context->SetState(_psTemporalPass);
context->DrawFullscreenTriangle();
}
context->ResetRenderTarget();
context->UnBindSR(TEXTURE1);
context->UnBindSR(TEXTURE2);
// TODO: if those 2 buffers are the same maybe we could swap them internally to prevent data copy?
context->CopyResource(oldTemporal, newTemporal);
RenderTargetPool::Release(newTemporal);
reflectionsBuffer = oldTemporal;
RenderTargetPool::Release(resolveBuffer);
context->CopyResource(buffers->TemporalSSR, newTemporal);
reflectionsBuffer = newTemporal;
}
context->UnBindSR(TEXTURE1);
// Mix Pass
context->SetViewportAndScissors((float)width, (float)height);
context->BindSR(TEXTURE0, reflectionsBuffer);
context->SetRenderTarget(reflectionsRT);
context->SetState(_psMixPass);
context->DrawFullscreenTriangle();
context->ResetRenderTarget();
// Cleanup
RenderTargetPool::Release(colorBuffer0);
RenderTargetPool::Release(colorBuffer1);
RenderTargetPool::Release(traceBuffer);
RenderTargetPool::Release(resolveBuffer);
return reflectionsBuffer;
}
#if COMPILE_WITH_DEV_ENV
@@ -409,7 +382,6 @@ void ScreenSpaceReflectionsPass::OnShaderReloading(Asset* obj)
{
_psCombinePass->ReleaseGPU();
_psTemporalPass->ReleaseGPU();
_psMixPass->ReleaseGPU();
_psRayTracePass.Release();
_psResolvePass.Release();
invalidateResources();
@@ -30,8 +30,6 @@ private:
GPUPipelineStatePermutationsPs<4> _psResolvePass;
GPUPipelineState* _psCombinePass = nullptr;
GPUPipelineState* _psTemporalPass = nullptr;
GPUPipelineState* _psMixPass = nullptr;
AssetReference<Shader> _shader;
AssetReference<Texture> _preIntegratedGF;
@@ -42,7 +40,8 @@ public:
/// <param name="renderContext">The rendering context.</param>
/// <param name="reflectionsRT">Temporary buffer to use for the reflections pass</param>
/// <param name="lightBuffer">Light buffer</param>
void Render(RenderContext& renderContext, GPUTextureView* reflectionsRT, GPUTextureView* lightBuffer);
/// <remarks>Result buffer with SSR contents to mix (null if not rendered). Free via RenderTargetPool after use.</remarks>
GPUTexture* Render(RenderContext& renderContext, GPUTextureView* reflectionsRT, GPUTextureView* lightBuffer);
private:
#if COMPILE_WITH_DEV_ENV
+17
View File
@@ -7,10 +7,15 @@
#include "./Flax/MaterialCommon.hlsl"
#include "./Flax/ReflectionsCommon.hlsl"
// Enable/disable blurring SSR during sampling results and mixing with reflections buffer
#define SSR_MIX_BLUR (!defined(PLATFORM_ANDROID) && !defined(PLATFORM_IOS) && !defined(PLATFORM_SWITCH))
META_CB_BEGIN(0, Data)
EnvProbeData PData;
float4x4 WVP;
GBufferData GBuffer;
float2 SSRTexelSize;
float2 Dummy0;
META_CB_END
DECLARE_GBUFFERDATA_ACCESS(GBuffer)
@@ -18,6 +23,7 @@ DECLARE_GBUFFERDATA_ACCESS(GBuffer)
TextureCube Probe : register(t4);
Texture2D Reflections : register(t5);
Texture2D PreIntegratedGF : register(t6);
Texture2D SSR : register(t7);
// Vertex Shader for probe shape rendering
META_VS(true, FEATURE_LEVEL_ES2)
@@ -76,6 +82,17 @@ float4 PS_CombinePass(Quad_VS2PS input) : SV_Target0
// Sample reflections buffer
float3 reflections = SAMPLE_RT(Reflections, input.TexCoord).rgb;
// Blend with Screen Space Reflections
float4 ssr = SSR.SampleLevel(SamplerLinearClamp, input.TexCoord, 0);
#if SSR_MIX_BLUR
ssr += SSR.SampleLevel(SamplerLinearClamp, input.TexCoord + float2(0, SSRTexelSize.y), 0);
ssr += SSR.SampleLevel(SamplerLinearClamp, input.TexCoord - float2(0, SSRTexelSize.y), 0);
ssr += SSR.SampleLevel(SamplerLinearClamp, input.TexCoord + float2(SSRTexelSize.x, 0), 0);
ssr += SSR.SampleLevel(SamplerLinearClamp, input.TexCoord - float2(SSRTexelSize.x, 0), 0);
ssr *= (1.0f / 5.0f);
#endif
reflections = lerp(reflections, ssr.rgb, saturate(ssr.a));
// Calculate specular color
float3 specularColor = GetSpecularColor(gBuffer);
-24
View File
@@ -14,9 +14,6 @@
// Enable/disable luminance filter to reduce reflections highlights
#define SSR_REDUCE_HIGHLIGHTS 1
// Enable/disable blurring SSR during sampling results and mixing with reflections buffer
#define SSR_MIX_BLUR (!defined(PLATFORM_ANDROID) && !defined(PLATFORM_IOS) && !defined(PLATFORM_SWITCH))
META_CB_BEGIN(0, Data)
GBufferData GBuffer;
float MaxColorMiplevel;
@@ -264,24 +261,3 @@ float4 PS_TemporalPass(Quad_VS2PS input) : SV_Target0
return current;
}
// Pixel Shader for screen space reflections rendering - mix pass
META_PS(true, FEATURE_LEVEL_ES2)
float4 PS_MixPass(Quad_VS2PS input) : SV_Target0
{
// Inputs:
// Texture0 - final SSR reflections buffer
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);
return ssr;
}