408 lines
14 KiB
C++
408 lines
14 KiB
C++
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
|
|
|
#include "ScreenSpaceReflectionsPass.h"
|
|
#include "ReflectionsPass.h"
|
|
#include "GBufferPass.h"
|
|
#include "RenderList.h"
|
|
#include "Engine/Content/Content.h"
|
|
#include "Engine/Graphics/Graphics.h"
|
|
#include "Engine/Graphics/RenderTools.h"
|
|
#include "Engine/Graphics/RenderTargetPool.h"
|
|
#include "Engine/Graphics/RenderBuffers.h"
|
|
#include "Engine/Engine/Time.h"
|
|
#include "Engine/Platform/Window.h"
|
|
#include "Utils/MultiScaler.h"
|
|
#include "Engine/Engine/Engine.h"
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
|
|
#define REFLECTIONS_FORMAT PixelFormat::R11G11B10_Float
|
|
#define RESOLVE_PASS_OUTPUT_FORMAT PixelFormat::R16G16B16A16_Float
|
|
|
|
// Shader input texture slots mapping
|
|
#define TEXTURE0 4
|
|
#define TEXTURE1 5
|
|
#define TEXTURE2 6
|
|
|
|
PACK_STRUCT(struct Data
|
|
{
|
|
GBufferData GBuffer;
|
|
|
|
float MaxColorMiplevel;
|
|
float TraceSizeMax;
|
|
float MaxTraceSamples;
|
|
float RoughnessFade;
|
|
|
|
Float2 SSRtexelSize;
|
|
float TemporalTime;
|
|
float BRDFBias;
|
|
|
|
float WorldAntiSelfOcclusionBias;
|
|
float EdgeFadeFactor;
|
|
float TemporalResponse;
|
|
float TemporalScale;
|
|
|
|
float RayTraceStep;
|
|
float TemporalEffect;
|
|
float Intensity;
|
|
float FadeOutDistance;
|
|
|
|
Matrix ViewMatrix;
|
|
Matrix ViewProjectionMatrix;
|
|
});
|
|
|
|
ScreenSpaceReflectionsPass::ScreenSpaceReflectionsPass()
|
|
: _psRayTracePass(nullptr)
|
|
, _psCombinePass(nullptr)
|
|
, _psTemporalPass(nullptr)
|
|
, _psMixPass(nullptr)
|
|
{
|
|
}
|
|
|
|
bool ScreenSpaceReflectionsPass::NeedMotionVectors(RenderContext& renderContext)
|
|
{
|
|
auto& settings = renderContext.List->Settings.ScreenSpaceReflections;
|
|
return settings.TemporalEffect && renderContext.View.Flags & ViewFlags::SSR;
|
|
}
|
|
|
|
String ScreenSpaceReflectionsPass::ToString() const
|
|
{
|
|
return TEXT("ScreenSpaceReflectionsPass");
|
|
}
|
|
|
|
bool ScreenSpaceReflectionsPass::Init()
|
|
{
|
|
// Create pipeline states
|
|
_psRayTracePass = GPUDevice::Instance->CreatePipelineState();
|
|
_psCombinePass = GPUDevice::Instance->CreatePipelineState();
|
|
_psResolvePass.CreatePipelineStates();
|
|
_psTemporalPass = GPUDevice::Instance->CreatePipelineState();
|
|
_psMixPass = GPUDevice::Instance->CreatePipelineState();
|
|
|
|
// Load assets
|
|
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/SSR"));
|
|
_preIntegratedGF = Content::LoadAsyncInternal<Texture>(PRE_INTEGRATED_GF_ASSET_NAME);
|
|
if (_shader == nullptr || _preIntegratedGF == nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
#if COMPILE_WITH_DEV_ENV
|
|
_shader.Get()->OnReloading.Bind<ScreenSpaceReflectionsPass, &ScreenSpaceReflectionsPass::OnShaderReloading>(this);
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScreenSpaceReflectionsPass::setupResources()
|
|
{
|
|
// Wait for the assets
|
|
if (!_preIntegratedGF->IsLoaded())
|
|
return true;
|
|
|
|
// Shader
|
|
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 stages
|
|
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
|
if (!_psRayTracePass->IsValid())
|
|
{
|
|
psDesc.PS = shader->GetPS("PS_RayTracePass");
|
|
if (_psRayTracePass->Init(psDesc))
|
|
return true;
|
|
}
|
|
if (!_psCombinePass->IsValid())
|
|
{
|
|
psDesc.PS = shader->GetPS("PS_CombinePass");
|
|
if (_psCombinePass->Init(psDesc))
|
|
return true;
|
|
}
|
|
if (!_psResolvePass.IsValid())
|
|
{
|
|
if (_psResolvePass.Create(psDesc, shader, "PS_ResolvePass"))
|
|
return true;
|
|
}
|
|
if (!_psTemporalPass->IsValid())
|
|
{
|
|
psDesc.PS = shader->GetPS("PS_TemporalPass");
|
|
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;
|
|
}
|
|
|
|
void ScreenSpaceReflectionsPass::Dispose()
|
|
{
|
|
// Base
|
|
RendererPass::Dispose();
|
|
|
|
// Cleanup
|
|
SAFE_DELETE_GPU_RESOURCE(_psRayTracePass);
|
|
SAFE_DELETE_GPU_RESOURCE(_psCombinePass);
|
|
SAFE_DELETE_GPU_RESOURCE(_psTemporalPass);
|
|
SAFE_DELETE_GPU_RESOURCE(_psMixPass);
|
|
_psResolvePass.Delete();
|
|
_shader = nullptr;
|
|
_preIntegratedGF = nullptr;
|
|
}
|
|
|
|
void ScreenSpaceReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* reflectionsRT, GPUTextureView* lightBuffer)
|
|
{
|
|
// Skip pass if resources aren't ready
|
|
if (checkIfSkipPass())
|
|
return;
|
|
auto& view = renderContext.View;
|
|
auto buffers = renderContext.Buffers;
|
|
|
|
// TODO: add support for SSR in ortho projection
|
|
if (view.IsOrthographicProjection())
|
|
return;
|
|
|
|
PROFILE_GPU_CPU("Screen Space Reflections");
|
|
|
|
// Cache data
|
|
auto device = GPUDevice::Instance;
|
|
auto context = device->GetMainContext();
|
|
const auto shader = _shader->GetShader();
|
|
auto cb = shader->GetCB(0);
|
|
auto& settings = renderContext.List->Settings.ScreenSpaceReflections;
|
|
const bool useTemporal = settings.TemporalEffect && !renderContext.Task->IsCameraCut;
|
|
|
|
// Prepare resolutions for passes
|
|
const int32 width = buffers->GetWidth();
|
|
const int32 height = buffers->GetHeight();
|
|
const int32 traceWidth = width / static_cast<int32>(settings.RayTracePassResolution);
|
|
const int32 traceHeight = height / static_cast<int32>(settings.RayTracePassResolution);
|
|
const int32 resolveWidth = width / static_cast<int32>(settings.RayTracePassResolution);
|
|
const int32 resolveHeight = height / static_cast<int32>(settings.RayTracePassResolution);
|
|
const int32 colorBufferWidth = width / 2;
|
|
const int32 colorBufferHeight = height / 2;
|
|
const int32 temporalWidth = width;
|
|
const int32 temporalHeight = height;
|
|
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 colorBuffer0 = RenderTargetPool::Get(tempDesc);
|
|
// 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);
|
|
tempDesc = GPUTextureDescription::New2D(traceWidth, traceHeight, REFLECTIONS_FORMAT);
|
|
auto traceBuffer = RenderTargetPool::Get(tempDesc);
|
|
tempDesc = GPUTextureDescription::New2D(resolveWidth, resolveHeight, RESOLVE_PASS_OUTPUT_FORMAT);
|
|
auto resolveBuffer = RenderTargetPool::Get(tempDesc);
|
|
|
|
// Pick effect settings
|
|
int32 maxTraceSamples = 60;
|
|
switch (Graphics::SSRQuality)
|
|
{
|
|
case Quality::Low:
|
|
maxTraceSamples = 20;
|
|
break;
|
|
case Quality::Medium:
|
|
maxTraceSamples = 55;
|
|
break;
|
|
case Quality::High:
|
|
maxTraceSamples = 70;
|
|
break;
|
|
case Quality::Ultra:
|
|
maxTraceSamples = 120;
|
|
break;
|
|
}
|
|
const int32 resolveSamples = settings.ResolveSamples;
|
|
int32 resolvePassIndex = 0;
|
|
if (resolveSamples >= 8)
|
|
resolvePassIndex = 3;
|
|
else if (resolveSamples >= 4)
|
|
resolvePassIndex = 2;
|
|
else if (resolveSamples >= 2)
|
|
resolvePassIndex = 1;
|
|
|
|
// Setup data
|
|
Data data;
|
|
GBufferPass::SetInputs(view, data.GBuffer);
|
|
data.RoughnessFade = Math::Saturate(settings.RoughnessThreshold);
|
|
data.MaxTraceSamples = static_cast<float>(maxTraceSamples);
|
|
data.BRDFBias = settings.BRDFBias;
|
|
data.WorldAntiSelfOcclusionBias = settings.WorldAntiSelfOcclusionBias;
|
|
data.EdgeFadeFactor = settings.EdgeFadeFactor;
|
|
data.SSRtexelSize = Vector2(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;
|
|
data.Intensity = settings.Intensity;
|
|
data.FadeOutDistance = Math::Max(settings.FadeOutDistance, 100.0f);
|
|
data.TemporalScale = settings.TemporalScale;
|
|
data.TemporalResponse = settings.TemporalResponse;
|
|
data.TemporalEffect = useTemporal ? 1.0f : 0.0f;
|
|
if (useTemporal)
|
|
{
|
|
const float time = Time::Draw.UnscaledTime.GetTotalSeconds();
|
|
|
|
// Keep time in smaller range to prevent temporal noise errors
|
|
const double scale = 10;
|
|
const double integral = round(time / scale) * scale;
|
|
data.TemporalTime = static_cast<float>(time - integral);
|
|
|
|
buffers->LastFrameTemporalSSR = Engine::FrameCount;
|
|
if (buffers->TemporalSSR == nullptr)
|
|
{
|
|
// Missing temporal buffer
|
|
tempDesc = GPUTextureDescription::New2D(temporalWidth, temporalHeight, RESOLVE_PASS_OUTPUT_FORMAT);
|
|
buffers->TemporalSSR = RenderTargetPool::Get(tempDesc);
|
|
}
|
|
else if (buffers->TemporalSSR->Width() != temporalWidth || buffers->TemporalSSR->Height() != temporalHeight)
|
|
{
|
|
// Wrong size temporal buffer
|
|
RenderTargetPool::Release(buffers->TemporalSSR);
|
|
tempDesc = GPUTextureDescription::New2D(temporalWidth, temporalHeight, RESOLVE_PASS_OUTPUT_FORMAT);
|
|
buffers->TemporalSSR = RenderTargetPool::Get(tempDesc);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
data.TemporalTime = 0;
|
|
}
|
|
Matrix::Transpose(view.View, data.ViewMatrix);
|
|
Matrix::Transpose(view.ViewProjection(), data.ViewProjectionMatrix);
|
|
|
|
// Check if resize depth
|
|
GPUTexture* originalDepthBuffer = buffers->DepthBuffer;
|
|
GPUTexture* smallerDepthBuffer = originalDepthBuffer;
|
|
if (settings.DepthResolution != ResolutionMode::Full)
|
|
{
|
|
// Smaller depth buffer improves ray tracing performance
|
|
smallerDepthBuffer = buffers->RequestHalfResDepth(context);
|
|
}
|
|
|
|
// Prepare constants
|
|
context->UpdateCB(cb, &data);
|
|
context->BindCB(0, cb);
|
|
|
|
// Bind GBuffer inputs
|
|
context->BindSR(0, buffers->GBuffer0);
|
|
context->BindSR(1, buffers->GBuffer1);
|
|
context->BindSR(2, buffers->GBuffer2);
|
|
context->BindSR(3, smallerDepthBuffer);
|
|
|
|
// Combine pass
|
|
context->BindSR(TEXTURE0, lightBuffer);
|
|
context->BindSR(TEXTURE1, reflectionsRT);
|
|
context->BindSR(TEXTURE2, _preIntegratedGF->GetTexture());
|
|
context->SetViewportAndScissors((float)colorBufferWidth, (float)colorBufferHeight);
|
|
context->SetRenderTarget(colorBuffer0->View(0));
|
|
context->SetState(_psCombinePass);
|
|
context->DrawFullscreenTriangle();
|
|
context->UnBindSR(TEXTURE1);
|
|
context->UnBindSR(TEXTURE2);
|
|
context->ResetRenderTarget();
|
|
|
|
// Blur Pass
|
|
GPUTexture* blurPassBuffer;
|
|
if (settings.UseColorBufferMips)
|
|
{
|
|
// Note: using color buffer mips maps helps with reducing artifacts
|
|
// 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
|
|
for (int32 mipLevel = 1; mipLevel < colorBufferMips; mipLevel++)
|
|
{
|
|
const int32 mipWidth = Math::Max(colorBufferWidth >> mipLevel, 1);
|
|
const int32 mipHeight = Math::Max(colorBufferHeight >> mipLevel, 1);
|
|
|
|
const auto srcMip = colorBuffer0->View(0, mipLevel - 1);
|
|
const auto tmpMip = colorBuffer1->View(0, mipLevel);
|
|
const auto dstMip = colorBuffer0->View(0, mipLevel);
|
|
|
|
MultiScaler::Instance()->Filter(filterMode, context, mipWidth, mipHeight, srcMip, dstMip, tmpMip);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Ray Trace Pass
|
|
context->SetViewportAndScissors((float)traceWidth, (float)traceHeight);
|
|
context->SetRenderTarget(*traceBuffer);
|
|
context->SetState(_psRayTracePass);
|
|
context->DrawFullscreenTriangle();
|
|
context->ResetRenderTarget();
|
|
|
|
// Resolve Pass
|
|
context->SetRenderTarget(resolveBuffer->View());
|
|
context->BindSR(TEXTURE0, blurPassBuffer->View());
|
|
context->BindSR(TEXTURE1, traceBuffer->View());
|
|
context->SetState(_psResolvePass.Get(resolvePassIndex));
|
|
context->DrawFullscreenTriangle();
|
|
context->ResetRenderTarget();
|
|
|
|
// Temporal Pass
|
|
GPUTexture* reflectionsBuffer = resolveBuffer;
|
|
if (useTemporal)
|
|
{
|
|
tempDesc = GPUTextureDescription::New2D(temporalWidth, temporalHeight, RESOLVE_PASS_OUTPUT_FORMAT);
|
|
auto newTemporal = RenderTargetPool::Get(tempDesc);
|
|
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();
|
|
context->ResetRenderTarget();
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|