You're breathtaking!
This commit is contained in:
572
Source/Engine/Renderer/AmbientOcclusionPass.cpp
Normal file
572
Source/Engine/Renderer/AmbientOcclusionPass.cpp
Normal file
@@ -0,0 +1,572 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "AmbientOcclusionPass.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "RenderList.h"
|
||||
#include "GBufferPass.h"
|
||||
|
||||
AmbientOcclusionPass::ASSAO_Settings::ASSAO_Settings()
|
||||
{
|
||||
Radius = 1.2f;
|
||||
ShadowMultiplier = 1.0f;
|
||||
ShadowPower = 1.50f;
|
||||
HorizonAngleThreshold = 0.06f;
|
||||
FadeOutFrom = 4500.0f;
|
||||
FadeOutTo = 5000.0f;
|
||||
QualityLevel = 2;
|
||||
BlurPassCount = 2;
|
||||
Sharpness = 0.68f;
|
||||
DetailShadowStrength = 0.5f;
|
||||
SkipHalfPixels = false;
|
||||
}
|
||||
|
||||
// Validate config
|
||||
static_assert(SSAO_DEPTH_MIP_LEVELS > 1, "Invalid amount of SSAO cache depth buffer mip levels.");
|
||||
static_assert(SSAO_MAX_BLUR_PASS_COUNT >= 0, "Invalid maximum amount of SSAO blur passes.");
|
||||
|
||||
// Shader Resource slots mapping (must match shader source)
|
||||
#define SSAO_CONSTANTS_BUFFER_SLOT 0
|
||||
#define SSAO_TEXTURE_SLOT0 0
|
||||
#define SSAO_TEXTURE_SLOT1 1
|
||||
#define SSAO_TEXTURE_SLOT2 2
|
||||
#define SSAO_TEXTURE_SLOT3 3
|
||||
|
||||
// Note: to boost performance a little bit we render final AO in full res to GBuffer surface which contains material AO term.
|
||||
#define SSAO_APPLY_OUTPUT_FORMAT GBUFFER2_FORMAT
|
||||
|
||||
AmbientOcclusionPass::AmbientOcclusionPass()
|
||||
{
|
||||
_psPrepareDepths = nullptr;
|
||||
_psPrepareDepthsHalf = nullptr;
|
||||
Platform::MemoryClear(_psPrepareDepthMip, sizeof(_psPrepareDepthMip));
|
||||
Platform::MemoryClear(_psGenerate, sizeof(_psGenerate));
|
||||
_psSmartBlur = nullptr;
|
||||
_psSmartBlurWide = nullptr;
|
||||
_psNonSmartBlur = nullptr;
|
||||
_psApply = nullptr;
|
||||
_psApplyHalf = nullptr;
|
||||
}
|
||||
|
||||
String AmbientOcclusionPass::ToString() const
|
||||
{
|
||||
return TEXT("AmbientOcclusionPass");
|
||||
}
|
||||
|
||||
bool AmbientOcclusionPass::Init()
|
||||
{
|
||||
// Create pipeline states
|
||||
_psPrepareDepths = GPUDevice::Instance->CreatePipelineState();
|
||||
_psPrepareDepthsHalf = GPUDevice::Instance->CreatePipelineState();
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_psPrepareDepthMip); i++)
|
||||
_psPrepareDepthMip[i] = GPUDevice::Instance->CreatePipelineState();
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_psGenerate); i++)
|
||||
_psGenerate[i] = GPUDevice::Instance->CreatePipelineState();
|
||||
_psSmartBlur = GPUDevice::Instance->CreatePipelineState();
|
||||
_psSmartBlurWide = GPUDevice::Instance->CreatePipelineState();
|
||||
_psNonSmartBlur = GPUDevice::Instance->CreatePipelineState();
|
||||
_psApply = GPUDevice::Instance->CreatePipelineState();
|
||||
_psApplyHalf = GPUDevice::Instance->CreatePipelineState();
|
||||
|
||||
// Load shader
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/SSAO"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<AmbientOcclusionPass, &AmbientOcclusionPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AmbientOcclusionPass::setupResources()
|
||||
{
|
||||
char nameBuffer[40];
|
||||
|
||||
// Check shader
|
||||
if (!_shader->IsLoaded())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffer size
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(ASSAOConstants))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, ASSAOConstants);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline states
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
// Prepare Depths
|
||||
if (!_psPrepareDepths->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_PrepareDepths");
|
||||
if (_psPrepareDepths->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psPrepareDepthsHalf->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_PrepareDepthsHalf");
|
||||
if (_psPrepareDepthsHalf->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
// Prepare Depth Mips
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_psPrepareDepthMip); i++)
|
||||
{
|
||||
if (!_psPrepareDepthMip[i]->IsValid())
|
||||
{
|
||||
sprintf(nameBuffer, "PS_PrepareDepthMip%d", i + 1);
|
||||
psDesc.PS = shader->GetPS(nameBuffer);
|
||||
if (_psPrepareDepthMip[i]->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// AO Generate
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_psGenerate); i++)
|
||||
{
|
||||
if (!_psGenerate[i]->IsValid())
|
||||
{
|
||||
sprintf(nameBuffer, "PS_GenerateQ%d", i);
|
||||
psDesc.PS = shader->GetPS(nameBuffer);
|
||||
if (_psGenerate[i]->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Blur
|
||||
if (!_psSmartBlur->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_SmartBlur");
|
||||
if (_psSmartBlur->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psSmartBlurWide->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_SmartBlurWide");
|
||||
if (_psSmartBlurWide->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psNonSmartBlur->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_NonSmartBlur");
|
||||
if (_psNonSmartBlur->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
// Apply AO
|
||||
psDesc.BlendMode = BlendingMode::Multiply;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::Alpha;
|
||||
if (!_psApply->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Apply");
|
||||
if (_psApply->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psApplyHalf->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_ApplyHalf");
|
||||
if (_psApplyHalf->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AmbientOcclusionPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_psPrepareDepths);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psPrepareDepthsHalf);
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_psPrepareDepthMip); i++)
|
||||
SAFE_DELETE_GPU_RESOURCE(_psPrepareDepthMip[i]);
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_psGenerate); i++)
|
||||
SAFE_DELETE_GPU_RESOURCE(_psGenerate[i]);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psSmartBlur);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psSmartBlurWide);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psNonSmartBlur);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psApply);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psApplyHalf);
|
||||
|
||||
// Release asset
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
void AmbientOcclusionPass::Render(RenderContext& renderContext)
|
||||
{
|
||||
// Check if can render the effect
|
||||
if (renderContext.List == nullptr)
|
||||
return;
|
||||
auto& aoSettings = renderContext.List->Settings.AmbientOcclusion;
|
||||
if (aoSettings.Enabled == false || (renderContext.View.Flags & ViewFlags::AO) == 0)
|
||||
return;
|
||||
|
||||
// TODO: add support for SSAO in ortho projection
|
||||
if (renderContext.View.IsOrthographicProjection())
|
||||
return;
|
||||
|
||||
// Ensure to have valid data
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Resources are missing. Do not perform rendering.
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_GPU_CPU("Ambient Occlusion");
|
||||
|
||||
settings.Radius = aoSettings.Radius * 0.006f;
|
||||
settings.ShadowMultiplier = aoSettings.Intensity;
|
||||
settings.ShadowPower = aoSettings.Power;
|
||||
settings.FadeOutTo = aoSettings.FadeOutDistance;
|
||||
settings.FadeOutFrom = aoSettings.FadeOutDistance - aoSettings.FadeDistance;
|
||||
|
||||
// expose param for HorizonAngleThreshold ?
|
||||
// expose param for Sharpness ?
|
||||
// expose param for DetailShadowStrength ?
|
||||
|
||||
// Apply quality level ASSAO to the settings
|
||||
switch (Graphics::SSAOQuality)
|
||||
{
|
||||
case Quality::Low:
|
||||
{
|
||||
settings.QualityLevel = 1;
|
||||
settings.BlurPassCount = 2;
|
||||
settings.SkipHalfPixels = true;
|
||||
break;
|
||||
}
|
||||
case Quality::Medium:
|
||||
{
|
||||
settings.QualityLevel = 2;
|
||||
settings.BlurPassCount = 2;
|
||||
settings.SkipHalfPixels = false;
|
||||
break;
|
||||
}
|
||||
case Quality::High:
|
||||
{
|
||||
settings.QualityLevel = 2;
|
||||
settings.BlurPassCount = 3;
|
||||
settings.SkipHalfPixels = false;
|
||||
break;
|
||||
}
|
||||
case Quality::Ultra:
|
||||
{
|
||||
settings.QualityLevel = 3;
|
||||
settings.BlurPassCount = 3;
|
||||
settings.SkipHalfPixels = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
m_sizeX = (float)renderContext.Buffers->GetWidth();
|
||||
m_sizeY = (float)renderContext.Buffers->GetHeight();
|
||||
m_halfSizeX = (m_sizeX + 1) / 2;
|
||||
m_halfSizeY = (m_sizeY + 1) / 2;
|
||||
|
||||
// Request temporary buffers
|
||||
InitRTs(renderContext);
|
||||
|
||||
// Update and bind constant buffer
|
||||
UpdateCB(renderContext, context, 0);
|
||||
context->BindCB(SSAO_CONSTANTS_BUFFER_SLOT, _shader->GetShader()->GetCB(SSAO_CONSTANTS_BUFFER_SLOT));
|
||||
|
||||
// Generate depths
|
||||
PrepareDepths(renderContext);
|
||||
|
||||
// Generate SSAO
|
||||
GenerateSSAO(renderContext);
|
||||
|
||||
// Apply
|
||||
context->BindSR(SSAO_TEXTURE_SLOT0, m_finalResults->ViewArray());
|
||||
context->SetViewportAndScissors(m_sizeX, m_sizeY);
|
||||
context->SetState(settings.SkipHalfPixels ? _psApplyHalf : _psApply);
|
||||
context->SetRenderTarget(renderContext.Buffers->GBuffer0->View());
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Release and cleanup
|
||||
ReleaseRTs(renderContext);
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
}
|
||||
|
||||
void AmbientOcclusionPass::InitRTs(const RenderContext& renderContext)
|
||||
{
|
||||
GPUTextureDescription tempDesc;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
// TODO: maybe instead of using whole mip chain request only SSAO_DEPTH_MIP_LEVELS?
|
||||
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, 0, SSAO_DEPTH_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews);
|
||||
m_halfDepths[i] = RenderTargetPool::Get(tempDesc);
|
||||
}
|
||||
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, SSAO_AO_RESULT_FORMAT);
|
||||
m_pingPongHalfResultA = RenderTargetPool::Get(tempDesc);
|
||||
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, SSAO_AO_RESULT_FORMAT);
|
||||
m_pingPongHalfResultB = RenderTargetPool::Get(tempDesc);
|
||||
tempDesc = GPUTextureDescription::New2D((int32)m_halfSizeX, (int32)m_halfSizeY, SSAO_AO_RESULT_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget, 4);
|
||||
m_finalResults = RenderTargetPool::Get(tempDesc);
|
||||
}
|
||||
|
||||
void AmbientOcclusionPass::ReleaseRTs(const RenderContext& renderContext)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
RenderTargetPool::Release(m_halfDepths[i]);
|
||||
for (int i = 0; i < 4; i++)
|
||||
m_halfDepths[i] = nullptr;
|
||||
RenderTargetPool::Release(m_pingPongHalfResultA);
|
||||
m_pingPongHalfResultA = nullptr;
|
||||
RenderTargetPool::Release(m_pingPongHalfResultB);
|
||||
m_pingPongHalfResultB = nullptr;
|
||||
RenderTargetPool::Release(m_finalResults);
|
||||
m_finalResults = nullptr;
|
||||
}
|
||||
|
||||
void AmbientOcclusionPass::UpdateCB(const RenderContext& renderContext, GPUContext* context, const int32 passIndex)
|
||||
{
|
||||
// Cache data
|
||||
const auto& view = renderContext.View;
|
||||
const float nearPlane = view.Near;
|
||||
const float farPlane = view.Far;
|
||||
const Matrix& proj = view.Projection;
|
||||
|
||||
GBufferPass::SetInputs(view, _constantsBufferData.GBuffer);
|
||||
Matrix::Transpose(view.View, _constantsBufferData.ViewMatrix);
|
||||
|
||||
_constantsBufferData.ViewportPixelSize = Vector2(1.0f / m_sizeX, 1.0f / m_sizeY);
|
||||
_constantsBufferData.HalfViewportPixelSize = Vector2(1.0f / m_halfSizeX, 1.0f / m_halfSizeY);
|
||||
|
||||
_constantsBufferData.Viewport2xPixelSize = Vector2(_constantsBufferData.ViewportPixelSize.X * 2.0f, _constantsBufferData.ViewportPixelSize.Y * 2.0f);
|
||||
_constantsBufferData.Viewport2xPixelSize_x_025 = Vector2(_constantsBufferData.Viewport2xPixelSize.X * 0.25f, _constantsBufferData.Viewport2xPixelSize.Y * 0.25f);
|
||||
|
||||
const float tanHalfFOVY = 1.0f / proj.Values[1][1];
|
||||
|
||||
_constantsBufferData.EffectRadius = Math::Clamp(settings.Radius / farPlane * 10000.0f, 0.0f, 100000.0f);
|
||||
_constantsBufferData.EffectShadowStrength = Math::Clamp(settings.ShadowMultiplier * 4.3f, 0.0f, 10.0f);
|
||||
_constantsBufferData.EffectShadowPow = Math::Clamp(settings.ShadowPower, 0.0f, 10.0f);
|
||||
_constantsBufferData.EffectHorizonAngleThreshold = Math::Clamp(settings.HorizonAngleThreshold, 0.0f, 1.0f);
|
||||
|
||||
// Effect fade params
|
||||
const float fadeOutFrom = Math::Min(settings.FadeOutFrom, farPlane - 200);
|
||||
const float fadeOutTo = Math::Min(settings.FadeOutTo, farPlane - 50);
|
||||
_constantsBufferData.EffectMaxDistance = fadeOutTo / farPlane;
|
||||
_constantsBufferData.EffectFadeOutMul = 1.0f / ((fadeOutTo - fadeOutFrom) / farPlane);
|
||||
_constantsBufferData.EffectFadeOutAdd = (-fadeOutFrom / farPlane) * _constantsBufferData.EffectFadeOutMul;
|
||||
_constantsBufferData.EffectNearFadeMul = farPlane / (settings.Radius * 2400.0f);
|
||||
|
||||
// 1.2 seems to be around the best trade off - 1.0 means on-screen radius will stop/slow growing when the camera is at 1.0 distance, so, depending on FOV, basically filling up most of the screen
|
||||
// This setting is viewspace-dependent and not screen size dependent intentionally, so that when you change FOV the effect stays (relatively) similar.
|
||||
float effectSamplingRadiusNearLimit = (settings.Radius * 1.2f);
|
||||
|
||||
// if the depth precision is switched to 32bit float, this can be set to something closer to 1 (0.9999 is fine)
|
||||
_constantsBufferData.DepthPrecisionOffsetMod = 0.9992f;
|
||||
|
||||
// Special settings for lowest quality level - just nerf the effect a tiny bit
|
||||
if (settings.QualityLevel == 0)
|
||||
{
|
||||
//_constantsBufferData.EffectShadowStrength *= 0.9f;
|
||||
effectSamplingRadiusNearLimit *= 1.50f;
|
||||
}
|
||||
effectSamplingRadiusNearLimit /= tanHalfFOVY; // to keep the effect same regardless of FOV
|
||||
if (settings.SkipHalfPixels)
|
||||
_constantsBufferData.EffectRadius *= 0.8f;
|
||||
|
||||
_constantsBufferData.EffectSamplingRadiusNearLimitRec = 1.0f / effectSamplingRadiusNearLimit;
|
||||
|
||||
_constantsBufferData.NegRecEffectRadius = -1.0f / _constantsBufferData.EffectRadius;
|
||||
|
||||
_constantsBufferData.PerPassFullResCoordOffsetX = passIndex % 2;
|
||||
_constantsBufferData.PerPassFullResCoordOffsetY = passIndex / 2;
|
||||
|
||||
_constantsBufferData.DetailAOStrength = settings.DetailShadowStrength;
|
||||
_constantsBufferData.InvSharpness = Math::Clamp(1.0f - settings.Sharpness, 0.0f, 1.0f);
|
||||
_constantsBufferData.PassIndex = passIndex;
|
||||
|
||||
const int32 subPassCount = 5;
|
||||
const float spmap[5]{ 0.0f, 1.0f, 4.0f, 3.0f, 2.0f };
|
||||
const float a = static_cast<float>(passIndex);
|
||||
|
||||
for (int32 subPass = 0; subPass < subPassCount; subPass++)
|
||||
{
|
||||
const float b = spmap[subPass];
|
||||
|
||||
const float angle0 = (a + b / subPassCount) * PI * 0.5f;
|
||||
const float ca = Math::Cos(angle0);
|
||||
const float sa = Math::Sin(angle0);
|
||||
|
||||
const float scale = 1.0f + (a - 1.5f + (b - (subPassCount - 1.0f) * 0.5f) / static_cast<float>(subPassCount)) * 0.07f;
|
||||
_constantsBufferData.PatternRotScaleMatrices[subPass] = Vector4(scale * ca, scale * -sa, -scale * sa, -scale * ca);
|
||||
}
|
||||
|
||||
// Update buffer
|
||||
const auto cb = _shader->GetShader()->GetCB(SSAO_CONSTANTS_BUFFER_SLOT);
|
||||
context->UpdateCB(cb, &_constantsBufferData);
|
||||
context->BindCB(SSAO_CONSTANTS_BUFFER_SLOT, cb);
|
||||
}
|
||||
|
||||
void AmbientOcclusionPass::PrepareDepths(const RenderContext& renderContext)
|
||||
{
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
|
||||
// Bind scene depth buffer and set proper viewport
|
||||
context->BindSR(SSAO_TEXTURE_SLOT0, renderContext.Buffers->DepthBuffer);
|
||||
context->SetViewportAndScissors(m_halfSizeX, m_halfSizeY);
|
||||
|
||||
// Prepare depth in half resolution
|
||||
{
|
||||
if (settings.SkipHalfPixels)
|
||||
{
|
||||
GPUTextureView* twoDepths[] =
|
||||
{
|
||||
m_halfDepths[0]->View(),
|
||||
m_halfDepths[3]->View()
|
||||
};
|
||||
context->SetRenderTarget(nullptr, ToSpan(twoDepths, ARRAY_COUNT(twoDepths)));
|
||||
context->SetState(_psPrepareDepthsHalf);
|
||||
}
|
||||
else
|
||||
{
|
||||
GPUTextureView* fourDepths[] =
|
||||
{
|
||||
m_halfDepths[0]->View(),
|
||||
m_halfDepths[1]->View(),
|
||||
m_halfDepths[2]->View(),
|
||||
m_halfDepths[3]->View()
|
||||
};
|
||||
context->SetRenderTarget(nullptr, ToSpan(fourDepths, ARRAY_COUNT(fourDepths)));
|
||||
context->SetState(_psPrepareDepths);
|
||||
}
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
// Only do mipmaps for higher quality levels (not beneficial on quality level 1, and detrimental on quality level 0)
|
||||
if (settings.QualityLevel > 1)
|
||||
{
|
||||
for (int i = 1; i < SSAO_DEPTH_MIP_LEVELS; i++)
|
||||
{
|
||||
GPUTextureView* fourDepthMips[] =
|
||||
{
|
||||
m_halfDepths[0]->View(0, i),
|
||||
m_halfDepths[1]->View(0, i),
|
||||
m_halfDepths[2]->View(0, i),
|
||||
m_halfDepths[3]->View(0, i)
|
||||
};
|
||||
context->SetRenderTarget(nullptr, ToSpan(fourDepthMips, ARRAY_COUNT(fourDepthMips)));
|
||||
|
||||
int32 mipWidth, mipHeight;
|
||||
m_halfDepths[0]->GetMipSize(i, mipWidth, mipHeight);
|
||||
context->SetViewportAndScissors((float)mipWidth, (float)mipHeight);
|
||||
|
||||
context->BindSR(SSAO_TEXTURE_SLOT0, m_halfDepths[0]->View(0, i - 1));
|
||||
context->BindSR(SSAO_TEXTURE_SLOT1, m_halfDepths[1]->View(0, i - 1));
|
||||
context->BindSR(SSAO_TEXTURE_SLOT2, m_halfDepths[2]->View(0, i - 1));
|
||||
context->BindSR(SSAO_TEXTURE_SLOT3, m_halfDepths[3]->View(0, i - 1));
|
||||
|
||||
context->SetState(_psPrepareDepthMip[i - 1]);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AmbientOcclusionPass::GenerateSSAO(const RenderContext& renderContext)
|
||||
{
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
const auto normalMapSRV = renderContext.Buffers->GBuffer1;
|
||||
|
||||
// Prepare
|
||||
context->SetViewportAndScissors(m_halfSizeX, m_halfSizeY);
|
||||
|
||||
// Render AO interleaved in checkerboard pattern
|
||||
for (int32 pass = 0; pass < 4; pass++)
|
||||
{
|
||||
if (settings.SkipHalfPixels && ((pass == 1) || (pass == 2)))
|
||||
continue;
|
||||
|
||||
int32 blurPasses = settings.BlurPassCount;
|
||||
blurPasses = Math::Min(blurPasses, SSAO_MAX_BLUR_PASS_COUNT);
|
||||
|
||||
if (settings.QualityLevel == 3)
|
||||
{
|
||||
blurPasses = Math::Max(1, blurPasses);
|
||||
}
|
||||
else if (settings.QualityLevel == 0)
|
||||
{
|
||||
// just one blur pass allowed for minimum quality
|
||||
blurPasses = Math::Min(1, settings.BlurPassCount);
|
||||
}
|
||||
|
||||
if (pass > 0)
|
||||
UpdateCB(renderContext, context, pass);
|
||||
|
||||
auto pPingRT = m_pingPongHalfResultA->View();
|
||||
auto pPongRT = m_pingPongHalfResultB->View();
|
||||
|
||||
// Generate Pass
|
||||
{
|
||||
auto rts = pPingRT;
|
||||
|
||||
// no blur?
|
||||
if (blurPasses == 0)
|
||||
rts = m_finalResults->View(pass);
|
||||
|
||||
context->SetRenderTarget(rts);
|
||||
context->BindSR(SSAO_TEXTURE_SLOT0, m_halfDepths[pass]);
|
||||
context->BindSR(SSAO_TEXTURE_SLOT1, normalMapSRV);
|
||||
context->SetState(_psGenerate[settings.QualityLevel]);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
// Blur Pass
|
||||
if (blurPasses > 0)
|
||||
{
|
||||
int wideBlursRemaining = Math::Max(0, blurPasses - 2);
|
||||
|
||||
for (int i = 0; i < blurPasses; i++)
|
||||
{
|
||||
auto rts = pPongRT;
|
||||
|
||||
// Last pass?
|
||||
if (i == (blurPasses - 1))
|
||||
rts = m_finalResults->View(pass);
|
||||
|
||||
context->SetRenderTarget(rts);
|
||||
context->BindSR(SSAO_TEXTURE_SLOT0, pPingRT);
|
||||
|
||||
if (settings.QualityLevel == 0)
|
||||
{
|
||||
context->SetState(_psNonSmartBlur);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wideBlursRemaining > 0)
|
||||
{
|
||||
context->SetState(_psSmartBlurWide);
|
||||
wideBlursRemaining--;
|
||||
}
|
||||
else
|
||||
{
|
||||
context->SetState(_psSmartBlur);
|
||||
}
|
||||
}
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
Swap(pPingRT, pPongRT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Source/Engine/Renderer/AmbientOcclusionPass.h
Normal file
154
Source/Engine/Renderer/AmbientOcclusionPass.h
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
|
||||
// Config
|
||||
#define SSAO_DEPTH_MIP_LEVELS 4 // <- must match shader define
|
||||
#define SSAO_DEPTH_FORMAT PixelFormat::R16_Float
|
||||
#define SSAO_AO_RESULT_FORMAT PixelFormat::R8G8_UNorm
|
||||
#define SSAO_MAX_BLUR_PASS_COUNT 6
|
||||
|
||||
/// <summary>
|
||||
/// Screen Space Ambient Occlusion rendering service
|
||||
/// Current implementation is based on ASSAO: https://github.com/GameTechDev/ASSAO
|
||||
/// </summary>
|
||||
class AmbientOcclusionPass : public RendererPass<AmbientOcclusionPass>
|
||||
{
|
||||
private:
|
||||
|
||||
// Packed shader constant buffer structure (this MUST match shader code)
|
||||
PACK_STRUCT(struct ASSAOConstants {
|
||||
GBufferData GBuffer;
|
||||
|
||||
Vector2 ViewportPixelSize;
|
||||
Vector2 HalfViewportPixelSize;
|
||||
|
||||
int32 PerPassFullResCoordOffsetX;
|
||||
int32 PerPassFullResCoordOffsetY;
|
||||
int32 PassIndex;
|
||||
float EffectMaxDistance;
|
||||
|
||||
Vector2 Viewport2xPixelSize;
|
||||
Vector2 Viewport2xPixelSize_x_025;
|
||||
|
||||
float EffectRadius;
|
||||
float EffectShadowStrength;
|
||||
float EffectShadowPow;
|
||||
float EffectNearFadeMul;
|
||||
|
||||
float EffectFadeOutMul;
|
||||
float EffectFadeOutAdd;
|
||||
float EffectHorizonAngleThreshold;
|
||||
float EffectSamplingRadiusNearLimitRec;
|
||||
|
||||
float DepthPrecisionOffsetMod;
|
||||
float NegRecEffectRadius;
|
||||
float InvSharpness;
|
||||
float DetailAOStrength;
|
||||
|
||||
Vector4 PatternRotScaleMatrices[5];
|
||||
|
||||
Matrix ViewMatrix;
|
||||
});
|
||||
|
||||
// Effect visual settings
|
||||
struct ASSAO_Settings
|
||||
{
|
||||
float Radius; // [0.0, ~ ] World (view) space size of the occlusion sphere.
|
||||
float ShadowMultiplier; // [0.0, 5.0] Effect strength linear multiplier
|
||||
float ShadowPower; // [0.5, 5.0] Effect strength pow modifier
|
||||
float HorizonAngleThreshold; // [0.0, 0.2] Limits self-shadowing (makes the sampling area less of a hemisphere, more of a spherical cone, to avoid self-shadowing and various artifacts due to low tessellation and depth buffer imprecision, etc.)
|
||||
float FadeOutFrom; // [0.0, ~ ] Distance to start start fading out the effect.
|
||||
float FadeOutTo; // [0.0, ~ ] Distance at which the effect is faded out.
|
||||
int QualityLevel; // [ 0, ] Effect quality; 0 - low, 1 - medium, 2 - high, 3 - very high; each quality level is roughly 2x more costly than the previous, except the q3 which is variable but, in general, above q2.
|
||||
int BlurPassCount; // [ 0, 6] Number of edge-sensitive smart blur passes to apply. Quality 0 is an exception with only one 'dumb' blur pass used.
|
||||
float Sharpness; // [0.0, 1.0] (How much to bleed over edges; 1: not at all, 0.5: half-half; 0.0: completely ignore edges)
|
||||
float DetailShadowStrength; // [0.0, 5.0] Used for high-res detail AO using neighboring depth pixels: adds a lot of detail but also reduces temporal stability (adds aliasing).
|
||||
bool SkipHalfPixels; // [true/false] Use half of the pixels (checkerboard pattern)
|
||||
|
||||
ASSAO_Settings();
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
|
||||
// All shader programs
|
||||
GPUPipelineState* _psPrepareDepths;
|
||||
GPUPipelineState* _psPrepareDepthsHalf;
|
||||
GPUPipelineState* _psPrepareDepthMip[SSAO_DEPTH_MIP_LEVELS - 1];
|
||||
GPUPipelineState* _psGenerate[4];
|
||||
GPUPipelineState* _psSmartBlur;
|
||||
GPUPipelineState* _psSmartBlurWide;
|
||||
GPUPipelineState* _psNonSmartBlur;
|
||||
GPUPipelineState* _psApply;
|
||||
GPUPipelineState* _psApplyHalf;
|
||||
|
||||
// Temporary render targets used by the effect
|
||||
GPUTexture* m_halfDepths[4];
|
||||
GPUTexture* m_pingPongHalfResultA;
|
||||
GPUTexture* m_pingPongHalfResultB;
|
||||
GPUTexture* m_finalResults;
|
||||
|
||||
// Cached data
|
||||
float m_sizeX;
|
||||
float m_sizeY;
|
||||
float m_halfSizeX;
|
||||
float m_halfSizeY;
|
||||
ASSAOConstants _constantsBufferData;
|
||||
ASSAO_Settings settings;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
AmbientOcclusionPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Perform SSAO rendering for the input task
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
void Render(RenderContext& renderContext);
|
||||
|
||||
private:
|
||||
|
||||
void InitRTs(const RenderContext& renderContext);
|
||||
void ReleaseRTs(const RenderContext& renderContext);
|
||||
void UpdateCB(const RenderContext& renderContext, GPUContext* context, const int32 passIndex);
|
||||
void PrepareDepths(const RenderContext& renderContext);
|
||||
void GenerateSSAO(const RenderContext& renderContext);
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psPrepareDepths->ReleaseGPU();
|
||||
_psPrepareDepthsHalf->ReleaseGPU();
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_psPrepareDepthMip); i++)
|
||||
_psPrepareDepthMip[i]->ReleaseGPU();
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_psGenerate); i++)
|
||||
_psGenerate[i]->ReleaseGPU();
|
||||
_psSmartBlur->ReleaseGPU();
|
||||
_psSmartBlurWide->ReleaseGPU();
|
||||
_psNonSmartBlur->ReleaseGPU();
|
||||
_psApply->ReleaseGPU();
|
||||
_psApplyHalf->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
92
Source/Engine/Renderer/AntiAliasing/FXAA.cpp
Normal file
92
Source/Engine/Renderer/AntiAliasing/FXAA.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "FXAA.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
|
||||
bool FXAA::Init()
|
||||
{
|
||||
// Create pipeline state
|
||||
_psFXAA.CreatePipelineStates();
|
||||
|
||||
// Load shader
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/FXAA"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<FXAA, &FXAA::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FXAA::setupResources()
|
||||
{
|
||||
// Check 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 state
|
||||
GPUPipelineState::Description psDesc;
|
||||
if (!_psFXAA.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (_psFXAA.Create(psDesc, shader, "PS"))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FXAA::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline state
|
||||
_psFXAA.Delete();
|
||||
|
||||
// Release asset
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
void FXAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output)
|
||||
{
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
|
||||
// Ensure to have valid data
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Resources are missing. Do not perform rendering, just copy input frame.
|
||||
context->SetRenderTarget(output);
|
||||
context->Draw(input);
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_GPU_CPU("Fast Approximate Antialiasing");
|
||||
|
||||
// Bind input
|
||||
Data data;
|
||||
data.ScreenSize = renderContext.View.ScreenSize;
|
||||
const auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
context->BindSR(0, input);
|
||||
|
||||
// Render
|
||||
context->SetRenderTarget(output);
|
||||
context->SetState(_psFXAA.Get(static_cast<int32>(Graphics::AAQuality)));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
58
Source/Engine/Renderer/AntiAliasing/FXAA.h
Normal file
58
Source/Engine/Renderer/AntiAliasing/FXAA.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
|
||||
/// <summary>
|
||||
/// Fast-Approximate Anti-Aliasing effect.
|
||||
/// </summary>
|
||||
class FXAA : public RendererPass<FXAA>
|
||||
{
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct Data
|
||||
{
|
||||
Vector4 ScreenSize;
|
||||
});
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX)> _psFXAA;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs AA pass rendering for the input task.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="input">The source render target.</param>
|
||||
/// <param name="output">The result render target.</param>
|
||||
void Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psFXAA.Release();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override
|
||||
{
|
||||
return TEXT("FXAA");
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
158
Source/Engine/Renderer/AntiAliasing/SMAA.cpp
Normal file
158
Source/Engine/Renderer/AntiAliasing/SMAA.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SMAA.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
|
||||
bool SMAA::setupResources()
|
||||
{
|
||||
// Load textures
|
||||
if (_areaTex == nullptr)
|
||||
{
|
||||
_areaTex = Content::LoadAsyncInternal<Texture>(SMAA_AREA_TEX);
|
||||
if (_areaTex == nullptr)
|
||||
return true;
|
||||
}
|
||||
if (_searchTex == nullptr)
|
||||
{
|
||||
_searchTex = Content::LoadAsyncInternal<Texture>(SMAA_SEARCH_TEX);
|
||||
if (_searchTex == nullptr)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check shader
|
||||
if (_shader == nullptr)
|
||||
{
|
||||
// Create pipeline states
|
||||
_psEdge.CreatePipelineStates();
|
||||
_psBlend.CreatePipelineStates();
|
||||
_psNeighbor = GPUDevice::Instance->CreatePipelineState();
|
||||
|
||||
// Load shader
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/SMAA"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<SMAA, &SMAA::OnShaderReloading>(this);
|
||||
#endif
|
||||
}
|
||||
if (!_shader->IsLoaded())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffer size
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(Data))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline state
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (!_psEdge.IsValid())
|
||||
{
|
||||
psDesc.VS = shader->GetVS("VS_Edge");
|
||||
if (_psEdge.Create(psDesc, shader, "PS_Edge"))
|
||||
return true;
|
||||
}
|
||||
if (!_psBlend.IsValid())
|
||||
{
|
||||
psDesc.VS = shader->GetVS("VS_Blend");
|
||||
if (_psBlend.Create(psDesc, shader, "PS_Blend"))
|
||||
return true;
|
||||
}
|
||||
if (!_psNeighbor->IsValid())
|
||||
{
|
||||
psDesc.VS = shader->GetVS("VS_Neighbor");
|
||||
psDesc.PS = shader->GetPS("PS_Neighbor");
|
||||
if (_psNeighbor->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SMAA::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
_psEdge.Delete();
|
||||
_psBlend.Delete();
|
||||
SAFE_DELETE_GPU_RESOURCE(_psNeighbor);
|
||||
|
||||
// Release assets
|
||||
_shader.Unlink();
|
||||
_areaTex.Unlink();
|
||||
_searchTex.Unlink();
|
||||
}
|
||||
|
||||
void SMAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output)
|
||||
{
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
const auto qualityLevel = static_cast<int32>(Graphics::AAQuality);
|
||||
|
||||
// Ensure to have valid data
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Resources are missing. Do not perform rendering, just copy input frame.
|
||||
context->SetRenderTarget(output);
|
||||
context->Draw(input);
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_GPU_CPU("Subpixel Morphological Antialiasing");
|
||||
|
||||
// Get temporary targets
|
||||
const auto tempDesc = GPUTextureDescription::New2D((int32)renderContext.View.ScreenSize.X, (int32)renderContext.View.ScreenSize.Y, PixelFormat::R8G8B8A8_UNorm);
|
||||
auto edges = RenderTargetPool::Get(tempDesc);
|
||||
auto weights = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
// Bind constants
|
||||
Data data;
|
||||
data.RtSize.X = 1.0f / tempDesc.Width;
|
||||
data.RtSize.Y = 1.0f / tempDesc.Height;
|
||||
data.RtSize.Z = (float)tempDesc.Width;
|
||||
data.RtSize.W = (float)tempDesc.Height;
|
||||
const auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
// Edge Detection
|
||||
context->BindSR(0, input);
|
||||
context->SetRenderTarget(*edges);
|
||||
context->Clear(*edges, Color::Transparent);
|
||||
context->SetState(_psEdge.Get(qualityLevel));
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Blend Weights
|
||||
context->BindSR(0, edges);
|
||||
context->BindSR(1, _areaTex->GetTexture());
|
||||
context->BindSR(2, _searchTex->GetTexture());
|
||||
context->SetRenderTarget(*weights);
|
||||
context->SetState(_psBlend.Get(qualityLevel));
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Neighborhood Blending
|
||||
context->BindSR(0, input);
|
||||
context->BindSR(1, weights);
|
||||
context->UnBindSR(2);
|
||||
context->SetRenderTarget(output);
|
||||
context->SetState(_psNeighbor);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Cleanup
|
||||
context->UnBindSR(0);
|
||||
context->UnBindSR(1);
|
||||
context->UnBindSR(2);
|
||||
RenderTargetPool::Release(edges);
|
||||
RenderTargetPool::Release(weights);
|
||||
}
|
||||
66
Source/Engine/Renderer/AntiAliasing/SMAA.h
Normal file
66
Source/Engine/Renderer/AntiAliasing/SMAA.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
|
||||
#define SMAA_AREA_TEX TEXT("Engine/Textures/SMAA_AreaTex")
|
||||
#define SMAA_SEARCH_TEX TEXT("Engine/Textures/SMAA_SearchTex")
|
||||
|
||||
/// <summary>
|
||||
/// Subpixel Morphological Anti-Aliasing effect.
|
||||
/// </summary>
|
||||
class SMAA : public RendererPass<SMAA>
|
||||
{
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct Data
|
||||
{
|
||||
Vector4 RtSize;
|
||||
});
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX)> _psEdge;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX)> _psBlend;
|
||||
GPUPipelineState* _psNeighbor = nullptr;
|
||||
AssetReference<Texture> _areaTex;
|
||||
AssetReference<Texture> _searchTex;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs AA pass rendering for the input task.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="input">The input render target.</param>
|
||||
/// <param name="output">The output render target.</param>
|
||||
void Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psEdge.Release();
|
||||
_psBlend.Release();
|
||||
_psNeighbor->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override
|
||||
{
|
||||
return TEXT("SMAA");
|
||||
}
|
||||
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
150
Source/Engine/Renderer/AntiAliasing/TAA.cpp
Normal file
150
Source/Engine/Renderer/AntiAliasing/TAA.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "TAA.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Core/Config/GraphicsSettings.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
|
||||
bool TAA::Init()
|
||||
{
|
||||
// Create pipeline state
|
||||
_psTAA.CreatePipelineStates();
|
||||
|
||||
// Load shader
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/TAA"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<TAA, &TAA::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TAA::setupResources()
|
||||
{
|
||||
// Check 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 state
|
||||
GPUPipelineState::Description psDesc;
|
||||
if (!_psTAA.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (_psTAA.Create(psDesc, shader, "PS"))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TAA::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline state
|
||||
_psTAA.Delete();
|
||||
|
||||
// Release asset
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
bool TAA::NeedMotionVectors(RenderContext& renderContext)
|
||||
{
|
||||
return renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing;
|
||||
}
|
||||
|
||||
void TAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output)
|
||||
{
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
|
||||
// Ensure to have valid data
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Resources are missing. Do not perform rendering, just copy source frame.
|
||||
context->SetRenderTarget(output);
|
||||
context->Draw(input);
|
||||
return;
|
||||
}
|
||||
const auto& settings = renderContext.List->Settings.AntiAliasing;
|
||||
|
||||
PROFILE_GPU_CPU("Temporal Antialiasing");
|
||||
|
||||
// Get history buffers
|
||||
bool resetHistory = renderContext.Task->IsCameraCut;
|
||||
renderContext.Buffers->LastFrameTemporalAA = Engine::FrameCount;
|
||||
const auto tempDesc = GPUTextureDescription::New2D((int32)renderContext.View.ScreenSize.X, (int32)renderContext.View.ScreenSize.Y, input->Format());
|
||||
if (renderContext.Buffers->TemporalAA == nullptr)
|
||||
{
|
||||
// Missing temporal buffer
|
||||
renderContext.Buffers->TemporalAA = RenderTargetPool::Get(tempDesc);
|
||||
resetHistory = true;
|
||||
}
|
||||
else if (renderContext.Buffers->TemporalAA->Width() != tempDesc.Width || renderContext.Buffers->TemporalAA->Height() != tempDesc.Height)
|
||||
{
|
||||
// Wrong size temporal buffer
|
||||
RenderTargetPool::Release(renderContext.Buffers->TemporalAA);
|
||||
renderContext.Buffers->TemporalAA = RenderTargetPool::Get(tempDesc);
|
||||
resetHistory = true;
|
||||
}
|
||||
auto inputHistory = renderContext.Buffers->TemporalAA;
|
||||
const auto outputHistory = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
// Duplicate the current frame to the history buffer if need to reset the temporal history
|
||||
float blendStrength = 1.0f;
|
||||
if (resetHistory)
|
||||
{
|
||||
PROFILE_GPU_CPU("Reset History");
|
||||
|
||||
context->SetRenderTarget(inputHistory->View());
|
||||
context->Draw(input);
|
||||
context->ResetRenderTarget();
|
||||
|
||||
blendStrength = 0.0f;
|
||||
}
|
||||
|
||||
// Bind input
|
||||
Data data;
|
||||
data.ScreenSize = renderContext.View.ScreenSize;
|
||||
data.TaaJitterStrength.X = renderContext.View.TemporalAAJitter.X;
|
||||
data.TaaJitterStrength.Y = renderContext.View.TemporalAAJitter.Y;
|
||||
data.TaaJitterStrength.Z = data.TaaJitterStrength.X / tempDesc.Width;
|
||||
data.TaaJitterStrength.W = data.TaaJitterStrength.Y / tempDesc.Height;
|
||||
data.FinalBlendParameters.X = settings.TAA_StationaryBlending * blendStrength;
|
||||
data.FinalBlendParameters.Y = settings.TAA_MotionBlending * blendStrength;
|
||||
data.FinalBlendParameters.Z = 100.0f * 60.0f;
|
||||
data.FinalBlendParameters.W = settings.TAA_Sharpness;
|
||||
const auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
context->BindSR(0, input);
|
||||
context->BindSR(1, inputHistory);
|
||||
context->BindSR(2, renderContext.Buffers->MotionVectors);
|
||||
context->BindSR(3, renderContext.Buffers->DepthBuffer);
|
||||
|
||||
// Render
|
||||
GPUTextureView* rts[] = { output, outputHistory->View() };
|
||||
context->SetRenderTarget(nullptr, ToSpan(rts, ARRAY_COUNT(rts)));
|
||||
context->SetState(_psTAA.Get(renderContext.View.IsOrthographicProjection() ? 1 : 0));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Swap the history
|
||||
RenderTargetPool::Release(inputHistory);
|
||||
renderContext.Buffers->TemporalAA = outputHistory;
|
||||
}
|
||||
67
Source/Engine/Renderer/AntiAliasing/TAA.h
Normal file
67
Source/Engine/Renderer/AntiAliasing/TAA.h
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
|
||||
/// <summary>
|
||||
/// Temporal Anti-Aliasing effect.
|
||||
/// </summary>
|
||||
class TAA : public RendererPass<TAA>
|
||||
{
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct Data
|
||||
{
|
||||
Vector4 ScreenSize;
|
||||
Vector4 TaaJitterStrength;
|
||||
Vector4 FinalBlendParameters;
|
||||
});
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineStatePermutationsPs<2> _psTAA;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determinates whenever this pass requires motion vectors rendering.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <returns>True if need to render motion vectors, otherwise false.</returns>
|
||||
static bool NeedMotionVectors(RenderContext& renderContext);
|
||||
|
||||
/// <summary>
|
||||
/// Performs AA pass rendering for the input task.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="input">The input render target.</param>
|
||||
/// <param name="output">The output render target.</param>
|
||||
void Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psTAA.Release();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override
|
||||
{
|
||||
return TEXT("TAA");
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
695
Source/Engine/Renderer/AtmospherePreCompute.cpp
Normal file
695
Source/Engine/Renderer/AtmospherePreCompute.cpp
Normal file
@@ -0,0 +1,695 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "AtmospherePreCompute.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/Textures/GPUTexture.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Platform/Window.h"
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Threading/ThreadPoolTask.h"
|
||||
#include "Engine/Graphics/GPUPipelineState.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
|
||||
// Amount of frames to wait for data from atmosphere precompute job
|
||||
#define ATMOSPHERE_PRECOMPUTE_LATENCY_FRAMES 1
|
||||
|
||||
const float DensityHeight = 0.5f;
|
||||
const int32 MaxScatteringOrder = 4;
|
||||
|
||||
const int32 TransmittanceTexWidth = 256;
|
||||
const int32 TransmittanceTexHeight = 64;
|
||||
|
||||
const int32 IrradianceTexWidth = 64;
|
||||
const int32 IrradianceTexHeight = 16;
|
||||
|
||||
const int32 InscatterMuNum = 128;
|
||||
const int32 InscatterMuSNum = 32;
|
||||
const int32 InscatterNuNum = 8;
|
||||
const int32 InscatterAltitudeSampleNum = 4;
|
||||
|
||||
const int32 InscatterWidth = InscatterMuSNum * InscatterNuNum;
|
||||
const int32 InscatterHeight = InscatterMuNum;
|
||||
const int32 InscatterDepth = InscatterAltitudeSampleNum;
|
||||
|
||||
const static float RadiusScale = 1;
|
||||
const static float RadiusGround = 6360 * RadiusScale;
|
||||
const static float RadiusAtmosphere = 6420 * RadiusScale;
|
||||
|
||||
class DownloadJob : public ThreadPoolTask
|
||||
{
|
||||
private:
|
||||
|
||||
GPUTexture* _transmittance;
|
||||
GPUTexture* _irradiance;
|
||||
GPUTexture* _inscatter;
|
||||
|
||||
public:
|
||||
|
||||
DownloadJob(GPUTexture* transmittance, GPUTexture* irradiance, GPUTexture* inscatter);
|
||||
|
||||
protected:
|
||||
|
||||
// [ThreadPoolTask]
|
||||
bool Run() override;
|
||||
};
|
||||
|
||||
PACK_STRUCT(struct Data
|
||||
{
|
||||
float FirstOrder;
|
||||
float AtmosphereR;
|
||||
int AtmosphereLayer;
|
||||
float Dummy0;
|
||||
Vector4 DhdH;
|
||||
});
|
||||
|
||||
namespace AtmospherePreComputeImpl
|
||||
{
|
||||
bool _isUpdatePending = false;
|
||||
bool _isReadyForCompute = false;
|
||||
bool _hasDataCached = false;
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psTransmittance = nullptr;
|
||||
GPUPipelineState* _psIrradiance1 = nullptr;
|
||||
GPUPipelineState* _psIrradianceN = nullptr;
|
||||
GPUPipelineState* _psCopyIrradiance = nullptr;
|
||||
GPUPipelineState* _psCopyIrradianceAdd = nullptr;
|
||||
GPUPipelineState* _psInscatter1_A = nullptr;
|
||||
GPUPipelineState* _psInscatter1_B = nullptr;
|
||||
GPUPipelineState* _psCopyInscatter1 = nullptr;
|
||||
GPUPipelineState* _psCopyInscatterNAdd = nullptr;
|
||||
GPUPipelineState* _psInscatterS = nullptr;
|
||||
GPUPipelineState* _psInscatterN = nullptr;
|
||||
RenderTask* _task = nullptr;
|
||||
|
||||
//
|
||||
GPUTexture* AtmosphereTransmittance = nullptr;
|
||||
GPUTexture* AtmosphereIrradiance = nullptr;
|
||||
GPUTexture* AtmosphereInscatter = nullptr;
|
||||
//
|
||||
GPUTexture* AtmosphereDeltaE = nullptr;
|
||||
GPUTexture* AtmosphereDeltaSR = nullptr;
|
||||
GPUTexture* AtmosphereDeltaSM = nullptr;
|
||||
GPUTexture* AtmosphereDeltaJ = nullptr;
|
||||
|
||||
uint64 _updateFrameNumber;
|
||||
bool _wasCancelled;
|
||||
|
||||
FORCE_INLINE bool isUpdateSynced()
|
||||
{
|
||||
return _updateFrameNumber > 0 && _updateFrameNumber + ATMOSPHERE_PRECOMPUTE_LATENCY_FRAMES <= Engine::FrameCount;
|
||||
}
|
||||
|
||||
void onRender(RenderTask* task, GPUContext* context);
|
||||
}
|
||||
|
||||
using namespace AtmospherePreComputeImpl;
|
||||
|
||||
class AtmospherePreComputeService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
AtmospherePreComputeService()
|
||||
: EngineService(TEXT("Atmosphere Pre Compute"), 50)
|
||||
{
|
||||
}
|
||||
|
||||
void Update() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
AtmospherePreComputeService AtmospherePreComputeServiceInstance;
|
||||
|
||||
bool AtmospherePreCompute::GetCache(AtmosphereCache* cache)
|
||||
{
|
||||
if (_hasDataCached)
|
||||
{
|
||||
if (cache)
|
||||
{
|
||||
cache->Transmittance = AtmosphereTransmittance;
|
||||
cache->Irradiance = AtmosphereIrradiance;
|
||||
cache->Inscatter = AtmosphereInscatter;
|
||||
}
|
||||
}
|
||||
else if (_task == nullptr || !_task->Enabled)
|
||||
{
|
||||
_isUpdatePending = true;
|
||||
}
|
||||
|
||||
return _hasDataCached;
|
||||
}
|
||||
|
||||
bool init()
|
||||
{
|
||||
if (_isReadyForCompute)
|
||||
return false;
|
||||
|
||||
LOG(Info, "Starting Atmosphere Pre Compute service");
|
||||
|
||||
// Load shader
|
||||
if (_shader == nullptr)
|
||||
{
|
||||
LOG(Warning, "Failed to load AtmospherePreCompute shader!");
|
||||
return true;
|
||||
}
|
||||
if (_shader->WaitForLoaded())
|
||||
{
|
||||
LOG(Warning, "Loading AtmospherePreCompute shader timeout!");
|
||||
return true;
|
||||
}
|
||||
auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffers sizes
|
||||
ASSERT(shader->GetCB(0) != nullptr);
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(Data))
|
||||
{
|
||||
LOG(Fatal, "Shader {0} has incorrect constant buffer {1} size: {2} bytes. Expected: {3} bytes", _shader->ToString(), 0, shader->GetCB(0)->GetSize(), sizeof(Data));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline stages
|
||||
_psTransmittance = GPUDevice::Instance->CreatePipelineState();
|
||||
_psIrradiance1 = GPUDevice::Instance->CreatePipelineState();
|
||||
_psIrradianceN = GPUDevice::Instance->CreatePipelineState();
|
||||
_psCopyIrradiance = GPUDevice::Instance->CreatePipelineState();
|
||||
_psCopyIrradianceAdd = GPUDevice::Instance->CreatePipelineState();
|
||||
_psInscatter1_A = GPUDevice::Instance->CreatePipelineState();
|
||||
_psInscatter1_B = GPUDevice::Instance->CreatePipelineState();
|
||||
_psCopyInscatter1 = GPUDevice::Instance->CreatePipelineState();
|
||||
_psInscatterS = GPUDevice::Instance->CreatePipelineState();
|
||||
_psInscatterN = GPUDevice::Instance->CreatePipelineState();
|
||||
_psCopyInscatterNAdd = GPUDevice::Instance->CreatePipelineState();
|
||||
GPUPipelineState::Description psDesc, psDescLayers;
|
||||
psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
psDescLayers = psDesc;
|
||||
//psDescLayers.GS = shader->GetGS(0);
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Transmittance");
|
||||
if (_psTransmittance->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Irradiance1");
|
||||
if (_psIrradiance1->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_IrradianceN");
|
||||
if (_psIrradianceN->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_CopyIrradiance1");
|
||||
if (_psCopyIrradiance->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDescLayers.PS = shader->GetPS("PS_Inscatter1_A");
|
||||
if (_psInscatter1_A->Init(psDescLayers))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDescLayers.PS = shader->GetPS("PS_Inscatter1_B");
|
||||
if (_psInscatter1_B->Init(psDescLayers))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDescLayers.PS = shader->GetPS("PS_CopyInscatter1");
|
||||
if (_psCopyInscatter1->Init(psDescLayers))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDescLayers.PS = shader->GetPS("PS_InscatterS");
|
||||
if (_psInscatterS->Init(psDescLayers))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDescLayers.PS = shader->GetPS("PS_InscatterN");
|
||||
if (_psInscatterN->Init(psDescLayers))
|
||||
return true;
|
||||
}
|
||||
psDescLayers.BlendMode = BlendingMode::Add;
|
||||
psDesc.BlendMode = BlendingMode::Add;
|
||||
{
|
||||
psDescLayers.PS = shader->GetPS("PS_CopyInscatterN");
|
||||
if (_psCopyInscatterNAdd->Init(psDescLayers))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_CopyIrradiance1");
|
||||
if (_psCopyIrradianceAdd->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Init rendering pipeline
|
||||
_task = New<SceneRenderTask>();
|
||||
_task->Enabled = false;
|
||||
_task->Render.Bind(onRender);
|
||||
|
||||
// Init render targets
|
||||
AtmosphereTransmittance = GPUDevice::Instance->CreateTexture(TEXT("AtmospherePreCompute.Transmittance"));
|
||||
if (AtmosphereTransmittance->Init(GPUTextureDescription::New2D(TransmittanceTexWidth, TransmittanceTexHeight, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)))
|
||||
return true;
|
||||
AtmosphereIrradiance = GPUDevice::Instance->CreateTexture(TEXT("AtmospherePreCompute.Irradiance"));
|
||||
if (AtmosphereIrradiance->Init(GPUTextureDescription::New2D(IrradianceTexWidth, IrradianceTexHeight, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)))
|
||||
return true;
|
||||
AtmosphereDeltaE = GPUDevice::Instance->CreateTexture(TEXT("AtmospherePreCompute.DeltaE"));
|
||||
if (AtmosphereDeltaE->Init(GPUTextureDescription::New2D(IrradianceTexWidth, IrradianceTexHeight, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)))
|
||||
return true;
|
||||
AtmosphereInscatter = GPUDevice::Instance->CreateTexture(TEXT("AtmospherePreCompute.Inscatter"));
|
||||
if (AtmosphereInscatter->Init(GPUTextureDescription::New3D(InscatterWidth, InscatterHeight, InscatterDepth, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerSliceViews)))
|
||||
return true;
|
||||
AtmosphereDeltaSR = GPUDevice::Instance->CreateTexture(TEXT("AtmospherePreCompute.DeltaSR"));
|
||||
if (AtmosphereDeltaSR->Init(GPUTextureDescription::New3D(InscatterWidth, InscatterHeight, InscatterDepth, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerSliceViews)))
|
||||
return true;
|
||||
AtmosphereDeltaSM = GPUDevice::Instance->CreateTexture(TEXT("AtmospherePreCompute.DeltaSM"));
|
||||
if (AtmosphereDeltaSM->Init(GPUTextureDescription::New3D(InscatterWidth, InscatterHeight, InscatterDepth, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerSliceViews)))
|
||||
return true;
|
||||
AtmosphereDeltaJ = GPUDevice::Instance->CreateTexture(TEXT("AtmospherePreCompute.DeltaJ"));
|
||||
if (AtmosphereDeltaJ->Init(GPUTextureDescription::New3D(InscatterWidth, InscatterHeight, InscatterDepth, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerSliceViews)))
|
||||
return true;
|
||||
|
||||
// Mark as ready
|
||||
_isReadyForCompute = true;
|
||||
_wasCancelled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
if (!_isReadyForCompute)
|
||||
return;
|
||||
if (_updateFrameNumber != 0)
|
||||
{
|
||||
_wasCancelled = true;
|
||||
}
|
||||
_updateFrameNumber = 0;
|
||||
|
||||
LOG(Info, "Disposing Atmosphere Pre Compute service");
|
||||
|
||||
// Release data
|
||||
SAFE_DELETE_GPU_RESOURCE(_psTransmittance);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psIrradiance1);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psIrradianceN);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCopyIrradiance);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCopyIrradianceAdd);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psInscatter1_A);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psInscatter1_B);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCopyInscatter1);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psInscatterS);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psInscatterN);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCopyInscatterNAdd);
|
||||
_shader.Unlink();
|
||||
SAFE_DELETE(_task);
|
||||
SAFE_DELETE_GPU_RESOURCE(AtmosphereTransmittance);
|
||||
SAFE_DELETE_GPU_RESOURCE(AtmosphereIrradiance);
|
||||
SAFE_DELETE_GPU_RESOURCE(AtmosphereInscatter);
|
||||
SAFE_DELETE_GPU_RESOURCE(AtmosphereDeltaE);
|
||||
SAFE_DELETE_GPU_RESOURCE(AtmosphereDeltaSR);
|
||||
SAFE_DELETE_GPU_RESOURCE(AtmosphereDeltaSM);
|
||||
SAFE_DELETE_GPU_RESOURCE(AtmosphereDeltaJ);
|
||||
|
||||
_isReadyForCompute = false;
|
||||
}
|
||||
|
||||
void AtmospherePreComputeService::Update()
|
||||
{
|
||||
// Calculate time delta since last update
|
||||
auto currentTime = Time::Update.UnscaledTime;
|
||||
|
||||
// Check if render job is done
|
||||
if (isUpdateSynced())
|
||||
{
|
||||
// Create async job to gather data from the GPU
|
||||
//ThreadPool::AddJob(New<DownloadJob>(AtmosphereTransmittance, AtmosphereIrradiance, AtmosphereInscatter));
|
||||
|
||||
// TODO: download data from generated cache textures
|
||||
// TODO: serialize cached textures
|
||||
|
||||
// Clear flags
|
||||
_updateFrameNumber = 0;
|
||||
_isUpdatePending = false;
|
||||
|
||||
// Release service
|
||||
//release();
|
||||
// TODO: release service data
|
||||
}
|
||||
else if (_isUpdatePending && (_task == nullptr || !_task->Enabled))
|
||||
{
|
||||
// TODO: init but without a stalls, just wait for resources loaded and then start rendering
|
||||
|
||||
// Init service
|
||||
if (!_shader)
|
||||
{
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/AtmospherePreCompute"));
|
||||
if (_shader && !_shader->IsLoaded())
|
||||
return;
|
||||
}
|
||||
if (init())
|
||||
{
|
||||
LOG(Fatal, "Cannot setup Atmosphere Pre Compute!");
|
||||
}
|
||||
|
||||
// Mark task to update
|
||||
_task->Enabled = true;
|
||||
_updateFrameNumber = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AtmospherePreComputeService::Dispose()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void GetLayerValue(int32 layer, float& atmosphereR, Vector4& DhdH)
|
||||
{
|
||||
float r = layer / Math::Max<float>(InscatterAltitudeSampleNum - 1.0f, 1.0f);
|
||||
r = r * r;
|
||||
r = Math::Sqrt(RadiusGround * RadiusGround + r * (RadiusAtmosphere * RadiusAtmosphere - RadiusGround * RadiusGround)) + (layer == 0 ? 0.01f : (layer == InscatterAltitudeSampleNum - 1 ? -0.001f : 0.0f));
|
||||
float dMin = RadiusAtmosphere - r;
|
||||
float dMax = Math::Sqrt(r * r - RadiusGround * RadiusGround) + Math::Sqrt(RadiusAtmosphere * RadiusAtmosphere - RadiusGround * RadiusGround);
|
||||
float dMinP = r - RadiusGround;
|
||||
float dMaxP = Math::Sqrt(r * r - RadiusGround * RadiusGround);
|
||||
atmosphereR = r;
|
||||
DhdH = Vector4(dMin, dMax, dMinP, dMaxP);
|
||||
}
|
||||
|
||||
void AtmospherePreComputeImpl::onRender(RenderTask* task, GPUContext* context)
|
||||
{
|
||||
// If job has been cancelled (eg. on window close)
|
||||
if (_wasCancelled)
|
||||
{
|
||||
LOG(Warning, "AtmospherePreCompute job cancelled");
|
||||
return;
|
||||
}
|
||||
ASSERT(_isUpdatePending && _updateFrameNumber == 0);
|
||||
|
||||
auto shader = _shader->GetShader();
|
||||
auto cb = shader->GetCB(0);
|
||||
Data data;
|
||||
|
||||
// Compute transmittance texture T (line 1 in algorithm 4.1)
|
||||
context->SetRenderTarget(*AtmosphereTransmittance);
|
||||
context->SetViewportAndScissors((float)TransmittanceTexWidth, (float)TransmittanceTexHeight);
|
||||
context->SetState(_psTransmittance);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Compute irradiance texture deltaE (line 2 in algorithm 4.1)
|
||||
context->SetRenderTarget(*AtmosphereDeltaE);
|
||||
context->SetViewportAndScissors((float)IrradianceTexWidth, (float)IrradianceTexHeight);
|
||||
context->BindSR(0, AtmosphereTransmittance);
|
||||
context->SetState(_psIrradiance1);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Compute single scattering texture deltaS (line 3 in algorithm 4.1)
|
||||
// Rayleigh and Mie separated in deltaSR + deltaSM
|
||||
context->SetViewportAndScissors((float)InscatterWidth, (float)InscatterHeight);
|
||||
context->SetState(_psInscatter1_A);
|
||||
for (int32 layer = 0; layer < InscatterAltitudeSampleNum; layer++)
|
||||
{
|
||||
GetLayerValue(layer, data.AtmosphereR, data.DhdH);
|
||||
data.AtmosphereLayer = layer;
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
context->SetRenderTarget(AtmosphereDeltaSR->View(layer));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
context->SetState(_psInscatter1_B);
|
||||
for (int32 layer = 0; layer < InscatterAltitudeSampleNum; layer++)
|
||||
{
|
||||
GetLayerValue(layer, data.AtmosphereR, data.DhdH);
|
||||
data.AtmosphereLayer = layer;
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
context->SetRenderTarget(AtmosphereDeltaSM->View(layer));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
context->ResetRenderTarget();
|
||||
|
||||
//// old way to render Inscatter1 to DeltaSR and DeltaSM at once but didn't work well :/ (no time to find out why)
|
||||
//for (int32 layer = 0; layer < InscatterAltitudeSampleNum; layer++)
|
||||
//{
|
||||
// GetLayerValue(layer, data.AtmosphereR, data.DhdH);
|
||||
// data.AtmosphereLayer = layer;
|
||||
// cb->SetData(&data);
|
||||
// context->Bind(0, cb);
|
||||
|
||||
// GPUTextureView* deltaRTs[2] = { AtmosphereDeltaSR->HandleSlice(layer), AtmosphereDeltaSM->HandleSlice(layer) };
|
||||
// //GPUTextureView* deltaRTs[2] = { AtmosphereIrradiance->Handle(), AtmosphereDeltaE->Handle() };
|
||||
// context->SetRenderTarget(nullptr, 2, deltaRTs);
|
||||
// //context->SetRenderTarget(AtmosphereIrradiance->View());
|
||||
// //context->SetRenderTarget(AtmosphereDeltaSR->HandleSlice(layer));
|
||||
// //context->SetRenderTarget(AtmosphereDeltaSR->View());
|
||||
// context->DrawFullscreenTriangle();
|
||||
//}
|
||||
//context->SetRenderTarget();
|
||||
|
||||
// Copy deltaE into irradiance texture E (line 4 in algorithm 4.1)
|
||||
/*context->SetRenderTarget(*AtmosphereIrradiance);
|
||||
context->SetViewport(IrradianceTexWidth, IrradianceTexHeight);
|
||||
context->Bind(3, AtmosphereDeltaE);
|
||||
context->SetState(_psCopyIrradiance);
|
||||
context->DrawFullscreenTriangle();*/
|
||||
// TODO: remove useless CopyIrradiance shader program?
|
||||
|
||||
context->SetViewportAndScissors((float)IrradianceTexWidth, (float)IrradianceTexHeight);
|
||||
context->Clear(*AtmosphereIrradiance, Color::Transparent);
|
||||
|
||||
// Copy deltaS into inscatter texture S (line 5 in algorithm 4.1)
|
||||
context->SetViewportAndScissors((float)InscatterWidth, (float)InscatterHeight);
|
||||
context->SetState(_psCopyInscatter1);
|
||||
context->BindSR(4, AtmosphereDeltaSR->ViewVolume());
|
||||
context->BindSR(5, AtmosphereDeltaSM->ViewVolume());
|
||||
for (int32 layer = 0; layer < InscatterAltitudeSampleNum; layer++)
|
||||
{
|
||||
GetLayerValue(layer, data.AtmosphereR, data.DhdH);
|
||||
data.AtmosphereLayer = layer;
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
context->SetRenderTarget(AtmosphereInscatter->View(layer));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Loop for each scattering order (line 6 in algorithm 4.1)
|
||||
for (int32 order = 2; order <= MaxScatteringOrder; order++)
|
||||
{
|
||||
// Compute deltaJ (line 7 in algorithm 4.1)
|
||||
context->UnBindSR(6);
|
||||
context->SetViewportAndScissors((float)InscatterWidth, (float)InscatterHeight);
|
||||
context->SetState(_psInscatterS);
|
||||
data.FirstOrder = order == 2 ? 1.0f : 0.0f;
|
||||
context->BindSR(0, AtmosphereTransmittance);
|
||||
context->BindSR(3, AtmosphereDeltaE);
|
||||
context->BindSR(4, AtmosphereDeltaSR->ViewVolume());
|
||||
context->BindSR(5, AtmosphereDeltaSM->ViewVolume());
|
||||
for (int32 layer = 0; layer < InscatterAltitudeSampleNum; layer++)
|
||||
{
|
||||
GetLayerValue(layer, data.AtmosphereR, data.DhdH);
|
||||
data.AtmosphereLayer = layer;
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
context->SetRenderTarget(AtmosphereDeltaJ->View(layer));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
// Compute deltaE (line 8 in algorithm 4.1)
|
||||
context->UnBindSR(3);
|
||||
context->SetRenderTarget(AtmosphereDeltaE->View());
|
||||
context->SetViewportAndScissors((float)IrradianceTexWidth, (float)IrradianceTexHeight);
|
||||
context->BindSR(0, AtmosphereTransmittance);
|
||||
context->BindSR(4, AtmosphereDeltaSR->ViewVolume());
|
||||
context->BindSR(5, AtmosphereDeltaSM->ViewVolume());
|
||||
context->SetState(_psIrradianceN);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Compute deltaS (line 9 in algorithm 4.1)
|
||||
context->UnBindSR(4);
|
||||
context->SetViewportAndScissors((float)InscatterWidth, (float)InscatterHeight);
|
||||
context->SetState(_psInscatterN);
|
||||
context->BindSR(0, AtmosphereTransmittance);
|
||||
context->BindSR(6, AtmosphereDeltaJ->ViewVolume());
|
||||
for (int32 layer = 0; layer < InscatterAltitudeSampleNum; layer++)
|
||||
{
|
||||
GetLayerValue(layer, data.AtmosphereR, data.DhdH);
|
||||
data.AtmosphereLayer = layer;
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
context->SetRenderTarget(AtmosphereDeltaSR->View(layer));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
// Add deltaE into irradiance texture E (line 10 in algorithm 4.1)
|
||||
context->SetRenderTarget(*AtmosphereIrradiance);
|
||||
context->SetViewportAndScissors((float)IrradianceTexWidth, (float)IrradianceTexHeight);
|
||||
context->BindSR(3, AtmosphereDeltaE);
|
||||
context->SetState(_psCopyIrradianceAdd);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Add deltaS into inscatter texture S (line 11 in algorithm 4.1)
|
||||
context->SetViewportAndScissors((float)InscatterWidth, (float)InscatterHeight);
|
||||
context->SetState(_psCopyInscatterNAdd);
|
||||
context->BindSR(4, AtmosphereDeltaSR->ViewVolume());
|
||||
for (int32 layer = 0; layer < InscatterAltitudeSampleNum; layer++)
|
||||
{
|
||||
GetLayerValue(layer, data.AtmosphereR, data.DhdH);
|
||||
data.AtmosphereLayer = layer;
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
context->SetRenderTarget(AtmosphereInscatter->View(layer));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
|
||||
// Mark as rendered
|
||||
_hasDataCached = true;
|
||||
_isUpdatePending = false;
|
||||
_updateFrameNumber = Engine::FrameCount;
|
||||
_task->Enabled = false;
|
||||
}
|
||||
|
||||
DownloadJob::DownloadJob(GPUTexture* transmittance, GPUTexture* irradiance, GPUTexture* inscatter)
|
||||
: _transmittance(transmittance)
|
||||
, _irradiance(irradiance)
|
||||
, _inscatter(inscatter)
|
||||
{
|
||||
}
|
||||
|
||||
bool DownloadJob::Run()
|
||||
{
|
||||
TextureData textureData;
|
||||
Array<byte> data;
|
||||
|
||||
#if BUILD_DEBUG
|
||||
|
||||
// Transmittance
|
||||
/*{
|
||||
if (_transmittance->DownloadData(textureData))
|
||||
{
|
||||
LOG(Fatal, 193, "AtmospherePreCompute::DownloadJob::Transmittance");
|
||||
return;
|
||||
}
|
||||
|
||||
auto in = textureData.Get(0, 0);
|
||||
int rowPich = 3 * TransmittanceTexWidth * sizeof(BYTE);
|
||||
int size = rowPich * TransmittanceTexHeight;
|
||||
data.EnsureCapacity(size, false);
|
||||
byte* p = data.Get();
|
||||
for (int y = 0; y < TransmittanceTexHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < TransmittanceTexWidth; x++)
|
||||
{
|
||||
Vector4 t = ((Half4*)&in->Data[y * in->RowPitch + x * sizeof(Half4)])->ToVector4();
|
||||
*p++ = Math::Saturate(t.Z) * 255;
|
||||
*p++ = Math::Saturate(t.Y) * 255;
|
||||
*p++ = Math::Saturate(t.X) * 255;
|
||||
}
|
||||
}
|
||||
|
||||
FileSystem::SaveBitmapToFile(data.Get(),
|
||||
TransmittanceTexWidth,
|
||||
TransmittanceTexHeight,
|
||||
24,
|
||||
0,
|
||||
L"C:\\Users\\Wojtek\\Downloads\\transmittance_Flax.bmp");
|
||||
}*/
|
||||
|
||||
// Irradiance
|
||||
/*{
|
||||
textureData.Clear();
|
||||
if (_irradiance->DownloadData(textureData))
|
||||
{
|
||||
LOG(Fatal, 193, "AtmospherePreCompute::DownloadJob::Irradiance");
|
||||
return;
|
||||
}
|
||||
|
||||
auto in = textureData.Get(0, 0);
|
||||
int rowPich = 3 * IrradianceTexWidth * sizeof(BYTE);
|
||||
int size = rowPich * IrradianceTexHeight;
|
||||
data.EnsureCapacity(size, false);
|
||||
byte* p = data.Get();
|
||||
for (int y = 0; y < IrradianceTexHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < IrradianceTexWidth; x++)
|
||||
{
|
||||
Vector4 t = ((Half4*)&in->Data[y * in->RowPitch + x * sizeof(Half4)])->ToVector4();
|
||||
*p++ = Math::Saturate(t.Z) * 255;
|
||||
*p++ = Math::Saturate(t.Y) * 255;
|
||||
*p++ = Math::Saturate(t.X) * 255;
|
||||
}
|
||||
}
|
||||
|
||||
FileSystem::SaveBitmapToFile(data.Get(),
|
||||
IrradianceTexWidth,
|
||||
IrradianceTexHeight,
|
||||
24,
|
||||
0,
|
||||
L"C:\\Users\\Wojtek\\Downloads\\irradiance_Flax.bmp");
|
||||
}*/
|
||||
|
||||
/*// Inscatter
|
||||
{
|
||||
textureData.Clear();
|
||||
if (_inscatter->DownloadData(textureData))
|
||||
{
|
||||
LOG(Fatal, 193, "AtmospherePreCompute::DownloadJob::Inscatter");
|
||||
return;
|
||||
}
|
||||
auto in = textureData.Get(0, 0);
|
||||
int rowPich = 3 * InscatterWidth * sizeof(BYTE);
|
||||
int size = rowPich * InscatterHeight;
|
||||
data.EnsureCapacity(size, false);
|
||||
|
||||
for (int32 depthSlice = 0; depthSlice < InscatterDepth; depthSlice++)
|
||||
{
|
||||
byte* p = data.Get();
|
||||
byte* s = in->Data.Get() + depthSlice * in->DepthPitch;
|
||||
|
||||
for (int y = 0; y < InscatterHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < InscatterWidth; x++)
|
||||
{
|
||||
Vector4 t = ((Half4*)&s[y * in->RowPitch + x * sizeof(Half4)])->ToVector4();
|
||||
*p++ = Math::Saturate(t.Z) * 255;
|
||||
*p++ = Math::Saturate(t.Y) * 255;
|
||||
*p++ = Math::Saturate(t.X) * 255;
|
||||
}
|
||||
}
|
||||
|
||||
FileSystem::SaveBitmapToFile(data.Get(),
|
||||
InscatterWidth,
|
||||
InscatterHeight,
|
||||
24,
|
||||
0,
|
||||
*String::Format(TEXT("C:\\Users\\Wojtek\\Downloads\\inscatter_{0}_Flax.bmp"), depthSlice));
|
||||
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
#else
|
||||
|
||||
MISSING_CODE("download precomputed atmosphere data from the GPU!");
|
||||
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
32
Source/Engine/Renderer/AtmospherePreCompute.h
Normal file
32
Source/Engine/Renderer/AtmospherePreCompute.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
class GPUTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains precomputed data for atmosphere rendering.
|
||||
/// </summary>
|
||||
struct AtmosphereCache
|
||||
{
|
||||
GPUTexture* Transmittance;
|
||||
GPUTexture* Irradiance;
|
||||
GPUTexture* Inscatter;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// PBR atmosphere cache data rendering service.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API AtmospherePreCompute
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the atmosphere cache textures.
|
||||
/// </summary>
|
||||
/// <param name="cache">Result cache</param>
|
||||
/// <returns>True if context is ready for usage.</returns>
|
||||
static bool GetCache(AtmosphereCache* cache);
|
||||
};
|
||||
215
Source/Engine/Renderer/ColorGradingPass.cpp
Normal file
215
Source/Engine/Renderer/ColorGradingPass.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ColorGradingPass.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
|
||||
PACK_STRUCT(struct Data {
|
||||
Vector4 ColorSaturationShadows;
|
||||
Vector4 ColorContrastShadows;
|
||||
Vector4 ColorGammaShadows;
|
||||
Vector4 ColorGainShadows;
|
||||
Vector4 ColorOffsetShadows;
|
||||
|
||||
Vector4 ColorSaturationMidtones;
|
||||
Vector4 ColorContrastMidtones;
|
||||
Vector4 ColorGammaMidtones;
|
||||
Vector4 ColorGainMidtones;
|
||||
Vector4 ColorOffsetMidtones;
|
||||
|
||||
Vector4 ColorSaturationHighlights;
|
||||
Vector4 ColorContrastHighlights;
|
||||
Vector4 ColorGammaHighlights;
|
||||
Vector4 ColorGainHighlights;
|
||||
Vector4 ColorOffsetHighlights;
|
||||
|
||||
float ColorCorrectionShadowsMax;
|
||||
float ColorCorrectionHighlightsMin;
|
||||
float WhiteTemp;
|
||||
float WhiteTint;
|
||||
|
||||
Vector3 Dummy;
|
||||
float LutWeight;
|
||||
});
|
||||
|
||||
ColorGradingPass::ColorGradingPass()
|
||||
: _useVolumeTexture(false)
|
||||
, _lutFormat()
|
||||
, _shader(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String ColorGradingPass::ToString() const
|
||||
{
|
||||
return TEXT("ColorGradingPass");
|
||||
}
|
||||
|
||||
bool ColorGradingPass::Init()
|
||||
{
|
||||
// Detect if can use volume texture (3d) for a LUT (faster, requires geometry shader)
|
||||
const auto device = GPUDevice::Instance;
|
||||
_useVolumeTexture = device->Limits.HasGeometryShaders && device->Limits.HasVolumeTextureRendering;
|
||||
|
||||
// Pick a proper LUT pixels format
|
||||
_lutFormat = PixelFormat::R10G10B10A2_UNorm;
|
||||
const auto formatSupport = device->GetFormatFeatures(_lutFormat).Support;
|
||||
FormatSupport formatSupportFlags = FormatSupport::ShaderSample | FormatSupport::RenderTarget;
|
||||
if (_useVolumeTexture)
|
||||
formatSupportFlags |= FormatSupport::Texture3D;
|
||||
else
|
||||
formatSupportFlags |= FormatSupport::Texture2D;
|
||||
if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(formatSupport, formatSupportFlags))
|
||||
{
|
||||
// Fallback to format that is supported on every washing machine
|
||||
_lutFormat = PixelFormat::R8G8B8A8_UNorm;
|
||||
}
|
||||
|
||||
// Create pipeline state
|
||||
_psLut.CreatePipelineStates();
|
||||
|
||||
// Load shader
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/ColorGrading"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<ColorGradingPass, &ColorGradingPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ColorGradingPass::setupResources()
|
||||
{
|
||||
// Wait for 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 (!_psLut.IsValid())
|
||||
{
|
||||
StringAnsiView psName;
|
||||
if (_useVolumeTexture)
|
||||
{
|
||||
psDesc.VS = shader->GetVS("VS_WriteToSlice");
|
||||
psDesc.GS = shader->GetGS("GS_WriteToSlice");
|
||||
psName = "PS_Lut3D";
|
||||
}
|
||||
else
|
||||
{
|
||||
psName = "PS_Lut2D";
|
||||
}
|
||||
if (_psLut.Create(psDesc, shader, psName))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ColorGradingPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline state
|
||||
_psLut.Delete();
|
||||
|
||||
// Release assets
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
GPUTexture* ColorGradingPass::RenderLUT(RenderContext& renderContext)
|
||||
{
|
||||
// Ensure to have valid data
|
||||
if (checkIfSkipPass())
|
||||
return nullptr;
|
||||
|
||||
PROFILE_GPU_CPU("Color Grading LUT");
|
||||
|
||||
// For a 3D texture, the viewport is 16x16 (per slice), for a 2D texture, it's unwrapped to 256x16
|
||||
const int32 LutSize = 32; // this must match value in shader (see ColorGrading.shader and PostProcessing.shader)
|
||||
GPUTextureDescription lutDesc;
|
||||
if (_useVolumeTexture)
|
||||
{
|
||||
lutDesc = GPUTextureDescription::New3D(LutSize, LutSize, LutSize, 1, _lutFormat);
|
||||
}
|
||||
else
|
||||
{
|
||||
lutDesc = GPUTextureDescription::New2D(LutSize * LutSize, LutSize, 1, _lutFormat);
|
||||
}
|
||||
const auto lut = RenderTargetPool::Get(lutDesc);
|
||||
|
||||
// Prepare the parameters
|
||||
Data data;
|
||||
auto& toneMapping = renderContext.List->Settings.ToneMapping;
|
||||
auto& colorGrading = renderContext.List->Settings.ColorGrading;
|
||||
// White Balance
|
||||
data.WhiteTemp = toneMapping.WhiteTemperature;
|
||||
data.WhiteTint = toneMapping.WhiteTint;
|
||||
// Shadows
|
||||
data.ColorSaturationShadows = colorGrading.ColorSaturationShadows * colorGrading.ColorSaturation;
|
||||
data.ColorContrastShadows = colorGrading.ColorContrastShadows * colorGrading.ColorContrast;
|
||||
data.ColorGammaShadows = colorGrading.ColorGammaShadows * colorGrading.ColorGamma;
|
||||
data.ColorGainShadows = colorGrading.ColorGainShadows * colorGrading.ColorGain;
|
||||
data.ColorOffsetShadows = colorGrading.ColorOffsetShadows + colorGrading.ColorOffset;
|
||||
data.ColorCorrectionShadowsMax = colorGrading.ShadowsMax;
|
||||
// Midtones
|
||||
data.ColorSaturationMidtones = colorGrading.ColorSaturationMidtones * colorGrading.ColorSaturation;
|
||||
data.ColorContrastMidtones = colorGrading.ColorContrastMidtones * colorGrading.ColorContrast;
|
||||
data.ColorGammaMidtones = colorGrading.ColorGammaMidtones * colorGrading.ColorGamma;
|
||||
data.ColorGainMidtones = colorGrading.ColorGainMidtones * colorGrading.ColorGain;
|
||||
data.ColorOffsetMidtones = colorGrading.ColorOffsetMidtones + colorGrading.ColorOffset;
|
||||
// Highlights
|
||||
data.ColorSaturationHighlights = colorGrading.ColorSaturationHighlights * colorGrading.ColorSaturation;
|
||||
data.ColorContrastHighlights = colorGrading.ColorContrastHighlights * colorGrading.ColorContrast;
|
||||
data.ColorGammaHighlights = colorGrading.ColorGammaHighlights * colorGrading.ColorGamma;
|
||||
data.ColorGainHighlights = colorGrading.ColorGainHighlights * colorGrading.ColorGain;
|
||||
data.ColorOffsetHighlights = colorGrading.ColorOffsetHighlights + colorGrading.ColorOffset;
|
||||
data.ColorCorrectionHighlightsMin = colorGrading.HighlightsMin;
|
||||
//
|
||||
const bool useLut = colorGrading.LutTexture && colorGrading.LutTexture->IsLoaded() && colorGrading.LutTexture->GetResidentMipLevels() > 0 && colorGrading.LutWeight > ZeroTolerance;
|
||||
data.LutWeight = useLut ? colorGrading.LutWeight : 0.0f;
|
||||
|
||||
// Prepare
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
const auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
context->SetViewportAndScissors((float)lutDesc.Width, (float)lutDesc.Height);
|
||||
context->SetState(_psLut.Get((int32)toneMapping.Mode));
|
||||
context->BindSR(0, useLut ? colorGrading.LutTexture->GetTexture() : nullptr);
|
||||
|
||||
// Draw
|
||||
if (_useVolumeTexture)
|
||||
{
|
||||
context->SetRenderTarget(lut->ViewVolume());
|
||||
|
||||
// Render a quad per slice affected by the given bounds
|
||||
const int32 numInstances = lutDesc.Depth;
|
||||
context->DrawFullscreenTriangle(numInstances);
|
||||
}
|
||||
else
|
||||
{
|
||||
context->SetRenderTarget(lut->View());
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
// TODO: this could run in async during scene rendering or sth
|
||||
|
||||
const Viewport viewport = renderContext.Task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
context->UnBindSR(0);
|
||||
|
||||
return lut;
|
||||
}
|
||||
57
Source/Engine/Renderer/ColorGradingPass.h
Normal file
57
Source/Engine/Renderer/ColorGradingPass.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
|
||||
/// <summary>
|
||||
/// Color Grading and Tone Mapping rendering service. Generates HDR LUT for PostFx pass.
|
||||
/// </summary>
|
||||
class ColorGradingPass : public RendererPass<ColorGradingPass>
|
||||
{
|
||||
private:
|
||||
|
||||
bool _useVolumeTexture;
|
||||
PixelFormat _lutFormat;
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineStatePermutationsPs<3> _psLut;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
ColorGradingPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs Look Up Table rendering for the input task.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <returns>Allocated temp render target with a rendered LUT. Can be 2d or 3d based on current graphics hardware caps. Release after usage.</returns>
|
||||
GPUTexture* RenderLUT(RenderContext& renderContext);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psLut.Release();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
122
Source/Engine/Renderer/Config.h
Normal file
122
Source/Engine/Renderer/Config.h
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Math/Vector2.h"
|
||||
#include "Engine/Core/Math/Vector3.h"
|
||||
#include "Engine/Core/Math/Vector4.h"
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains information about GBuffer for shaders.
|
||||
/// </summary>
|
||||
struct GBufferData
|
||||
{
|
||||
Vector4 ViewInfo;
|
||||
Vector4 ScreenSize;
|
||||
Vector3 ViewPos;
|
||||
float ViewFar;
|
||||
Matrix InvViewMatrix;
|
||||
Matrix InvProjectionMatrix;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains information about exponential height fog for shaders.
|
||||
/// </summary>
|
||||
struct ExponentialHeightFogData
|
||||
{
|
||||
Vector3 FogInscatteringColor;
|
||||
float FogMinOpacity;
|
||||
|
||||
float FogDensity;
|
||||
float FogHeight;
|
||||
float FogHeightFalloff;
|
||||
float FogAtViewPosition;
|
||||
|
||||
Vector3 InscatteringLightDirection;
|
||||
float ApplyDirectionalInscattering;
|
||||
|
||||
Vector3 DirectionalInscatteringColor;
|
||||
float DirectionalInscatteringExponent;
|
||||
|
||||
float FogCutoffDistance;
|
||||
float VolumetricFogMaxDistance;
|
||||
float DirectionalInscatteringStartDistance;
|
||||
float StartDistance;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains information about atmosphere fog for shaders.
|
||||
/// </summary>
|
||||
struct AtmosphericFogData
|
||||
{
|
||||
float AtmosphericFogDensityScale;
|
||||
float AtmosphericFogSunDiscScale;
|
||||
float AtmosphericFogDistanceScale;
|
||||
float AtmosphericFogGroundOffset;
|
||||
|
||||
float AtmosphericFogAltitudeScale;
|
||||
float AtmosphericFogStartDistance;
|
||||
float AtmosphericFogPower;
|
||||
float AtmosphericFogDistanceOffset;
|
||||
|
||||
Vector3 AtmosphericFogSunDirection;
|
||||
float AtmosphericFogSunPower;
|
||||
|
||||
Vector3 AtmosphericFogSunColor;
|
||||
float AtmosphericFogDensityOffset;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains information about light for shaders.
|
||||
/// </summary>
|
||||
PACK_STRUCT(struct LightData {
|
||||
Vector2 SpotAngles;
|
||||
float SourceRadius;
|
||||
float SourceLength;
|
||||
Vector3 Color;
|
||||
float MinRoughness;
|
||||
Vector3 Position;
|
||||
float CastShadows;
|
||||
Vector3 Direction;
|
||||
float Radius;
|
||||
float FalloffExponent;
|
||||
float InverseSquared;
|
||||
float Dummy0;
|
||||
float RadiusInv;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains information about light for shaders.
|
||||
/// </summary>
|
||||
PACK_STRUCT(struct LightShadowData {
|
||||
Vector2 ShadowMapSize;
|
||||
float Sharpness;
|
||||
float Fade;
|
||||
float NormalOffsetScale;
|
||||
float Bias;
|
||||
float FadeDistance;
|
||||
uint32 NumCascades;
|
||||
Vector4 CascadeSplits;
|
||||
Matrix ShadowVP[6];
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Packed env probe data
|
||||
/// </summary>
|
||||
PACK_STRUCT(struct ProbeData {
|
||||
Vector4 Data0; // x - Position.x, y - Position.y, z - Position.z, w - unused
|
||||
Vector4 Data1; // x - Radius , y - 1 / Radius, z - Brightness, w - unused
|
||||
});
|
||||
|
||||
// Minimum roughness value used for shading (prevent 0 roughness which causes NaNs in Vis_SmithJointApprox)
|
||||
#define MIN_ROUGHNESS 0.04f
|
||||
|
||||
// Maximum amount of directional light cascades (using CSM technique)
|
||||
#define MAX_CSM_CASCADES 4
|
||||
|
||||
// Default format for the shadow map textures
|
||||
#define SHADOW_MAPS_FORMAT PixelFormat::D16_UNorm
|
||||
|
||||
// Material distortion offsets output pass (material uses PS_Distortion, ForwardPass resolves the offsets)
|
||||
#define Distortion_Pass_Output_Format PixelFormat::R8G8B8A8_UNorm
|
||||
468
Source/Engine/Renderer/DepthOfFieldPass.cpp
Normal file
468
Source/Engine/Renderer/DepthOfFieldPass.cpp
Normal file
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "DepthOfFieldPass.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/Textures/GPUTexture.h"
|
||||
#include "Engine/Graphics/GPUBuffer.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/PostProcessBase.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
|
||||
// This must match hlsl defines
|
||||
#define DOF_MAX_SAMPLE_RADIUS 10
|
||||
#define DOF_GRID_SIZE 450
|
||||
#define DOF_APRON_SIZE DOF_MAX_SAMPLE_RADIUS
|
||||
#define DOF_THREAD_GROUP_SIZE (DOF_GRID_SIZE + (DOF_APRON_SIZE * 2))
|
||||
|
||||
DepthOfFieldPass::DepthOfFieldPass()
|
||||
{
|
||||
}
|
||||
|
||||
String DepthOfFieldPass::ToString() const
|
||||
{
|
||||
return TEXT("DepthOfFieldPass");
|
||||
}
|
||||
|
||||
bool DepthOfFieldPass::Init()
|
||||
{
|
||||
// Disable Depth Of Field for platforms without compute shaders support
|
||||
// (in future we should support it or faster solution using pixel shaders)
|
||||
auto& limits = GPUDevice::Instance->Limits;
|
||||
_platformSupportsDoF = limits.HasCompute;
|
||||
_platformSupportsBokeh = _platformSupportsDoF && limits.HasGeometryShaders && limits.HasDrawIndirect && limits.HasAppendConsumeBuffers;
|
||||
|
||||
// Create pipeline states
|
||||
if (_platformSupportsDoF)
|
||||
{
|
||||
_psDofDepthBlurGeneration = GPUDevice::Instance->CreatePipelineState();
|
||||
_psDoNotGenerateBokeh = GPUDevice::Instance->CreatePipelineState();
|
||||
if (_platformSupportsBokeh)
|
||||
{
|
||||
_psBokehGeneration = GPUDevice::Instance->CreatePipelineState();
|
||||
_psBokeh = GPUDevice::Instance->CreatePipelineState();
|
||||
_psBokehComposite = GPUDevice::Instance->CreatePipelineState();
|
||||
}
|
||||
}
|
||||
|
||||
// Load shaders
|
||||
if (_platformSupportsDoF)
|
||||
{
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/DepthOfField"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<DepthOfFieldPass, &DepthOfFieldPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DepthOfFieldPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_psDofDepthBlurGeneration);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psBokehGeneration);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psDoNotGenerateBokeh);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psBokeh);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psBokehComposite);
|
||||
|
||||
// Release assets
|
||||
_shader.Unlink();
|
||||
_defaultBokehHexagon.Unlink();
|
||||
_defaultBokehOctagon.Unlink();
|
||||
_defaultBokehCircle.Unlink();
|
||||
_defaultBokehCross.Unlink();
|
||||
|
||||
// Release resources
|
||||
SAFE_DELETE_GPU_RESOURCE(_bokehBuffer);
|
||||
SAFE_DELETE_GPU_RESOURCE(_bokehIndirectArgsBuffer);
|
||||
}
|
||||
|
||||
bool DepthOfFieldPass::setupResources()
|
||||
{
|
||||
// Dof
|
||||
if (_platformSupportsDoF == false)
|
||||
return false;
|
||||
|
||||
// Wait for 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 (!_psDofDepthBlurGeneration->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_DofDepthBlurGeneration");
|
||||
if (_psDofDepthBlurGeneration->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psDoNotGenerateBokeh->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_DoNotGenerateBokeh");
|
||||
if (_psDoNotGenerateBokeh->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (_platformSupportsBokeh)
|
||||
{
|
||||
if (!_psBokehGeneration->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_GenerateBokeh");
|
||||
if (_psBokehGeneration->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psBokehComposite->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_BokehComposite");
|
||||
if (_psBokehComposite->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psBokeh->IsValid())
|
||||
{
|
||||
psDesc.VS = shader->GetVS("VS_Bokeh");
|
||||
psDesc.GS = shader->GetGS("GS_Bokeh");
|
||||
psDesc.PS = shader->GetPS("PS_Bokeh");
|
||||
psDesc.BlendMode = BlendingMode::Additive;
|
||||
psDesc.PrimitiveTopologyType = PrimitiveTopologyType::Point;
|
||||
if (_psBokeh->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create buffer
|
||||
if (_bokehBuffer == nullptr)
|
||||
_bokehBuffer = GPUDevice::Instance->CreateBuffer(TEXT("Bokeh Buffer"));
|
||||
if (_bokehIndirectArgsBuffer == nullptr)
|
||||
_bokehIndirectArgsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("Bokeh Indirect Args Buffer"));
|
||||
uint32 indirectArgsBufferInitData[4] = { 0, 1, 0, 0 };
|
||||
if (_bokehIndirectArgsBuffer->Init(GPUBufferDescription::Argument(indirectArgsBufferInitData, sizeof(indirectArgsBufferInitData))))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GPUTexture* DepthOfFieldPass::getDofBokehShape(DepthOfFieldSettings& dofSettings)
|
||||
{
|
||||
Texture* result;
|
||||
#define GET_AND_SET_BOKEH(property, name) if (property == nullptr) property = Content::LoadAsyncInternal<Texture>(TEXT(name)); result = property
|
||||
switch (dofSettings.BokehShape)
|
||||
{
|
||||
case BokehShapeType::Hexagon: GET_AND_SET_BOKEH(_defaultBokehHexagon, "Engine/Textures/Bokeh/Hexagon");
|
||||
break;
|
||||
case BokehShapeType::Octagon: GET_AND_SET_BOKEH(_defaultBokehOctagon, "Engine/Textures/Bokeh/Octagon");
|
||||
break;
|
||||
case BokehShapeType::Circle: GET_AND_SET_BOKEH(_defaultBokehCircle, "Engine/Textures/Bokeh/Circle");
|
||||
break;
|
||||
case BokehShapeType::Cross: GET_AND_SET_BOKEH(_defaultBokehCross, "Engine/Textures/Bokeh/Cross");
|
||||
break;
|
||||
case BokehShapeType::Custom:
|
||||
result = dofSettings.BokehShapeCustom;
|
||||
break;
|
||||
default:
|
||||
result = nullptr;
|
||||
break;
|
||||
}
|
||||
#undef GET_AND_SET_BOKEH
|
||||
return result ? result->GetTexture() : nullptr;
|
||||
}
|
||||
|
||||
GPUTexture* DepthOfFieldPass::Render(RenderContext& renderContext, GPUTexture* input)
|
||||
{
|
||||
// Ensure to have valid data
|
||||
if (!_platformSupportsDoF || checkIfSkipPass())
|
||||
return nullptr;
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
const auto depthBuffer = renderContext.Buffers->DepthBuffer;
|
||||
const auto shader = _shader->GetShader();
|
||||
DepthOfFieldSettings& dofSettings = renderContext.List->Settings.DepthOfField;
|
||||
const bool useDoF = _platformSupportsDoF && (renderContext.View.Flags & ViewFlags::DepthOfField) != 0 && dofSettings.Enabled;
|
||||
|
||||
// Skip if disabled
|
||||
if (!useDoF)
|
||||
return nullptr;
|
||||
|
||||
PROFILE_GPU_CPU("Depth Of Field");
|
||||
|
||||
context->ResetSR();
|
||||
|
||||
// Resolution settings
|
||||
// TODO: render depth of field in 1/4 resolution?
|
||||
const int32 cocResolutionDivider = 1;
|
||||
const int32 dofResolutionDivider = 1;
|
||||
const int32 bokehResolutionDivider = 1;
|
||||
|
||||
// TODO: in low-res DoF maybe use shared HalfResDepth?
|
||||
|
||||
// Cache viewport sizes
|
||||
const int32 w1 = input->Width();
|
||||
const int32 h1 = input->Height();
|
||||
const int32 cocWidth = w1 / cocResolutionDivider;
|
||||
const int32 cocHeight = h1 / cocResolutionDivider;
|
||||
const int32 dofWidth = w1 / dofResolutionDivider;
|
||||
const int32 dofHeight = h1 / dofResolutionDivider;
|
||||
const int32 bokehTargetWidth = w1 / bokehResolutionDivider;
|
||||
const int32 bokehTargetHeight = h1 / bokehResolutionDivider;
|
||||
|
||||
// TODO: maybe we could render particles (whole transparency in general) to the depth buffer to apply DoF on them as well?
|
||||
|
||||
// TODO: reduce amount of used temporary render targets, we could plan rendering steps in more static way and hardcode some logic to make it run faster with less memory usage (less bandwitch)
|
||||
|
||||
// Setup constant buffer
|
||||
Data cbData;
|
||||
{
|
||||
float nearPlane = renderContext.View.Near;
|
||||
float farPlane = renderContext.View.Far;
|
||||
|
||||
float focalRegionHalf = dofSettings.FocalRegion * 0.5f;
|
||||
float nearFocusEnd = Math::Max(0.0f, dofSettings.FocalDistance - focalRegionHalf);
|
||||
float nearFocusStart = Math::Max(0.0f, nearFocusEnd - dofSettings.NearTransitionRange);
|
||||
float farFocusStart = Math::Min(farPlane - 5.0f, dofSettings.FocalDistance + focalRegionHalf);
|
||||
float farFocusEnd = Math::Min(farPlane - 5.0f, farFocusStart + dofSettings.FarTransitionRange);
|
||||
float depthLimitMax = farPlane - 10.0f;
|
||||
|
||||
cbData.DOFDepths.X = nearFocusStart;
|
||||
cbData.DOFDepths.Y = nearFocusEnd;
|
||||
cbData.DOFDepths.Z = farFocusStart;
|
||||
cbData.DOFDepths.W = farFocusEnd;
|
||||
cbData.MaxBokehSize = dofSettings.BokehSize;
|
||||
cbData.BokehBrightnessThreshold = dofSettings.BokehBrightnessThreshold;
|
||||
cbData.BokehBlurThreshold = dofSettings.BokehBlurThreshold;
|
||||
cbData.BokehFalloff = dofSettings.BokehFalloff;
|
||||
cbData.BokehDepthCutoff = dofSettings.BokehDepthCutoff;
|
||||
cbData.DepthLimit = dofSettings.DepthLimit > ZeroTolerance ? Math::Min(dofSettings.DepthLimit, depthLimitMax) : depthLimitMax;
|
||||
cbData.BlurStrength = Math::Saturate(dofSettings.BlurStrength);
|
||||
cbData.BokehBrightness = dofSettings.BokehBrightness;
|
||||
|
||||
cbData.DOFTargetSize.X = static_cast<float>(dofWidth); // TODO: check if this param is binded right. maybe use w1 or bokehTargetWidth?
|
||||
cbData.DOFTargetSize.Y = static_cast<float>(dofHeight);
|
||||
cbData.InputSize.X = static_cast<float>(w1);
|
||||
cbData.InputSize.Y = static_cast<float>(h1);
|
||||
cbData.BokehTargetSize.X = static_cast<float>(bokehTargetWidth);
|
||||
cbData.BokehTargetSize.Y = static_cast<float>(bokehTargetHeight);
|
||||
|
||||
// TODO: use projection matrix instead of this far and near stuff?
|
||||
cbData.ProjectionAB.X = farPlane / (farPlane - nearPlane);
|
||||
cbData.ProjectionAB.Y = (-farPlane * nearPlane) / (farPlane - nearPlane);
|
||||
}
|
||||
|
||||
// Bind constant buffer
|
||||
auto cb = shader->GetCB(0);
|
||||
context->UpdateCB(cb, &cbData);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
// Depth/blur generation pass
|
||||
auto tempDesc = GPUTextureDescription::New2D(cocWidth, cocHeight, DOF_DEPTH_BLUR_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::UnorderedAccess);
|
||||
GPUTexture* depthBlurTarget = RenderTargetPool::Get(tempDesc);
|
||||
context->SetViewportAndScissors((float)cocWidth, (float)cocHeight);
|
||||
context->SetRenderTarget(*depthBlurTarget);
|
||||
context->BindSR(0, depthBuffer);
|
||||
context->SetState(_psDofDepthBlurGeneration);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// CoC Spread pass
|
||||
// todo: add config for CoC spread in postFx settings?
|
||||
// TODO: test it out
|
||||
bool isCoCSpreadEnabled = false;
|
||||
if (isCoCSpreadEnabled)
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
context->ResetUA();
|
||||
context->FlushState();
|
||||
|
||||
tempDesc = GPUTextureDescription::New2D(cocWidth, cocHeight, DOF_DEPTH_BLUR_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::UnorderedAccess);
|
||||
GPUTexture* tempTarget = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
// Horizontal pass
|
||||
context->BindSR(0, depthBlurTarget);
|
||||
//
|
||||
context->BindUA(0, tempTarget->View());
|
||||
//
|
||||
uint32 groupCountX = (cocWidth / DOF_GRID_SIZE) + ((cocWidth % DOF_GRID_SIZE) > 0 ? 1 : 0);
|
||||
uint32 groupCountY = cocHeight;
|
||||
//
|
||||
context->Dispatch(shader->GetCS("CS_CoCSpreadH"), groupCountX, groupCountY, 1);
|
||||
|
||||
// Vertical pass
|
||||
context->BindSR(0, tempTarget);
|
||||
//
|
||||
context->BindUA(0, depthBlurTarget->View());
|
||||
//
|
||||
groupCountX = cocWidth;
|
||||
groupCountY = (cocHeight / DOF_GRID_SIZE) + (cocHeight % DOF_GRID_SIZE) > 0 ? 1 : 0;
|
||||
//
|
||||
context->Dispatch(shader->GetCS("CS_CoCSpreadV"), groupCountX, groupCountY, 1);
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->UnBindSR(0);
|
||||
context->UnBindUA(0);
|
||||
context->FlushState();
|
||||
RenderTargetPool::Release(tempTarget);
|
||||
}
|
||||
|
||||
// Peek temporary render target for dof pass
|
||||
tempDesc = GPUTextureDescription::New2D(dofWidth, dofHeight, DOF_RT_FORMAT);
|
||||
GPUTexture* dofInput = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
// Do the bokeh point generation, or just do a copy if disabled
|
||||
bool isBokehGenerationEnabled = dofSettings.BokehEnabled && _platformSupportsBokeh && dofSettings.BokehBrightness > 0.0f;
|
||||
if (isBokehGenerationEnabled)
|
||||
{
|
||||
// Update bokeh buffer to have enough size for points
|
||||
// TODO: maybe add param to control this? because in most cases there won't be width*height bokehs
|
||||
const uint32 minRequiredElements = dofWidth * dofHeight / 16;
|
||||
const uint32 elementStride = sizeof(BokehPoint);
|
||||
const uint32 minRequiredSize = minRequiredElements * elementStride;
|
||||
// TODO: resize also if main viewport has been resized? or just cache maximum size during last 60 frames and then resize adaptivly to that information
|
||||
if (minRequiredSize > _bokehBuffer->GetSize())
|
||||
{
|
||||
// Resize buffer
|
||||
if (_bokehBuffer->Init(GPUBufferDescription::StructuredAppend(minRequiredElements, elementStride)))
|
||||
{
|
||||
LOG(Fatal, "Cannot create buffer {0}.", TEXT("Bokeh Buffer"));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear bokeh points count
|
||||
context->ResetCounter(_bokehBuffer);
|
||||
|
||||
// Generate bokeh points
|
||||
context->BindSR(0, input);
|
||||
context->BindSR(1, depthBlurTarget);
|
||||
context->SetRenderTarget(*dofInput, _bokehBuffer);
|
||||
context->SetViewportAndScissors((float)dofWidth, (float)dofHeight);
|
||||
context->SetState(_psBokehGeneration);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generate bokeh points
|
||||
context->BindSR(0, input);
|
||||
context->BindSR(1, depthBlurTarget);
|
||||
context->SetRenderTarget(*dofInput);
|
||||
context->SetViewportAndScissors((float)dofWidth, (float)dofHeight);
|
||||
context->SetState(_psDoNotGenerateBokeh);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
// Do depth of field (using compute shaders in full resolution)
|
||||
GPUTexture* dofOutput;
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
context->ResetUA();
|
||||
context->FlushState();
|
||||
{
|
||||
// Peek temporary targets for two blur passes
|
||||
tempDesc = GPUTextureDescription::New2D(dofWidth, dofHeight, DOF_RT_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::UnorderedAccess);
|
||||
auto dofTargetH = RenderTargetPool::Get(tempDesc);
|
||||
auto dofTargetV = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
// Horizontal pass
|
||||
context->BindSR(0, dofInput);
|
||||
context->BindSR(1, depthBlurTarget);
|
||||
//
|
||||
context->BindUA(0, dofTargetH->View());
|
||||
//
|
||||
uint32 groupCountX = (dofWidth / DOF_GRID_SIZE) + ((dofWidth % DOF_GRID_SIZE) > 0 ? 1 : 0);
|
||||
uint32 groupCountY = dofHeight;
|
||||
//
|
||||
context->Dispatch(shader->GetCS("CS_DepthOfFieldH"), groupCountX, groupCountY, 1);
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->UnBindSR(0);
|
||||
context->UnBindUA(0);
|
||||
context->FlushState();
|
||||
|
||||
// Vertical pass
|
||||
context->BindUA(0, dofTargetV->View());
|
||||
//
|
||||
context->BindSR(0, dofTargetH);
|
||||
context->BindSR(1, depthBlurTarget);
|
||||
//
|
||||
groupCountX = dofWidth;
|
||||
groupCountY = (dofHeight / DOF_GRID_SIZE) + ((dofHeight % DOF_GRID_SIZE) > 0 ? 1 : 0);
|
||||
//
|
||||
// TODO: cache Compute Shaders
|
||||
context->Dispatch(shader->GetCS("CS_DepthOfFieldV"), groupCountX, groupCountY, 1);
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Cleanup
|
||||
context->UnBindSR(0);
|
||||
context->UnBindSR(1);
|
||||
context->UnBindUA(0);
|
||||
context->FlushState();
|
||||
RenderTargetPool::Release(dofTargetH);
|
||||
|
||||
dofOutput = dofTargetV;
|
||||
}
|
||||
|
||||
// Cleanup temporary texture
|
||||
RenderTargetPool::Release(dofInput);
|
||||
|
||||
// Render the bokeh points
|
||||
if (isBokehGenerationEnabled)
|
||||
{
|
||||
tempDesc = GPUTextureDescription::New2D(bokehTargetWidth, bokehTargetHeight, DOF_RT_FORMAT);
|
||||
auto bokehTarget = RenderTargetPool::Get(tempDesc);
|
||||
context->Clear(*bokehTarget, Color::Black);
|
||||
|
||||
{
|
||||
// Copy the count from the bokeh point AppendStructuredBuffer to our indirect arguments buffer.
|
||||
// This lets us issue a draw call with number of vertices == the number of points in the buffer, without having to copy anything back to the CPU.
|
||||
context->CopyCounter(_bokehIndirectArgsBuffer, 0, _bokehBuffer);
|
||||
|
||||
// Blend the points additive to an intermediate target
|
||||
context->SetRenderTarget(*bokehTarget);
|
||||
context->SetViewportAndScissors((float)bokehTargetWidth, (float)bokehTargetHeight);
|
||||
|
||||
// Draw the bokeh points
|
||||
context->BindSR(0, (GPUTexture*)getDofBokehShape(dofSettings));
|
||||
context->BindSR(1, depthBlurTarget);
|
||||
context->BindSR(2, _bokehBuffer->View());
|
||||
context->SetState(_psBokeh);
|
||||
context->DrawInstancedIndirect(_bokehIndirectArgsBuffer, 0);
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
// Composite the bokeh rendering results with the depth of field result
|
||||
tempDesc = GPUTextureDescription::New2D(dofWidth, dofHeight, DOF_RT_FORMAT);
|
||||
auto compositeTarget = RenderTargetPool::Get(tempDesc);
|
||||
context->BindSR(0, bokehTarget);
|
||||
context->BindSR(1, dofOutput);
|
||||
context->SetRenderTarget(*compositeTarget);
|
||||
context->SetViewportAndScissors((float)dofWidth, (float)dofHeight);
|
||||
context->SetState(_psBokehComposite);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
RenderTargetPool::Release(bokehTarget);
|
||||
RenderTargetPool::Release(dofOutput);
|
||||
dofOutput = compositeTarget;
|
||||
}
|
||||
|
||||
RenderTargetPool::Release(depthBlurTarget);
|
||||
|
||||
// Return output temporary render target
|
||||
return dofOutput;
|
||||
}
|
||||
103
Source/Engine/Renderer/DepthOfFieldPass.h
Normal file
103
Source/Engine/Renderer/DepthOfFieldPass.h
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Graphics/PostProcessSettings.h"
|
||||
|
||||
#define DOF_DEPTH_BLUR_FORMAT PixelFormat::R16G16_Float
|
||||
#define DOF_RT_FORMAT PixelFormat::R11G11B10_Float
|
||||
|
||||
/// <summary>
|
||||
/// Depth of Field rendering
|
||||
/// </summary>
|
||||
class DepthOfFieldPass : public RendererPass<DepthOfFieldPass>
|
||||
{
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct Data {
|
||||
Vector2 ProjectionAB;
|
||||
float BokehDepthCullThreshold;
|
||||
float BokehDepthCutoff;
|
||||
|
||||
Vector4 DOFDepths;
|
||||
|
||||
float MaxBokehSize;
|
||||
float BokehBrightnessThreshold;
|
||||
float BokehBlurThreshold;
|
||||
float BokehFalloff;
|
||||
|
||||
Vector2 BokehTargetSize;
|
||||
Vector2 DOFTargetSize;
|
||||
|
||||
Vector2 InputSize;
|
||||
float DepthLimit;
|
||||
float BlurStrength;
|
||||
|
||||
Vector3 Dummy;
|
||||
float BokehBrightness;
|
||||
});
|
||||
|
||||
// Structure used for outputting bokeh points to an AppendStructuredBuffer
|
||||
struct BokehPoint
|
||||
{
|
||||
Vector3 Position;
|
||||
float Blur;
|
||||
Vector3 Color;
|
||||
};
|
||||
|
||||
bool _platformSupportsDoF = false;
|
||||
bool _platformSupportsBokeh = false;
|
||||
GPUBuffer* _bokehBuffer = nullptr;
|
||||
GPUBuffer* _bokehIndirectArgsBuffer = nullptr;
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psDofDepthBlurGeneration = nullptr;
|
||||
GPUPipelineState* _psBokehGeneration = nullptr;
|
||||
GPUPipelineState* _psDoNotGenerateBokeh = nullptr;
|
||||
GPUPipelineState* _psBokeh = nullptr;
|
||||
GPUPipelineState* _psBokehComposite = nullptr;
|
||||
AssetReference<Texture> _defaultBokehHexagon;
|
||||
AssetReference<Texture> _defaultBokehOctagon;
|
||||
AssetReference<Texture> _defaultBokehCircle;
|
||||
AssetReference<Texture> _defaultBokehCross;
|
||||
|
||||
public:
|
||||
|
||||
DepthOfFieldPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Perform Depth Of Field rendering for the input task
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="input">Target with rendered HDR frame</param>
|
||||
/// <returns>Allocated temporary render target, should be released by the called. Can be null if pass skipped.</returns>
|
||||
GPUTexture* Render(RenderContext& renderContext, GPUTexture* input);
|
||||
|
||||
private:
|
||||
|
||||
GPUTexture* getDofBokehShape(DepthOfFieldSettings& dofSettings);
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psDofDepthBlurGeneration->ReleaseGPU();
|
||||
_psBokehGeneration->ReleaseGPU();
|
||||
_psBokeh->ReleaseGPU();
|
||||
_psBokehComposite->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
338
Source/Engine/Renderer/DrawCall.h
Normal file
338
Source/Engine/Renderer/DrawCall.h
Normal file
@@ -0,0 +1,338 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Config.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Core/Math/Color.h"
|
||||
|
||||
struct RenderView;
|
||||
struct RenderContext;
|
||||
struct DrawCall;
|
||||
class IMaterial;
|
||||
class RenderTask;
|
||||
class SceneRenderTask;
|
||||
class DirectionalLight;
|
||||
class PointLight;
|
||||
class SpotLight;
|
||||
class SkyLight;
|
||||
class EnvironmentProbe;
|
||||
class Skybox;
|
||||
class Decal;
|
||||
class MaterialBase;
|
||||
class SkinnedMeshDrawData;
|
||||
class Lightmap;
|
||||
class RenderBuffers;
|
||||
class GPUTextureView;
|
||||
class GPUContext;
|
||||
class GPUBuffer;
|
||||
class GPUTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for objects that can render custom sky
|
||||
/// </summary>
|
||||
class ISkyRenderer
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Apply sky material/shader state to the GPU pipeline with custom parameters set (render to GBuffer).
|
||||
/// </summary>
|
||||
/// <param name="context">The context responsible for rendering commands.</param>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="world">The world matrix to use during sky model vertices transformations.</param>
|
||||
virtual void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Volumetric fog feature settings
|
||||
/// </summary>
|
||||
struct VolumetricFogOptions
|
||||
{
|
||||
bool Enable;
|
||||
float ScatteringDistribution;
|
||||
Color Albedo;
|
||||
Color Emissive;
|
||||
float ExtinctionScale;
|
||||
float Distance;
|
||||
Vector4 FogParameters;
|
||||
|
||||
bool UseVolumetricFog() const
|
||||
{
|
||||
return Enable && Distance > 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Interface for objects that can render custom fog/atmosphere
|
||||
/// </summary>
|
||||
class IFogRenderer
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the volumetric fog options.
|
||||
/// </summary>
|
||||
/// <param name="result">The result.</param>
|
||||
virtual void GetVolumetricFogOptions(VolumetricFogOptions& result) const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exponential height fog data.
|
||||
/// </summary>
|
||||
/// <param name="view">The rendering view.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
virtual void GetExponentialHeightFogData(const RenderView& view, ExponentialHeightFogData& result) const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Draw fog using GBuffer inputs
|
||||
/// </summary>
|
||||
/// <param name="context">Context responsible for rendering commands</param>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="output">Output buffer</param>
|
||||
virtual void DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Interface for objects that can render custom atmospheric fog
|
||||
/// </summary>
|
||||
class IAtmosphericFogRenderer
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Draw fog using GBuffer inputs
|
||||
/// </summary>
|
||||
/// <param name="context">Context responsible for rendering commands</param>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="output">Output buffer</param>
|
||||
virtual void DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) = 0;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Renderer draw call used for dynamic batching process.
|
||||
/// </summary>
|
||||
struct DrawCall
|
||||
{
|
||||
struct
|
||||
{
|
||||
/// <summary>
|
||||
/// The geometry index buffer (cannot be null).
|
||||
/// </summary>
|
||||
GPUBuffer* IndexBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// The geometry vertex buffers.
|
||||
/// </summary>
|
||||
GPUBuffer* VertexBuffers[3];
|
||||
|
||||
/// <summary>
|
||||
/// The geometry vertex buffers byte offsets.
|
||||
/// </summary>
|
||||
uint32 VertexBuffersOffsets[3];
|
||||
|
||||
/// <summary>
|
||||
/// The location of the first index read by the GPU from the index buffer.
|
||||
/// </summary>
|
||||
int32 StartIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The indices count.
|
||||
/// </summary>
|
||||
int32 IndicesCount;
|
||||
} Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of instances of the geometry to draw. Set to 0 if use indirect draw arguments buffer.
|
||||
/// </summary>
|
||||
int32 InstanceCount;
|
||||
|
||||
/// <summary>
|
||||
/// The indirect draw arguments offset.
|
||||
/// </summary>
|
||||
uint32 IndirectArgsOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The indirect draw arguments buffer.
|
||||
/// </summary>
|
||||
GPUBuffer* IndirectArgsBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// The target material to use.
|
||||
/// </summary>
|
||||
IMaterial* Material;
|
||||
|
||||
// Particles don't use skinning nor lightmaps so pack those stuff together
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/// <summary>
|
||||
/// Pointer to lightmap for static object with prebaked lighting.
|
||||
/// </summary>
|
||||
const Lightmap* Lightmap;
|
||||
|
||||
/// <summary>
|
||||
/// The skinning data. If set then material should use GPU skinning during rendering.
|
||||
/// </summary>
|
||||
SkinnedMeshDrawData* Skinning;
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
/// <summary>
|
||||
/// The particles data. Used only by the particles shaders.
|
||||
/// </summary>
|
||||
class ParticleBuffer* Particles;
|
||||
|
||||
/// <summary>
|
||||
/// The particle module to draw.
|
||||
/// </summary>
|
||||
class ParticleEmitterGraphCPUNode* Module;
|
||||
};
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Object world transformation matrix.
|
||||
/// </summary>
|
||||
Matrix World;
|
||||
|
||||
// Terrain and particles don't use previous world matrix so pack those stuff together
|
||||
union
|
||||
{
|
||||
/// <summary>
|
||||
/// Object world transformation matrix using during previous frame.
|
||||
/// </summary>
|
||||
Matrix PrevWorld;
|
||||
|
||||
struct
|
||||
{
|
||||
Vector4 HeightmapUVScaleBias;
|
||||
Vector4 NeighborLOD;
|
||||
Vector2 OffsetUV;
|
||||
float CurrentLOD;
|
||||
float ChunkSizeNextLOD;
|
||||
float TerrainChunkSizeLOD0;
|
||||
const class TerrainPatch* Patch;
|
||||
} TerrainData;
|
||||
|
||||
struct
|
||||
{
|
||||
int32 RibbonOrderOffset;
|
||||
float UVTilingDistance;
|
||||
float UVScaleX;
|
||||
float UVScaleY;
|
||||
float UVOffsetX;
|
||||
float UVOffsetY;
|
||||
uint32 SegmentCount;
|
||||
GPUBuffer* SegmentDistances;
|
||||
} Ribbon;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap UVs area that entry occupies.
|
||||
/// </summary>
|
||||
Rectangle LightmapUVsArea;
|
||||
|
||||
/// <summary>
|
||||
/// Object location in the world used for draw calls sorting.
|
||||
/// </summary>
|
||||
Vector3 ObjectPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The world matrix determinant sign (used for geometry that is two sided or has inverse scale - needs to flip normal vectors and change triangles culling).
|
||||
/// </summary>
|
||||
float WorldDeterminantSign;
|
||||
|
||||
/// <summary>
|
||||
/// Object geometry size in the world (unscaled).
|
||||
/// </summary>
|
||||
Vector3 GeometrySize;
|
||||
|
||||
/// <summary>
|
||||
/// The random per-instance value (normalized to range 0-1).
|
||||
/// </summary>
|
||||
float PerInstanceRandom;
|
||||
|
||||
/// <summary>
|
||||
/// The model LOD transition dither factor.
|
||||
/// </summary>
|
||||
float LODDitherFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Does nothing.
|
||||
/// </summary>
|
||||
DrawCall()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether world transform matrix is performing negative scale (then model culling should be inverted).
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if world matrix contains negative scale; otherwise, <c>false</c>.</returns>
|
||||
FORCE_INLINE bool IsNegativeScale() const
|
||||
{
|
||||
return WorldDeterminantSign < 0;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<DrawCall>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Data container for meshes and skinned meshes rendering with minimal state caching.
|
||||
/// Used to update previous world transformation matrix for motion vectors pass and handle LOD transitions blending.
|
||||
/// </summary>
|
||||
struct GeometryDrawStateData
|
||||
{
|
||||
/// <summary>
|
||||
/// The previous frame world transformation matrix for the given geometry instance.
|
||||
/// </summary>
|
||||
Matrix PrevWorld;
|
||||
|
||||
/// <summary>
|
||||
/// The previous frame index. In sync with Engine::FrameCount used to detect new frames and rendering gaps to reset state.
|
||||
/// </summary>
|
||||
uint64 PrevFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The previous frame model LOD index used. It's locked during LOD transition to cache the transition start LOD.
|
||||
/// </summary>
|
||||
char PrevLOD;
|
||||
|
||||
/// <summary>
|
||||
/// The LOD transition timer. Value 255 means the end of the transition (aka no transition), value 0 means transition started.
|
||||
/// Interpolated between 0-255 to smooth transition over several frames and reduce LOD changing artifacts.
|
||||
/// </summary>
|
||||
byte LODTransition;
|
||||
|
||||
GeometryDrawStateData()
|
||||
{
|
||||
PrevWorld = Matrix::Identity;
|
||||
PrevFrame = 0;
|
||||
PrevLOD = -1;
|
||||
LODTransition = 255;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<GeometryDrawStateData>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
#define GEOMETRY_DRAW_STATE_EVENT_BEGIN(drawState, worldMatrix) \
|
||||
const auto frame = Engine::FrameCount; \
|
||||
if (drawState.PrevFrame + 1 < frame) \
|
||||
{ \
|
||||
drawState.PrevWorld = worldMatrix; \
|
||||
}
|
||||
|
||||
#define GEOMETRY_DRAW_STATE_EVENT_END(drawState, worldMatrix) \
|
||||
if (drawState.PrevFrame != frame) \
|
||||
{ \
|
||||
drawState.PrevWorld = _world; \
|
||||
drawState.PrevFrame = frame; \
|
||||
}
|
||||
228
Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp
Normal file
228
Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "LightmapUVsDensity.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Graphics/GPUPipelineState.h"
|
||||
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
#include "Engine/Foliage/Foliage.h"
|
||||
#include "Engine/ShadowsOfMordor/Builder.Config.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Level/Actors/StaticModel.h"
|
||||
|
||||
PACK_STRUCT(struct LightmapUVsDensityMaterialShaderData {
|
||||
Matrix ViewProjectionMatrix;
|
||||
Matrix WorldMatrix;
|
||||
Rectangle LightmapArea;
|
||||
Vector3 WorldInvScale;
|
||||
float LightmapTexelsPerWorldUnit;
|
||||
Vector3 Dummy0;
|
||||
float LightmapSize;
|
||||
});
|
||||
|
||||
LightmapUVsDensityMaterialShader::LightmapUVsDensityMaterialShader()
|
||||
{
|
||||
_ps = GPUDevice::Instance->CreatePipelineState();
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/Editor/LightmapUVsDensity"));
|
||||
if (!_shader)
|
||||
return;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<LightmapUVsDensityMaterialShader, &LightmapUVsDensityMaterialShader::OnShaderReloading>(this);
|
||||
#endif
|
||||
_gridTexture = Content::LoadAsyncInternal<Texture>(TEXT("Engine/Textures/Tiles_M"));
|
||||
}
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
|
||||
void LightmapUVsDensityMaterialShader::OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_ps->ReleaseGPU();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const MaterialInfo& LightmapUVsDensityMaterialShader::GetInfo() const
|
||||
{
|
||||
return _info;
|
||||
}
|
||||
|
||||
bool LightmapUVsDensityMaterialShader::IsReady() const
|
||||
{
|
||||
return _shader && _shader->IsLoaded();
|
||||
}
|
||||
|
||||
DrawPass LightmapUVsDensityMaterialShader::GetDrawModes() const
|
||||
{
|
||||
return DrawPass::GBuffer;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
Actor* FindActorByDrawCall(Actor* actor, const DrawCall& drawCall, float& scaleInLightmap)
|
||||
{
|
||||
const auto asStaticModel = ScriptingObject::Cast<StaticModel>(actor);
|
||||
if (asStaticModel && asStaticModel->GetPerInstanceRandom() == drawCall.PerInstanceRandom && asStaticModel->GetPosition() == drawCall.ObjectPosition)
|
||||
{
|
||||
scaleInLightmap = asStaticModel->GetScaleInLightmap();
|
||||
return asStaticModel;
|
||||
}
|
||||
const auto asFoliage = ScriptingObject::Cast<Foliage>(actor);
|
||||
if (asFoliage)
|
||||
{
|
||||
for (auto i = asFoliage->Instances.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
auto& instance = *i;
|
||||
if (instance.Random == drawCall.PerInstanceRandom && instance.Transform.Translation == drawCall.ObjectPosition)
|
||||
{
|
||||
scaleInLightmap = asFoliage->FoliageTypes[instance.Type].ScaleInLightmap;
|
||||
return asFoliage;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Actor* child : actor->Children)
|
||||
{
|
||||
const auto other = FindActorByDrawCall(child, drawCall, scaleInLightmap);
|
||||
if (other)
|
||||
return other;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void LightmapUVsDensityMaterialShader::Bind(BindParameters& params)
|
||||
{
|
||||
// Prepare
|
||||
auto context = params.GPUContext;
|
||||
auto& drawCall = *params.FirstDrawCall;
|
||||
|
||||
// Setup
|
||||
auto shader = _shader->GetShader();
|
||||
auto cb = shader->GetCB(0);
|
||||
if (!_ps->IsValid())
|
||||
{
|
||||
auto psDesc = GPUPipelineState::Description::Default;
|
||||
psDesc.VS = shader->GetVS("VS");
|
||||
psDesc.PS = shader->GetPS("PS");
|
||||
_ps->Init(psDesc);
|
||||
}
|
||||
|
||||
// Find the static model that produced this draw call
|
||||
const Actor* drawCallActor = nullptr;
|
||||
float scaleInLightmap = 1.0f;
|
||||
if (params.RenderContext.Task)
|
||||
{
|
||||
if (params.RenderContext.Task->ActorsSource & ActorsSources::CustomActors)
|
||||
{
|
||||
for (auto actor : params.RenderContext.Task->CustomActors)
|
||||
{
|
||||
drawCallActor = FindActorByDrawCall(actor, drawCall, scaleInLightmap);
|
||||
if (drawCallActor)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!drawCallActor && params.RenderContext.Task->ActorsSource & ActorsSources::Scenes)
|
||||
{
|
||||
for (auto& scene : Level::Scenes)
|
||||
{
|
||||
drawCallActor = FindActorByDrawCall(scene, drawCall, scaleInLightmap);
|
||||
if (drawCallActor)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the model that produced this draw call
|
||||
const Model* drawCallModel = nullptr;
|
||||
const ModelLOD* drawCallModelLod = nullptr;
|
||||
const Mesh* drawCallMesh = nullptr;
|
||||
for (auto& e : Content::GetAssetsRaw())
|
||||
{
|
||||
auto model = ScriptingObject::Cast<Model>(e.Value);
|
||||
if (!model)
|
||||
continue;
|
||||
for (const auto& lod : model->LODs)
|
||||
{
|
||||
for (const auto& mesh : lod.Meshes)
|
||||
{
|
||||
if (mesh.GetIndexBuffer() == drawCall.Geometry.IndexBuffer)
|
||||
{
|
||||
drawCallModel = model;
|
||||
drawCallModelLod = &lod;
|
||||
drawCallMesh = &mesh;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (drawCallModel)
|
||||
break;
|
||||
}
|
||||
|
||||
// Bind constants
|
||||
if (cb && cb->GetSize())
|
||||
{
|
||||
ASSERT(cb->GetSize() == sizeof(LightmapUVsDensityMaterialShaderData));
|
||||
LightmapUVsDensityMaterialShaderData data;
|
||||
Matrix::Transpose(params.RenderContext.View.Frustum.GetMatrix(), data.ViewProjectionMatrix);
|
||||
Matrix::Transpose(drawCall.World, data.WorldMatrix);
|
||||
const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length();
|
||||
const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length();
|
||||
const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length();
|
||||
data.WorldInvScale = Vector3(
|
||||
scaleX > 0.00001f ? 1.0f / scaleX : 0.0f,
|
||||
scaleY > 0.00001f ? 1.0f / scaleY : 0.0f,
|
||||
scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f);
|
||||
data.LightmapTexelsPerWorldUnit = ShadowsOfMordor::LightmapTexelsPerWorldUnit;
|
||||
data.LightmapSize = 1024.0f;
|
||||
data.LightmapArea = drawCall.LightmapUVsArea;
|
||||
if (drawCallModel)
|
||||
{
|
||||
// Calculate current lightmap slot size for the object (matches the ShadowsOfMordor calculations when baking the lighting)
|
||||
float globalObjectsScale = 1.0f;
|
||||
int32 atlasSize = 1024;
|
||||
int32 chartsPadding = 3;
|
||||
if (drawCallActor)
|
||||
{
|
||||
const Scene* drawCallScene = drawCallActor->GetScene();
|
||||
if (drawCallScene)
|
||||
{
|
||||
globalObjectsScale = drawCallScene->Info.LightmapSettings.GlobalObjectsScale;
|
||||
atlasSize = (int32)drawCallScene->Info.LightmapSettings.AtlasSize;
|
||||
chartsPadding = drawCallScene->Info.LightmapSettings.ChartsPadding;
|
||||
}
|
||||
}
|
||||
BoundingBox box = drawCallModelLod->GetBox(drawCall.World);
|
||||
Vector3 size = box.GetSize();
|
||||
float dimensionsCoeff = size.AverageArithmetic();
|
||||
if (size.X <= 1.0f)
|
||||
dimensionsCoeff = Vector2(size.Y, size.Z).AverageArithmetic();
|
||||
else if (size.Y <= 1.0f)
|
||||
dimensionsCoeff = Vector2(size.X, size.Z).AverageArithmetic();
|
||||
else if (size.Z <= 1.0f)
|
||||
dimensionsCoeff = Vector2(size.Y, size.X).AverageArithmetic();
|
||||
float scale = globalObjectsScale * scaleInLightmap * ShadowsOfMordor::LightmapTexelsPerWorldUnit * dimensionsCoeff;
|
||||
if (scale <= ZeroTolerance)
|
||||
scale = 0.0f;
|
||||
const int32 maximumChartSize = atlasSize - chartsPadding * 2;
|
||||
int32 width = Math::Clamp(Math::CeilToInt(scale), ShadowsOfMordor::LightmapMinChartSize, maximumChartSize);
|
||||
int32 height = Math::Clamp(Math::CeilToInt(scale), ShadowsOfMordor::LightmapMinChartSize, maximumChartSize);
|
||||
float invSize = 1.0f / atlasSize;
|
||||
data.LightmapArea = Rectangle(0, 0, width * invSize, height * invSize);
|
||||
data.LightmapSize = (float)atlasSize;
|
||||
}
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
}
|
||||
|
||||
// Bind grid texture
|
||||
context->BindSR(0, _gridTexture ? _gridTexture->GetTexture() : GPUDevice::Instance->GetDefaultWhiteTexture());
|
||||
|
||||
// Bind pipeline
|
||||
context->SetState(_ps);
|
||||
}
|
||||
|
||||
#endif
|
||||
43
Source/Engine/Renderer/Editor/LightmapUVsDensity.h
Normal file
43
Source/Engine/Renderer/Editor/LightmapUVsDensity.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Assets/Texture.h"
|
||||
#include "Engine/Graphics/Materials/IMaterial.h"
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap UVs Density rendering for profiling and debugging in editor.
|
||||
/// </summary>
|
||||
class LightmapUVsDensityMaterialShader : public IMaterial
|
||||
{
|
||||
private:
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
AssetReference<Texture> _gridTexture;
|
||||
GPUPipelineState* _ps = nullptr;
|
||||
MaterialInfo _info;
|
||||
|
||||
public:
|
||||
|
||||
LightmapUVsDensityMaterialShader();
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [IMaterial]
|
||||
const MaterialInfo& GetInfo() const override;
|
||||
bool IsReady() const override;
|
||||
DrawPass GetDrawModes() const override;
|
||||
void Bind(BindParameters& params) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
84
Source/Engine/Renderer/Editor/VertexColors.cpp
Normal file
84
Source/Engine/Renderer/Editor/VertexColors.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "VertexColors.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/GPUPipelineState.h"
|
||||
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
|
||||
PACK_STRUCT(struct VertexColorsMaterialShaderData {
|
||||
Matrix ViewProjectionMatrix;
|
||||
Matrix WorldMatrix;
|
||||
});
|
||||
|
||||
VertexColorsMaterialShader::VertexColorsMaterialShader()
|
||||
{
|
||||
_ps = GPUDevice::Instance->CreatePipelineState();
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/Editor/VertexColors"));
|
||||
if (!_shader)
|
||||
return;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<VertexColorsMaterialShader, &VertexColorsMaterialShader::OnShaderReloading>(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
|
||||
void VertexColorsMaterialShader::OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_ps->ReleaseGPU();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const MaterialInfo& VertexColorsMaterialShader::GetInfo() const
|
||||
{
|
||||
return _info;
|
||||
}
|
||||
|
||||
bool VertexColorsMaterialShader::IsReady() const
|
||||
{
|
||||
return _shader && _shader->IsLoaded();
|
||||
}
|
||||
|
||||
DrawPass VertexColorsMaterialShader::GetDrawModes() const
|
||||
{
|
||||
return DrawPass::GBuffer;
|
||||
}
|
||||
|
||||
void VertexColorsMaterialShader::Bind(BindParameters& params)
|
||||
{
|
||||
// Prepare
|
||||
auto context = params.GPUContext;
|
||||
auto& drawCall = *params.FirstDrawCall;
|
||||
|
||||
// Setup
|
||||
auto shader = _shader->GetShader();
|
||||
auto cb = shader->GetCB(0);
|
||||
if (!_ps->IsValid())
|
||||
{
|
||||
auto psDesc = GPUPipelineState::Description::Default;
|
||||
psDesc.VS = shader->GetVS("VS");
|
||||
psDesc.PS = shader->GetPS("PS");
|
||||
_ps->Init(psDesc);
|
||||
}
|
||||
|
||||
// Bind constants
|
||||
if (cb && cb->GetSize())
|
||||
{
|
||||
ASSERT(cb->GetSize() == sizeof(VertexColorsMaterialShaderData));
|
||||
VertexColorsMaterialShaderData data;
|
||||
Matrix::Transpose(params.RenderContext.View.Frustum.GetMatrix(), data.ViewProjectionMatrix);
|
||||
Matrix::Transpose(drawCall.World, data.WorldMatrix);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
}
|
||||
|
||||
// Bind pipeline
|
||||
context->SetState(_ps);
|
||||
}
|
||||
|
||||
#endif
|
||||
41
Source/Engine/Renderer/Editor/VertexColors.h
Normal file
41
Source/Engine/Renderer/Editor/VertexColors.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Graphics/Materials/IMaterial.h"
|
||||
|
||||
/// <summary>
|
||||
/// Vertex Colors rendering for profiling and debugging in editor.
|
||||
/// </summary>
|
||||
class VertexColorsMaterialShader : public IMaterial
|
||||
{
|
||||
private:
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _ps = nullptr;
|
||||
MaterialInfo _info;
|
||||
|
||||
public:
|
||||
|
||||
VertexColorsMaterialShader();
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj);
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [IMaterial]
|
||||
const MaterialInfo& GetInfo() const override;
|
||||
bool IsReady() const override;
|
||||
DrawPass GetDrawModes() const override;
|
||||
void Bind(BindParameters& params) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
306
Source/Engine/Renderer/EyeAdaptationPass.cpp
Normal file
306
Source/Engine/Renderer/EyeAdaptationPass.cpp
Normal file
@@ -0,0 +1,306 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "EyeAdaptationPass.h"
|
||||
#include "RenderList.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/PostProcessBase.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "HistogramPass.h"
|
||||
|
||||
PACK_STRUCT(struct EyeAdaptationData {
|
||||
float MinBrightness;
|
||||
float MaxBrightness;
|
||||
float SpeedUp;
|
||||
float SpeedDown;
|
||||
|
||||
float PreExposure;
|
||||
float DeltaTime;
|
||||
float HistogramMul;
|
||||
float HistogramAdd;
|
||||
|
||||
float HistogramLowPercent;
|
||||
float HistogramHighPercent;
|
||||
float DropHistory;
|
||||
float Dummy1;
|
||||
});
|
||||
|
||||
void EyeAdaptationPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer)
|
||||
{
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
auto& settings = renderContext.List->Settings.EyeAdaptation;
|
||||
bool dropHistory = renderContext.Buffers->LastEyeAdaptationTime < ZeroTolerance || renderContext.Task->IsCameraCut;
|
||||
const float time = Time::Draw.UnscaledTime.GetTotalSeconds();
|
||||
//const float frameDelta = Time::ElapsedGameTime.GetTotalSeconds();
|
||||
const float frameDelta = time - renderContext.Buffers->LastEyeAdaptationTime;
|
||||
renderContext.Buffers->LastEyeAdaptationTime = 0.0f;
|
||||
|
||||
// Optionally skip the rendering
|
||||
if (checkIfSkipPass() || (view.Flags & ViewFlags::EyeAdaptation) == 0 || settings.Mode == EyeAdaptationMode::None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_GPU_CPU("Eye Adaptation");
|
||||
|
||||
// Setup constants
|
||||
GPUBuffer* histogramBuffer = nullptr;
|
||||
EyeAdaptationData data;
|
||||
data.MinBrightness = Math::Max(Math::Min(settings.MinBrightness, settings.MaxBrightness), 0.0001f);
|
||||
data.MaxBrightness = Math::Max(settings.MinBrightness, settings.MaxBrightness);
|
||||
data.PreExposure = Math::Exp2(settings.PreExposure);
|
||||
auto mode = settings.Mode == EyeAdaptationMode::AutomaticHistogram && !_canUseHistogram ? EyeAdaptationMode::AutomaticAverageLuminance : settings.Mode;
|
||||
switch (mode)
|
||||
{
|
||||
case EyeAdaptationMode::Manual:
|
||||
{
|
||||
if (Math::IsZero(settings.PreExposure))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case EyeAdaptationMode::AutomaticHistogram:
|
||||
{
|
||||
ASSERT(_canUseHistogram);
|
||||
data.HistogramLowPercent = Math::Clamp(settings.HistogramLowPercent * 0.01f, 0.01f, 0.99f);
|
||||
data.HistogramHighPercent = Math::Clamp(settings.HistogramHighPercent * 0.01f, data.HistogramLowPercent, 1.0f);
|
||||
HistogramPass::Instance()->GetHistogramMad(data.HistogramMul, data.HistogramAdd);
|
||||
|
||||
// Render histogram
|
||||
histogramBuffer = HistogramPass::Instance()->Render(renderContext, colorBuffer);
|
||||
if (!histogramBuffer)
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
case EyeAdaptationMode::AutomaticAverageLuminance:
|
||||
{
|
||||
dropHistory |= renderContext.Buffers->LuminanceMap == nullptr;
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
}
|
||||
data.SpeedUp = settings.SpeedUp;
|
||||
data.SpeedDown = settings.SpeedDown;
|
||||
data.DeltaTime = frameDelta;
|
||||
data.DropHistory = dropHistory ? 1.0f : 0.0f;
|
||||
|
||||
// Update constants
|
||||
const auto shader = _shader->GetShader();
|
||||
const auto cb0 = shader->GetCB(0);
|
||||
context->UpdateCB(cb0, &data);
|
||||
context->BindCB(0, cb0);
|
||||
|
||||
if (mode == EyeAdaptationMode::Manual)
|
||||
{
|
||||
// Apply fixed manual exposure
|
||||
context->SetRenderTarget(*colorBuffer);
|
||||
context->SetViewportAndScissors((float)colorBuffer->Width(), (float)colorBuffer->Height());
|
||||
context->SetState(_psManual);
|
||||
context->DrawFullscreenTriangle();
|
||||
return;
|
||||
}
|
||||
|
||||
GPUTexture* previousLuminanceMap = renderContext.Buffers->LuminanceMap;
|
||||
if (dropHistory && previousLuminanceMap)
|
||||
{
|
||||
RenderTargetPool::Release(previousLuminanceMap);
|
||||
previousLuminanceMap = nullptr;
|
||||
}
|
||||
GPUTexture* currentLuminanceMap = RenderTargetPool::Get(GPUTextureDescription::New2D(1, 1, PixelFormat::R16_Float));
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case EyeAdaptationMode::AutomaticHistogram:
|
||||
{
|
||||
// Blend luminance with the histogram-based luminance
|
||||
context->BindSR(0, histogramBuffer->View());
|
||||
context->BindSR(1, previousLuminanceMap);
|
||||
context->SetRenderTarget(*currentLuminanceMap);
|
||||
context->SetViewportAndScissors(1, 1);
|
||||
context->SetState(_psHistogram);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->UnBindSR(1);
|
||||
context->ResetRenderTarget();
|
||||
|
||||
break;
|
||||
}
|
||||
case EyeAdaptationMode::AutomaticAverageLuminance:
|
||||
{
|
||||
const Int2 luminanceMapSize(colorBuffer->Width() / 2, colorBuffer->Height() / 2);
|
||||
GPUTexture* luminanceMap = RenderTargetPool::Get(GPUTextureDescription::New2D(luminanceMapSize.X, luminanceMapSize.Y, 0, PixelFormat::R16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews));
|
||||
|
||||
// Calculate the luminance for the scene color
|
||||
context->BindSR(0, *colorBuffer);
|
||||
context->SetRenderTarget(luminanceMap->View(0, 0));
|
||||
context->SetViewportAndScissors((float)luminanceMapSize.X, (float)luminanceMapSize.Y);
|
||||
context->SetState(_psLuminanceMap);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Generate the mip chain
|
||||
int32 mipLevel = 1;
|
||||
Int2 mipSize = luminanceMapSize / 2;
|
||||
const int32 totalMips = luminanceMap->MipLevels();
|
||||
while (mipLevel < totalMips)
|
||||
{
|
||||
// Downscale
|
||||
context->SetRenderTarget(luminanceMap->View(0, mipLevel));
|
||||
context->SetViewportAndScissors((float)mipSize.X, (float)mipSize.Y);
|
||||
context->Draw(luminanceMap->View(0, mipLevel - 1));
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Move down
|
||||
if (mipSize.X > 1)
|
||||
mipSize.X >>= 1;
|
||||
if (mipSize.Y > 1)
|
||||
mipSize.Y >>= 1;
|
||||
mipLevel++;
|
||||
}
|
||||
|
||||
if (dropHistory)
|
||||
{
|
||||
// Copy 1x1 luminance value from the last mip map
|
||||
context->SetRenderTarget(*currentLuminanceMap);
|
||||
context->SetViewportAndScissors(1, 1);
|
||||
context->Draw(luminanceMap->View(0, mipLevel - 1));
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blend luminance and copy it from last mip to the separate 1x1 texture
|
||||
context->BindSR(0, luminanceMap->View(0, mipLevel - 1));
|
||||
context->BindSR(1, previousLuminanceMap);
|
||||
context->SetRenderTarget(*currentLuminanceMap);
|
||||
context->SetViewportAndScissors(1, 1);
|
||||
context->SetState(_psBlendLuminance);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->UnBindSR(1);
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
RenderTargetPool::Release(luminanceMap);
|
||||
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
}
|
||||
|
||||
// Apply the luminance
|
||||
context->BindSR(0, *currentLuminanceMap);
|
||||
context->SetRenderTarget(*colorBuffer);
|
||||
context->SetViewportAndScissors((float)colorBuffer->Width(), (float)colorBuffer->Height());
|
||||
context->SetState(_psApplyLuminance);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->UnBindSR(0);
|
||||
|
||||
// Update the luminance map buffer
|
||||
renderContext.Buffers->LastEyeAdaptationTime = time;
|
||||
renderContext.Buffers->LastFrameLuminanceMap = Engine::FrameCount;
|
||||
renderContext.Buffers->LuminanceMap = currentLuminanceMap;
|
||||
|
||||
// Cleanup
|
||||
if (previousLuminanceMap)
|
||||
RenderTargetPool::Release(previousLuminanceMap);
|
||||
}
|
||||
|
||||
String EyeAdaptationPass::ToString() const
|
||||
{
|
||||
return TEXT("EyeAdaptationPass");
|
||||
}
|
||||
|
||||
bool EyeAdaptationPass::Init()
|
||||
{
|
||||
_canUseHistogram = GPUDevice::Instance->Limits.HasCompute;
|
||||
|
||||
// Create pipeline states
|
||||
_psManual = GPUDevice::Instance->CreatePipelineState();
|
||||
_psLuminanceMap = GPUDevice::Instance->CreatePipelineState();
|
||||
_psBlendLuminance = GPUDevice::Instance->CreatePipelineState();
|
||||
_psApplyLuminance = GPUDevice::Instance->CreatePipelineState();
|
||||
_psHistogram = GPUDevice::Instance->CreatePipelineState();
|
||||
|
||||
// Load shaders
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/EyeAdaptation"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<EyeAdaptationPass, &EyeAdaptationPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EyeAdaptationPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_psManual);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psLuminanceMap);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psBlendLuminance);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psApplyLuminance);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psHistogram);
|
||||
|
||||
// Release asset
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
bool EyeAdaptationPass::setupResources()
|
||||
{
|
||||
// Wait for shader
|
||||
if (!_shader->IsLoaded())
|
||||
return true;
|
||||
const auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffer size
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(EyeAdaptationData))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, EyeAdaptationData);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline stages
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (!_psLuminanceMap->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_LuminanceMap");
|
||||
if (_psLuminanceMap->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psBlendLuminance->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_BlendLuminance");
|
||||
if (_psBlendLuminance->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psHistogram->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Histogram");
|
||||
if (_psHistogram->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
psDesc.BlendMode = BlendingMode::Multiply;
|
||||
if (!_psManual->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Manual");
|
||||
if (_psManual->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psApplyLuminance->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_ApplyLuminance");
|
||||
if (_psApplyLuminance->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
56
Source/Engine/Renderer/EyeAdaptationPass.h
Normal file
56
Source/Engine/Renderer/EyeAdaptationPass.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
|
||||
/// <summary>
|
||||
/// Eye adaptation effect based on color buffer luminance.
|
||||
/// </summary>
|
||||
class EyeAdaptationPass : public RendererPass<EyeAdaptationPass>
|
||||
{
|
||||
private:
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psManual = nullptr;
|
||||
GPUPipelineState* _psLuminanceMap = nullptr;
|
||||
GPUPipelineState* _psBlendLuminance = nullptr;
|
||||
GPUPipelineState* _psApplyLuminance = nullptr;
|
||||
GPUPipelineState* _psHistogram = nullptr;
|
||||
bool _canUseHistogram;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs the eye adaptation effect.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="colorBuffer">The input and output color buffer to apply eye adaptation effect to it.</param>
|
||||
void Render(RenderContext& renderContext, GPUTexture* colorBuffer);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psManual->ReleaseGPU();
|
||||
_psLuminanceMap->ReleaseGPU();
|
||||
_psBlendLuminance->ReleaseGPU();
|
||||
_psApplyLuminance->ReleaseGPU();
|
||||
_psHistogram->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
144
Source/Engine/Renderer/ForwardPass.cpp
Normal file
144
Source/Engine/Renderer/ForwardPass.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ForwardPass.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
|
||||
ForwardPass::ForwardPass()
|
||||
: _shader(nullptr)
|
||||
, _psApplyDistortion(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String ForwardPass::ToString() const
|
||||
{
|
||||
return TEXT("ForwardPass");
|
||||
}
|
||||
|
||||
bool ForwardPass::Init()
|
||||
{
|
||||
// Prepare resources
|
||||
_psApplyDistortion = GPUDevice::Instance->CreatePipelineState();
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/Forward"));
|
||||
if (_shader == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<ForwardPass, &ForwardPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ForwardPass::setupResources()
|
||||
{
|
||||
// Check shader
|
||||
if (!_shader->IsLoaded())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const auto shader = _shader->GetShader();
|
||||
|
||||
// Create pipeline stages
|
||||
GPUPipelineState::Description psDesc;
|
||||
if (!_psApplyDistortion->IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
psDesc.PS = shader->GetPS("PS_ApplyDistortion");
|
||||
if (_psApplyDistortion->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ForwardPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_psApplyDistortion);
|
||||
|
||||
// Release assets
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
void ForwardPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output)
|
||||
{
|
||||
PROFILE_GPU_CPU("Forward");
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
auto mainCache = renderContext.List;
|
||||
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
|
||||
// Try to use read-only depth if supported
|
||||
GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer;
|
||||
GPUTextureView* depthBufferHandle = depthBuffer->View();
|
||||
if (depthBuffer->GetDescription().Flags & GPUTextureFlags::ReadOnlyDepthView)
|
||||
depthBufferHandle = depthBuffer->ViewReadOnlyDepth();
|
||||
|
||||
// Check if there is no objects to render or no resources ready
|
||||
auto& forwardList = mainCache->DrawCallsLists[(int32)DrawCallsListType::Forward];
|
||||
auto& distortionList = mainCache->DrawCallsLists[(int32)DrawCallsListType::Distortion];
|
||||
if (distortionList.IsEmpty() || checkIfSkipPass())
|
||||
{
|
||||
// Copy frame
|
||||
context->SetRenderTarget(output->View());
|
||||
context->Draw(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
PROFILE_GPU_CPU("Distortion");
|
||||
|
||||
// Peek temporary render target for the distortion pass
|
||||
// TODO: render distortion in half-res?
|
||||
const int32 width = renderContext.Buffers->GetWidth();
|
||||
const int32 height = renderContext.Buffers->GetHeight();
|
||||
const int32 distortionWidth = width;
|
||||
const int32 distortionHeight = height;
|
||||
const auto tempDesc = GPUTextureDescription::New2D(distortionWidth, distortionHeight, Distortion_Pass_Output_Format);
|
||||
auto distortionRT = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
// Clear distortion vectors
|
||||
context->Clear(distortionRT->View(), Color::Transparent);
|
||||
context->SetViewportAndScissors((float)distortionWidth, (float)distortionHeight);
|
||||
context->SetRenderTarget(depthBufferHandle, distortionRT->View());
|
||||
|
||||
// Render distortion pass
|
||||
view.Pass = DrawPass::Distortion;
|
||||
mainCache->ExecuteDrawCalls(renderContext, distortionList);
|
||||
context->SetViewportAndScissors((float)width, (float)height);
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
|
||||
// Bind inputs
|
||||
context->BindSR(0, input);
|
||||
context->BindSR(1, distortionRT);
|
||||
|
||||
// Copy combined frame with distortion from transparent materials
|
||||
context->SetRenderTarget(output->View());
|
||||
context->SetState(_psApplyDistortion);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
RenderTargetPool::Release(distortionRT);
|
||||
}
|
||||
|
||||
if (!forwardList.IsEmpty())
|
||||
{
|
||||
// Run forward pass
|
||||
view.Pass = DrawPass::Forward;
|
||||
context->SetRenderTarget(depthBufferHandle, output->View());
|
||||
mainCache->ExecuteDrawCalls(renderContext, forwardList);
|
||||
}
|
||||
}
|
||||
57
Source/Engine/Renderer/ForwardPass.h
Normal file
57
Source/Engine/Renderer/ForwardPass.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Graphics/RenderView.h"
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
|
||||
/// <summary>
|
||||
/// Forward rendering pass for transparent geometry.
|
||||
/// </summary>
|
||||
class ForwardPass : public RendererPass<ForwardPass>
|
||||
{
|
||||
private:
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psApplyDistortion;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ForwardPass"/> class.
|
||||
/// </summary>
|
||||
ForwardPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs forward pass rendering for the input task. Renders transparent objects.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="input">Target with renderer frame ready for further processing.</param>
|
||||
/// <param name="output">The output frame.</param>
|
||||
void Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psApplyDistortion->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
381
Source/Engine/Renderer/GBufferPass.cpp
Normal file
381
Source/Engine/Renderer/GBufferPass.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "GBufferPass.h"
|
||||
#include "Engine/Renderer/Editor/VertexColors.h"
|
||||
#include "Engine/Renderer/Editor/LightmapUVsDensity.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Level/Actors/Decal.h"
|
||||
|
||||
PACK_STRUCT(struct GBufferPassData{
|
||||
GBufferData GBuffer;
|
||||
Vector3 Dummy0;
|
||||
int32 ViewMode;
|
||||
});
|
||||
|
||||
String GBufferPass::ToString() const
|
||||
{
|
||||
return TEXT("GBufferPass");
|
||||
}
|
||||
|
||||
bool GBufferPass::Init()
|
||||
{
|
||||
// Create pipeline state
|
||||
_psDebug = GPUDevice::Instance->CreatePipelineState();
|
||||
|
||||
// Load assets
|
||||
_gBufferShader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/GBuffer"));
|
||||
_skyModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Sphere"));
|
||||
_boxModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/SimpleBox"));
|
||||
if (_gBufferShader == nullptr || _skyModel == nullptr || _boxModel == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_gBufferShader.Get()->OnReloading.Bind<GBufferPass, &GBufferPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GBufferPass::setupResources()
|
||||
{
|
||||
ASSERT(_gBufferShader);
|
||||
|
||||
// Check if shader has not been loaded
|
||||
if (!_gBufferShader->IsLoaded())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto gbuffer = _gBufferShader->GetShader();
|
||||
|
||||
// Validate shader constant buffers sizes
|
||||
if (gbuffer->GetCB(0)->GetSize() != sizeof(GBufferPassData))
|
||||
{
|
||||
LOG(Warning, "GBuffer shader has incorrct constant buffers sizes.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline stages
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (!_psDebug->IsValid())
|
||||
{
|
||||
psDesc.PS = gbuffer->GetPS("PS_DebugView");
|
||||
if (_psDebug->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GBufferPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline state
|
||||
SAFE_DELETE_GPU_RESOURCE(_psDebug);
|
||||
|
||||
// Release assets
|
||||
_gBufferShader.Unlink();
|
||||
_skyModel.Unlink();
|
||||
_boxModel.Unlink();
|
||||
|
||||
#if USE_EDITOR
|
||||
SAFE_DELETE(_lightmapUVsDensityMaterialShader);
|
||||
SAFE_DELETE(_vertexColorsMaterialShader);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer)
|
||||
{
|
||||
PROFILE_GPU_CPU("GBuffer");
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
GPUTextureView* targetBuffers[5] =
|
||||
{
|
||||
lightBuffer,
|
||||
renderContext.Buffers->GBuffer0->View(),
|
||||
renderContext.Buffers->GBuffer1->View(),
|
||||
renderContext.Buffers->GBuffer2->View(),
|
||||
renderContext.Buffers->GBuffer3->View(),
|
||||
};
|
||||
view.Pass = DrawPass::GBuffer;
|
||||
|
||||
// Clear GBuffer
|
||||
{
|
||||
PROFILE_GPU_CPU("Clear");
|
||||
|
||||
context->ClearDepth(*renderContext.Buffers->DepthBuffer);
|
||||
context->Clear(lightBuffer, Color::Transparent);
|
||||
context->Clear(renderContext.Buffers->GBuffer0->View(), Color::Transparent);
|
||||
context->Clear(renderContext.Buffers->GBuffer1->View(), Color::Transparent);
|
||||
context->Clear(renderContext.Buffers->GBuffer2->View(), Color(1, 0, 0, 0));
|
||||
context->Clear(renderContext.Buffers->GBuffer3->View(), Color::Transparent);
|
||||
}
|
||||
|
||||
// Ensure to have valid data
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Resources are missing. Do not perform rendering.
|
||||
return;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
// Override draw calls material to use material debug shader
|
||||
if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
|
||||
{
|
||||
if (!_lightmapUVsDensityMaterialShader)
|
||||
_lightmapUVsDensityMaterialShader = New<LightmapUVsDensityMaterialShader>();
|
||||
if (_lightmapUVsDensityMaterialShader->IsReady())
|
||||
{
|
||||
auto& drawCallsList = renderContext.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer];
|
||||
for (int32 i : drawCallsList.Indices)
|
||||
{
|
||||
auto& drawCall = renderContext.List->DrawCalls[i];
|
||||
if (drawCall.Material->IsSurface())
|
||||
{
|
||||
drawCall.Material = _lightmapUVsDensityMaterialShader;
|
||||
}
|
||||
}
|
||||
if (!_lightmapUVsDensityMaterialShader->CanUseInstancing())
|
||||
{
|
||||
drawCallsList.CanUseInstancing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (renderContext.View.Mode == ViewMode::VertexColors)
|
||||
{
|
||||
if (!_vertexColorsMaterialShader)
|
||||
_vertexColorsMaterialShader = New<VertexColorsMaterialShader>();
|
||||
if (_vertexColorsMaterialShader->IsReady())
|
||||
{
|
||||
auto& drawCallsList = renderContext.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer];
|
||||
for (int32 i : drawCallsList.Indices)
|
||||
{
|
||||
auto& drawCall = renderContext.List->DrawCalls[i];
|
||||
if (drawCall.Material->IsSurface())
|
||||
{
|
||||
drawCall.Material = _vertexColorsMaterialShader;
|
||||
}
|
||||
}
|
||||
if (!_vertexColorsMaterialShader->CanUseInstancing())
|
||||
{
|
||||
drawCallsList.CanUseInstancing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Draw objects that can get decals
|
||||
context->SetRenderTarget(*renderContext.Buffers->DepthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers)));
|
||||
renderContext.List->ExecuteDrawCalls(renderContext, DrawCallsListType::GBuffer);
|
||||
|
||||
// Draw decals
|
||||
DrawDecals(renderContext, lightBuffer);
|
||||
|
||||
// Draw objects that cannot get decals
|
||||
context->SetRenderTarget(*renderContext.Buffers->DepthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers)));
|
||||
renderContext.List->ExecuteDrawCalls(renderContext, DrawCallsListType::GBufferNoDecals);
|
||||
|
||||
// Draw sky
|
||||
if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered())
|
||||
{
|
||||
PROFILE_GPU_CPU("Sky");
|
||||
|
||||
// Cache data
|
||||
auto model = _skyModel.Get();
|
||||
auto box = model->GetBox();
|
||||
|
||||
// Calculate sphere model transform to cover far plane
|
||||
Matrix m1, m2;
|
||||
Matrix::Scaling(view.Far / (box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum
|
||||
Matrix::CreateWorld(view.Position, Vector3::Up, Vector3::Backward, m2); // Rotate sphere model
|
||||
m1 *= m2;
|
||||
|
||||
// Draw sky
|
||||
renderContext.List->Sky->ApplySky(context, renderContext, m1);
|
||||
model->Render(context);
|
||||
}
|
||||
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
bool SortDecal(Decal* const& a, Decal* const& b)
|
||||
{
|
||||
return a->SortOrder < b->SortOrder;
|
||||
}
|
||||
|
||||
void GBufferPass::RenderDebug(RenderContext& renderContext)
|
||||
{
|
||||
// Check if has resources loaded
|
||||
if (setupResources())
|
||||
return;
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto lights = _gBufferShader->GetShader();
|
||||
GBufferPassData data;
|
||||
|
||||
// Set constants buffer
|
||||
SetInputs(renderContext.View, data.GBuffer);
|
||||
data.ViewMode = static_cast<int32>(renderContext.View.Mode);
|
||||
auto cb = lights->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
// Bind inputs
|
||||
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(4, renderContext.Buffers->GBuffer3);
|
||||
|
||||
// Combine frame
|
||||
context->SetState(_psDebug);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Cleanup
|
||||
context->ResetSR();
|
||||
}
|
||||
|
||||
bool GBufferPass::IsDebugView(ViewMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case ViewMode::Unlit:
|
||||
case ViewMode::Diffuse:
|
||||
case ViewMode::Normals:
|
||||
case ViewMode::Depth:
|
||||
case ViewMode::AmbientOcclusion:
|
||||
case ViewMode::Metalness:
|
||||
case ViewMode::Roughness:
|
||||
case ViewMode::Specular:
|
||||
case ViewMode::SpecularColor:
|
||||
case ViewMode::SubsurfaceColor:
|
||||
case ViewMode::ShadingModel:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GBufferPass::SetInputs(const RenderView& view, GBufferData& gBuffer)
|
||||
{
|
||||
// GBuffer params:
|
||||
// ViewInfo : x-1/Projection[0,0] y-1/Projection[1,1] z-(Far / (Far - Near) w-(-Far * Near) / (Far - Near) / Far)
|
||||
// ScreenSize : x-Width y-Height z-1 / Width w-1 / Height
|
||||
// ViewPos,ViewFar : x,y,z - world space view position w-Far
|
||||
// InvViewMatrix : inverse view matrix (4 rows by 4 columns)
|
||||
// InvProjectionMatrix : inverse projection matrix (4 rows by 4 columns)
|
||||
gBuffer.ViewInfo = view.ViewInfo;
|
||||
gBuffer.ScreenSize = view.ScreenSize;
|
||||
gBuffer.ViewPos = view.Position;
|
||||
gBuffer.ViewFar = view.Far;
|
||||
Matrix::Transpose(view.IV, gBuffer.InvViewMatrix);
|
||||
Matrix::Transpose(view.IP, gBuffer.InvProjectionMatrix);
|
||||
}
|
||||
|
||||
void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer)
|
||||
{
|
||||
// Skip if no decals to render
|
||||
auto& decals = renderContext.List->Decals;
|
||||
if (decals.IsEmpty() || _boxModel == nullptr || !_boxModel->CanBeRendered())
|
||||
return;
|
||||
|
||||
PROFILE_GPU_CPU("Decals");
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto gpuContext = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
auto model = _boxModel.Get();
|
||||
auto buffers = renderContext.Buffers;
|
||||
|
||||
// Sort decals from the lowest order to the highest order
|
||||
Sorting::QuickSort(decals.Get(), (int32)decals.Count(), &SortDecal);
|
||||
|
||||
// TODO: batch decals using the same material
|
||||
|
||||
// TODO: sort decals by the blending mode within the same order
|
||||
|
||||
// Prepare
|
||||
DrawCall drawCall;
|
||||
MaterialBase::BindParameters bindParams(gpuContext, renderContext, drawCall);
|
||||
drawCall.Material = nullptr;
|
||||
drawCall.Lightmap = nullptr;
|
||||
drawCall.Skinning = nullptr;
|
||||
drawCall.WorldDeterminantSign = 1.0f;
|
||||
|
||||
// Draw all decals
|
||||
for (int32 i = 0; i < decals.Count(); i++)
|
||||
{
|
||||
const auto decal = decals[i];
|
||||
ASSERT(decal && decal->Material);
|
||||
decal->GetWorld(&drawCall.World);
|
||||
drawCall.ObjectPosition = drawCall.World.GetTranslation();
|
||||
|
||||
gpuContext->ResetRenderTarget();
|
||||
|
||||
// Bind output
|
||||
const MaterialInfo& info = decal->Material->GetInfo();
|
||||
switch (info.DecalBlendingMode)
|
||||
{
|
||||
case MaterialDecalBlendingMode::Translucent:
|
||||
{
|
||||
GPUTextureView* targetBuffers[4];
|
||||
int32 count = 2;
|
||||
targetBuffers[0] = buffers->GBuffer0->View();
|
||||
targetBuffers[1] = buffers->GBuffer2->View();
|
||||
if (info.UsageFlags & MaterialUsageFlags::UseEmissive)
|
||||
{
|
||||
count++;
|
||||
targetBuffers[2] = lightBuffer;
|
||||
|
||||
if (info.UsageFlags & MaterialUsageFlags::UseNormal)
|
||||
{
|
||||
count++;
|
||||
targetBuffers[3] = buffers->GBuffer1->View();
|
||||
}
|
||||
}
|
||||
else if (info.UsageFlags & MaterialUsageFlags::UseNormal)
|
||||
{
|
||||
count++;
|
||||
targetBuffers[2] = buffers->GBuffer1->View();
|
||||
}
|
||||
gpuContext->SetRenderTarget(nullptr, ToSpan(targetBuffers, count));
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Stain:
|
||||
{
|
||||
gpuContext->SetRenderTarget(buffers->GBuffer0->View());
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Normal:
|
||||
{
|
||||
gpuContext->SetRenderTarget(buffers->GBuffer1->View());
|
||||
break;
|
||||
}
|
||||
case MaterialDecalBlendingMode::Emissive:
|
||||
{
|
||||
gpuContext->SetRenderTarget(lightBuffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw decal
|
||||
drawCall.PerInstanceRandom = decal->GetPerInstanceRandom();
|
||||
decal->Material->Bind(bindParams);
|
||||
model->Render(gpuContext);
|
||||
}
|
||||
|
||||
gpuContext->ResetSR();
|
||||
}
|
||||
72
Source/Engine/Renderer/GBufferPass.h
Normal file
72
Source/Engine/Renderer/GBufferPass.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
|
||||
/// <summary>
|
||||
/// Rendering scene to the GBuffer
|
||||
/// </summary>
|
||||
class GBufferPass : public RendererPass<GBufferPass>
|
||||
{
|
||||
private:
|
||||
|
||||
AssetReference<Shader> _gBufferShader;
|
||||
GPUPipelineState* _psDebug = nullptr;
|
||||
AssetReference<Model> _skyModel;
|
||||
AssetReference<Model> _boxModel;
|
||||
#if USE_EDITOR
|
||||
class LightmapUVsDensityMaterialShader* _lightmapUVsDensityMaterialShader = nullptr;
|
||||
class VertexColorsMaterialShader* _vertexColorsMaterialShader = nullptr;
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Fill GBuffer
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="lightBuffer">Light buffer to output material emissive light and precomputed indirect lighting</param>
|
||||
void Fill(RenderContext& renderContext, GPUTextureView* lightBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// Render debug view
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
void RenderDebug(RenderContext& renderContext);
|
||||
|
||||
public:
|
||||
|
||||
static bool IsDebugView(ViewMode mode);
|
||||
|
||||
/// <summary>
|
||||
/// Set GBuffer inputs structure for given render task
|
||||
/// </summary>
|
||||
/// <param name="view">The rendering view.</param>
|
||||
/// <param name="gBuffer">GBuffer input to setup</param>
|
||||
static void SetInputs(const RenderView& view, GBufferData& gBuffer);
|
||||
|
||||
private:
|
||||
|
||||
void DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer);
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psDebug->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
127
Source/Engine/Renderer/HistogramPass.cpp
Normal file
127
Source/Engine/Renderer/HistogramPass.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "HistogramPass.h"
|
||||
#include "RenderList.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/PostProcessBase.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
|
||||
// Those defines must match the HLSL
|
||||
#define THREADGROUP_SIZE_X 16
|
||||
#define THREADGROUP_SIZE_Y 16
|
||||
#define HISTOGRAM_SIZE 64
|
||||
|
||||
PACK_STRUCT(struct HistogramData {
|
||||
uint32 InputSizeX;
|
||||
uint32 InputSizeY;
|
||||
float HistogramMul;
|
||||
float HistogramAdd;
|
||||
});
|
||||
|
||||
GPUBuffer* HistogramPass::Render(RenderContext& renderContext, GPUTexture* colorBuffer)
|
||||
{
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();;
|
||||
if (checkIfSkipPass() || !_isSupported)
|
||||
return nullptr;
|
||||
|
||||
PROFILE_GPU_CPU("Histogram");
|
||||
|
||||
// Setup constants
|
||||
HistogramData data;
|
||||
const uint32 colorBufferX = colorBuffer->Width();
|
||||
const uint32 colorBufferY = colorBuffer->Height();
|
||||
data.InputSizeX = colorBufferX;
|
||||
data.InputSizeY = colorBufferY;
|
||||
GetHistogramMad(data.HistogramMul, data.HistogramAdd);
|
||||
|
||||
// Update constants
|
||||
const auto shader = _shader->GetShader();
|
||||
const auto cb0 = shader->GetCB(0);
|
||||
context->UpdateCB(cb0, &data);
|
||||
context->BindCB(0, cb0);
|
||||
|
||||
// Clear the histogram buffer
|
||||
context->BindUA(0, _histogramBuffer->View());
|
||||
context->Dispatch(_csClearHistogram, (HISTOGRAM_SIZE + THREADGROUP_SIZE_X - 1) / THREADGROUP_SIZE_X, 1, 1);
|
||||
|
||||
// Generate the histogram
|
||||
context->BindSR(0, colorBuffer);
|
||||
context->BindUA(0, _histogramBuffer->View());
|
||||
context->Dispatch(_csGenerateHistogram, (colorBufferX + THREADGROUP_SIZE_X - 1) / THREADGROUP_SIZE_X, (colorBufferY + THREADGROUP_SIZE_Y - 1) / THREADGROUP_SIZE_Y, 1);
|
||||
|
||||
// Cleanup
|
||||
context->ResetUA();
|
||||
context->ResetSR();
|
||||
|
||||
return _histogramBuffer;
|
||||
}
|
||||
|
||||
void HistogramPass::GetHistogramMad(float& multiply, float& add)
|
||||
{
|
||||
const float histogramLogMin = -8.0f;
|
||||
const float histogramLogMax = 6.0f;
|
||||
const float histogramLogRange = histogramLogMax - histogramLogMin;
|
||||
multiply = 1.0f / histogramLogRange;
|
||||
add = -histogramLogMin * multiply;
|
||||
}
|
||||
|
||||
String HistogramPass::ToString() const
|
||||
{
|
||||
return TEXT("HistogramPass");
|
||||
}
|
||||
|
||||
bool HistogramPass::Init()
|
||||
{
|
||||
_isSupported = GPUDevice::Instance->Limits.HasCompute;
|
||||
if (!_isSupported)
|
||||
return false;
|
||||
|
||||
// Create buffer for histogram
|
||||
_histogramBuffer = GPUDevice::Instance->CreateBuffer(TEXT("Histogram"));
|
||||
if (_histogramBuffer->Init(GPUBufferDescription::Buffer(HISTOGRAM_SIZE * sizeof(uint32), GPUBufferFlags::Structured | GPUBufferFlags::ShaderResource | GPUBufferFlags::UnorderedAccess, PixelFormat::R32_UInt, nullptr, sizeof(uint32))))
|
||||
return true;
|
||||
|
||||
// Load shaders
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/Histogram"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<HistogramPass, &HistogramPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HistogramPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
SAFE_DELETE_GPU_RESOURCE(_histogramBuffer);
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
bool HistogramPass::setupResources()
|
||||
{
|
||||
// Wait for shader
|
||||
if (!_shader->IsLoaded())
|
||||
return true;
|
||||
const auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffer size
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(HistogramData))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, HistogramData);
|
||||
return true;
|
||||
}
|
||||
|
||||
_csClearHistogram = shader->GetCS("CS_ClearHistogram");
|
||||
_csGenerateHistogram = shader->GetCS("CS_GenerateHistogram");
|
||||
|
||||
return false;
|
||||
}
|
||||
59
Source/Engine/Renderer/HistogramPass.h
Normal file
59
Source/Engine/Renderer/HistogramPass.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
|
||||
/// <summary>
|
||||
/// Luminance histogram rendering pass. Uses compute shaders.
|
||||
/// </summary>
|
||||
class HistogramPass : public RendererPass<HistogramPass>
|
||||
{
|
||||
private:
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUShaderProgramCS* _csClearHistogram;
|
||||
GPUShaderProgramCS* _csGenerateHistogram;
|
||||
GPUBuffer* _histogramBuffer = nullptr;
|
||||
bool _isSupported;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs the histogram rendering.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="colorBuffer">The input color buffer to use as a luminance source.</param>
|
||||
/// <returns>The created histogram, or null if failed or not supported.</param>
|
||||
GPUBuffer* Render(RenderContext& renderContext, GPUTexture* colorBuffer);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the multiply and add value to pack or unpack data for histogram buffer.
|
||||
/// </summary>
|
||||
/// <param name="multiply">The multiply factor.</param>
|
||||
/// <param name="add">The add factor.</param>
|
||||
void GetHistogramMad(float& multiply, float& add);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_csClearHistogram = nullptr;
|
||||
_csGenerateHistogram = nullptr;
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
431
Source/Engine/Renderer/LightPass.cpp
Normal file
431
Source/Engine/Renderer/LightPass.cpp
Normal file
@@ -0,0 +1,431 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "LightPass.h"
|
||||
#include "ShadowsPass.h"
|
||||
#include "GBufferPass.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Content/Assets/CubeTexture.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
|
||||
PACK_STRUCT(struct PerLight{
|
||||
LightData Light;
|
||||
Matrix WVP;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct PerFrame{
|
||||
GBufferData GBuffer;
|
||||
});
|
||||
|
||||
LightPass::LightPass()
|
||||
: _shader(nullptr)
|
||||
, _psLightSkyNormal(nullptr)
|
||||
, _psLightSkyInverted(nullptr)
|
||||
, _sphereModel(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String LightPass::ToString() const
|
||||
{
|
||||
return TEXT("LightPass");
|
||||
}
|
||||
|
||||
bool LightPass::Init()
|
||||
{
|
||||
// Create pipeline states
|
||||
_psLightDir.CreatePipelineStates();
|
||||
_psLightPointNormal.CreatePipelineStates();
|
||||
_psLightPointInverted.CreatePipelineStates();
|
||||
_psLightSpotNormal.CreatePipelineStates();
|
||||
_psLightSpotInverted.CreatePipelineStates();
|
||||
_psLightSkyNormal = GPUDevice::Instance->CreatePipelineState();
|
||||
_psLightSkyInverted = GPUDevice::Instance->CreatePipelineState();
|
||||
|
||||
// Load assets
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/Lights"));
|
||||
_sphereModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/SphereLowPoly"));
|
||||
if (_shader == nullptr || _sphereModel == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<LightPass, &LightPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
auto format = PixelFormat::R8G8_UNorm;
|
||||
if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(format).Support, (FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D)))
|
||||
{
|
||||
format = PixelFormat::B8G8R8A8_UNorm;
|
||||
}
|
||||
_shadowMaskFormat = format;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LightPass::setupResources()
|
||||
{
|
||||
// Wait for the assets
|
||||
if (!_sphereModel->CanBeRendered() || !_shader->IsLoaded())
|
||||
return true;
|
||||
auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffers sizes
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(PerLight))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, PerLight);
|
||||
return true;
|
||||
}
|
||||
if (shader->GetCB(1)->GetSize() != sizeof(PerFrame))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 1, PerFrame);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline stages
|
||||
GPUPipelineState::Description psDesc;
|
||||
if (!_psLightDir.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
psDesc.BlendMode = BlendingMode::Add;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
if (_psLightDir.Create(psDesc, shader, "PS_Directional"))
|
||||
return true;
|
||||
}
|
||||
if (!_psLightPointNormal.IsValid() || !_psLightPointInverted.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc.BlendMode = BlendingMode::Add;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
psDesc.CullMode = CullMode::Normal;
|
||||
psDesc.VS = shader->GetVS("VS_Model");
|
||||
if (_psLightPointNormal.Create(psDesc, shader, "PS_Point"))
|
||||
return true;
|
||||
psDesc.CullMode = CullMode::Inverted;
|
||||
if (_psLightPointInverted.Create(psDesc, shader, "PS_Point"))
|
||||
return true;
|
||||
}
|
||||
if (!_psLightSpotNormal.IsValid() || !_psLightSpotInverted.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc.BlendMode = BlendingMode::Add;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
psDesc.CullMode = CullMode::Normal;
|
||||
psDesc.VS = shader->GetVS("VS_Model");
|
||||
if (_psLightSpotNormal.Create(psDesc, shader, "PS_Spot"))
|
||||
return true;
|
||||
psDesc.CullMode = CullMode::Inverted;
|
||||
if (_psLightSpotInverted.Create(psDesc, shader, "PS_Spot"))
|
||||
return true;
|
||||
}
|
||||
if (!_psLightSkyNormal->IsValid() || !_psLightSkyInverted->IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc.BlendMode = BlendingMode::Add;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
psDesc.CullMode = CullMode::Normal;
|
||||
psDesc.VS = shader->GetVS("VS_Model");
|
||||
psDesc.PS = shader->GetPS("PS_Sky");
|
||||
if (_psLightSkyNormal->Init(psDesc))
|
||||
return true;
|
||||
psDesc.CullMode = CullMode::Inverted;
|
||||
if (_psLightSkyInverted->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CanRenderShadow(RenderView& view, const T& light)
|
||||
{
|
||||
bool result = false;
|
||||
switch ((ShadowsCastingMode)light.ShadowsMode)
|
||||
{
|
||||
case ShadowsCastingMode::StaticOnly:
|
||||
result = view.IsOfflinePass;
|
||||
break;
|
||||
case ShadowsCastingMode::DynamicOnly:
|
||||
result = !view.IsOfflinePass;
|
||||
break;
|
||||
case ShadowsCastingMode::All:
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result && light.ShadowsStrength > ZeroTolerance;
|
||||
}
|
||||
|
||||
void LightPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
_psLightDir.Delete();
|
||||
_psLightPointNormal.Delete();
|
||||
_psLightPointInverted.Delete();
|
||||
_psLightSpotNormal.Delete();
|
||||
_psLightSpotInverted.Delete();
|
||||
SAFE_DELETE_GPU_RESOURCE(_psLightSkyNormal);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psLightSkyInverted);
|
||||
|
||||
// Release assets
|
||||
_sphereModel.Unlink();
|
||||
}
|
||||
|
||||
void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightBuffer)
|
||||
{
|
||||
const float sphereModelScale = 3.0f;
|
||||
|
||||
// Ensure to have valid data
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Resources are missing. Do not perform rendering.
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_GPU_CPU("Lights");
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
auto mainCache = renderContext.List;
|
||||
const auto lightShader = _shader->GetShader();
|
||||
const bool useShadows = ShadowsPass::Instance()->IsReady() && ((view.Flags & ViewFlags::Shadows) != 0);
|
||||
const bool disableSpecular = (view.Flags & ViewFlags::SpecularLight) == 0;
|
||||
|
||||
// Temporary data
|
||||
PerLight perLight;
|
||||
PerFrame perFrame;
|
||||
|
||||
// Bind output
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
|
||||
// Set per frame data
|
||||
GBufferPass::SetInputs(renderContext.View, perFrame.GBuffer);
|
||||
auto cb0 = lightShader->GetCB(0);
|
||||
auto cb1 = lightShader->GetCB(1);
|
||||
context->UpdateCB(cb1, &perFrame);
|
||||
|
||||
// Prepare shadows rendering (is will be used)
|
||||
if (useShadows)
|
||||
{
|
||||
ShadowsPass::Instance()->Prepare(renderContext, context);
|
||||
}
|
||||
|
||||
// Bind inputs
|
||||
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(4, renderContext.Buffers->GBuffer3);
|
||||
|
||||
// Check if debug lights
|
||||
if (renderContext.View.Mode == ViewMode::LightBuffer)
|
||||
{
|
||||
// Clear diffuse
|
||||
context->Clear(renderContext.Buffers->GBuffer0->View(), Color::White);
|
||||
}
|
||||
|
||||
// Fullscreen shadow mask buffer
|
||||
GPUTexture* shadowMask = nullptr;
|
||||
#define GET_SHADOW_MASK() \
|
||||
if (!shadowMask) { \
|
||||
auto rtDesc = GPUTextureDescription::New2D(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight(), _shadowMaskFormat); \
|
||||
shadowMask = RenderTargetPool::Get(rtDesc); \
|
||||
} \
|
||||
auto shadowMaskView = shadowMask->View()
|
||||
|
||||
// Render all point lights
|
||||
for (int32 lightIndex = 0; lightIndex < mainCache->PointLights.Count(); lightIndex++)
|
||||
{
|
||||
PROFILE_GPU_CPU("Point Light");
|
||||
|
||||
// Cache data
|
||||
auto& light = mainCache->PointLights[lightIndex];
|
||||
float lightRadius = light.Radius;
|
||||
Vector3 lightPosition = light.Position;
|
||||
const bool renderShadow = useShadows && CanRenderShadow(view, light) && ShadowsPass::Instance()->CanRenderShadow(renderContext, light);
|
||||
bool useIES = light.IESTexture != nullptr;
|
||||
|
||||
// Get distance from view center to light center less radius (check if view is inside a sphere)
|
||||
float distance = ViewToCenterLessRadius(view, lightPosition, lightRadius * sphereModelScale);
|
||||
bool isViewInside = distance < 0;
|
||||
|
||||
// Calculate world view projection matrix for the light sphere
|
||||
Matrix world, wvp, matrix;
|
||||
Matrix::Scaling(lightRadius * sphereModelScale, wvp);
|
||||
Matrix::Translation(lightPosition, matrix);
|
||||
Matrix::Multiply(wvp, matrix, world);
|
||||
Matrix::Multiply(world, view.ViewProjection(), wvp);
|
||||
|
||||
// Check if render shadow
|
||||
if (renderShadow)
|
||||
{
|
||||
GET_SHADOW_MASK();
|
||||
ShadowsPass::Instance()->RenderShadow(renderContext, light, shadowMaskView);
|
||||
|
||||
// Bind output
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
|
||||
// Set shadow mask
|
||||
context->BindSR(5, shadowMaskView);
|
||||
}
|
||||
|
||||
// Pack light properties buffer
|
||||
light.SetupLightData(&perLight.Light, view, renderShadow);
|
||||
Matrix::Transpose(wvp, perLight.WVP);
|
||||
if (useIES)
|
||||
{
|
||||
context->BindSR(6, light.IESTexture);
|
||||
}
|
||||
|
||||
// Calculate lighting
|
||||
context->UpdateCB(cb0, &perLight);
|
||||
context->BindCB(0, cb0);
|
||||
context->BindCB(1, cb1);
|
||||
int32 permutationIndex = (disableSpecular ? 1 : 0) + (useIES ? 2 : 0);
|
||||
context->SetState((isViewInside ? _psLightPointInverted : _psLightPointNormal).Get(permutationIndex));
|
||||
_sphereModel->Render(context);
|
||||
}
|
||||
|
||||
context->UnBindCB(0);
|
||||
|
||||
// Render all spot lights
|
||||
for (int32 lightIndex = 0; lightIndex < mainCache->SpotLights.Count(); lightIndex++)
|
||||
{
|
||||
PROFILE_GPU_CPU("Spot Light");
|
||||
|
||||
// Cache data
|
||||
auto& light = mainCache->SpotLights[lightIndex];
|
||||
float lightRadius = light.Radius;
|
||||
Vector3 lightPosition = light.Position;
|
||||
const bool renderShadow = useShadows && CanRenderShadow(view, light) && ShadowsPass::Instance()->CanRenderShadow(renderContext, light);
|
||||
bool useIES = light.IESTexture != nullptr;
|
||||
|
||||
// Get distance from view center to light center less radius (check if view is inside a sphere)
|
||||
float distance = ViewToCenterLessRadius(view, lightPosition, lightRadius * sphereModelScale);
|
||||
bool isViewInside = distance < 0;
|
||||
|
||||
// Calculate world view projection matrix for the light sphere
|
||||
Matrix world, wvp, matrix;
|
||||
Matrix::Scaling(lightRadius * sphereModelScale, wvp);
|
||||
Matrix::Translation(lightPosition, matrix);
|
||||
Matrix::Multiply(wvp, matrix, world);
|
||||
Matrix::Multiply(world, view.ViewProjection(), wvp);
|
||||
|
||||
// Check if render shadow
|
||||
if (renderShadow)
|
||||
{
|
||||
GET_SHADOW_MASK();
|
||||
ShadowsPass::Instance()->RenderShadow(renderContext, light, shadowMaskView);
|
||||
|
||||
// Bind output
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
|
||||
// Set shadow mask
|
||||
context->BindSR(5, shadowMaskView);
|
||||
}
|
||||
|
||||
// Pack light properties buffer
|
||||
light.SetupLightData(&perLight.Light, view, renderShadow);
|
||||
Matrix::Transpose(wvp, perLight.WVP);
|
||||
if (useIES)
|
||||
{
|
||||
context->BindSR(6, light.IESTexture);
|
||||
}
|
||||
|
||||
// Calculate lighting
|
||||
context->UpdateCB(cb0, &perLight);
|
||||
context->BindCB(0, cb0);
|
||||
context->BindCB(1, cb1);
|
||||
int32 permutationIndex = (disableSpecular ? 1 : 0) + (useIES ? 2 : 0);
|
||||
context->SetState((isViewInside ? _psLightSpotInverted : _psLightSpotNormal).Get(permutationIndex));
|
||||
_sphereModel->Render(context);
|
||||
}
|
||||
|
||||
context->UnBindCB(0);
|
||||
|
||||
// Render all directional lights
|
||||
for (int32 lightIndex = 0; lightIndex < mainCache->DirectionalLights.Count(); lightIndex++)
|
||||
{
|
||||
PROFILE_GPU_CPU("Directional Light");
|
||||
|
||||
// Cache data
|
||||
auto& light = mainCache->DirectionalLights[lightIndex];
|
||||
const bool renderShadow = useShadows && CanRenderShadow(view, light) && ShadowsPass::Instance()->CanRenderShadow(renderContext, light);
|
||||
|
||||
// Check if render shadow
|
||||
if (renderShadow)
|
||||
{
|
||||
GET_SHADOW_MASK();
|
||||
ShadowsPass::Instance()->RenderShadow(renderContext, light, lightIndex, shadowMaskView);
|
||||
|
||||
// Bind output
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
|
||||
// Set shadow mask
|
||||
context->BindSR(5, shadowMaskView);
|
||||
}
|
||||
|
||||
// Pack light properties buffer
|
||||
light.SetupLightData(&perLight.Light, view, renderShadow);
|
||||
|
||||
// Calculate lighting
|
||||
context->UpdateCB(cb0, &perLight);
|
||||
context->BindCB(0, cb0);
|
||||
context->BindCB(1, cb1);
|
||||
context->SetState(_psLightDir.Get(disableSpecular));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
context->UnBindCB(0);
|
||||
|
||||
// Render all sky lights
|
||||
for (int32 lightIndex = 0; lightIndex < mainCache->SkyLights.Count(); lightIndex++)
|
||||
{
|
||||
PROFILE_GPU_CPU("Sky Light");
|
||||
|
||||
// Cache data
|
||||
auto& light = mainCache->SkyLights[lightIndex];
|
||||
float lightRadius = light.Radius;
|
||||
Vector3 lightPosition = light.Position;
|
||||
|
||||
// Get distance from view center to light center less radius (check if view is inside a sphere)
|
||||
float distance = ViewToCenterLessRadius(view, lightPosition, lightRadius * sphereModelScale);
|
||||
bool isViewInside = distance < 0;
|
||||
|
||||
// Calculate world view projection matrix for the light sphere
|
||||
Matrix world, wvp, matrix;
|
||||
Matrix::Scaling(lightRadius * sphereModelScale, wvp);
|
||||
Matrix::Translation(lightPosition, matrix);
|
||||
Matrix::Multiply(wvp, matrix, world);
|
||||
Matrix::Multiply(world, view.ViewProjection(), wvp);
|
||||
|
||||
// Pack light properties buffer
|
||||
light.SetupLightData(&perLight.Light, view, false);
|
||||
Matrix::Transpose(wvp, perLight.WVP);
|
||||
|
||||
// Bind source image
|
||||
context->BindSR(7, light.Image ? light.Image->GetTexture() : nullptr);
|
||||
|
||||
// Calculate lighting
|
||||
context->UpdateCB(cb0, &perLight);
|
||||
context->BindCB(0, cb0);
|
||||
context->BindCB(1, cb1);
|
||||
context->SetState(isViewInside ? _psLightSkyInverted : _psLightSkyNormal);
|
||||
_sphereModel->Render(context);
|
||||
}
|
||||
|
||||
RenderTargetPool::Release(shadowMask);
|
||||
|
||||
// Restore state
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
context->ResetCB();
|
||||
}
|
||||
75
Source/Engine/Renderer/LightPass.h
Normal file
75
Source/Engine/Renderer/LightPass.h
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Graphics/RenderView.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
|
||||
/// <summary>
|
||||
/// Lighting rendering service. Handles dynamic lights diffuse and specular color calculations.
|
||||
/// </summary>
|
||||
class LightPass : public RendererPass<LightPass>
|
||||
{
|
||||
private:
|
||||
|
||||
// Lights
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineStatePermutationsPs<2> _psLightDir;
|
||||
GPUPipelineStatePermutationsPs<4> _psLightPointNormal;
|
||||
GPUPipelineStatePermutationsPs<4> _psLightPointInverted;
|
||||
GPUPipelineStatePermutationsPs<4> _psLightSpotNormal;
|
||||
GPUPipelineStatePermutationsPs<4> _psLightSpotInverted;
|
||||
GPUPipelineState* _psLightSkyNormal;
|
||||
GPUPipelineState* _psLightSkyInverted;
|
||||
|
||||
// Msc
|
||||
AssetReference<Model> _sphereModel;
|
||||
PixelFormat _shadowMaskFormat;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
LightPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Performs the lighting rendering for the input task.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="lightBuffer">The light accumulation buffer (input and output).</param>
|
||||
void RenderLight(RenderContext& renderContext, GPUTextureView* lightBuffer);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psLightDir.Release();
|
||||
_psLightPointNormal.Release();
|
||||
_psLightPointInverted.Release();
|
||||
_psLightSpotNormal.Release();
|
||||
_psLightSpotInverted.Release();
|
||||
_psLightSkyNormal->ReleaseGPU();
|
||||
_psLightSkyInverted->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
163
Source/Engine/Renderer/Lightmaps.h
Normal file
163
Source/Engine/Renderer/Lightmaps.h
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/Guid.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Serialization/ISerializable.h"
|
||||
|
||||
#if USE_EDITOR
|
||||
// Additional options used in editor for lightmaps baking
|
||||
extern bool IsRunningRadiancePass;
|
||||
extern bool IsBakingLightmaps;
|
||||
extern bool EnableLightmapsUsage;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Single lightmap info data
|
||||
/// </summary>
|
||||
struct SavedLightmapInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Lightmap 0 texture ID
|
||||
/// </summary>
|
||||
Guid Lightmap0;
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap 1 texture ID
|
||||
/// </summary>
|
||||
Guid Lightmap1;
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap 2 texture ID
|
||||
/// </summary>
|
||||
Guid Lightmap2;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes object reference to the lightmap
|
||||
/// </summary>
|
||||
struct LightmapEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of the lightmap
|
||||
/// </summary>
|
||||
int32 TextureIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap UVs area that entry occupies
|
||||
/// </summary>
|
||||
Rectangle UVsArea;
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
LightmapEntry()
|
||||
: TextureIndex(INVALID_INDEX)
|
||||
, UVsArea(Rectangle::Empty)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Describes lightmap generation options
|
||||
/// </summary>
|
||||
API_STRUCT() struct LightmapSettings : ISerializable
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(LightmapSettings);
|
||||
|
||||
/// <summary>
|
||||
/// Lightmap atlas sizes (in pixels).
|
||||
/// </summary>
|
||||
API_ENUM() enum class AtlasSizes
|
||||
{
|
||||
/// <summary>
|
||||
/// 64x64
|
||||
/// </summary>
|
||||
_64 = 64,
|
||||
|
||||
/// <summary>
|
||||
/// 128x128
|
||||
/// </summary>
|
||||
_128 = 128,
|
||||
|
||||
/// <summary>
|
||||
/// 256x256
|
||||
/// </summary>
|
||||
_256 = 256,
|
||||
|
||||
/// <summary>
|
||||
/// 512x512
|
||||
/// </summary>
|
||||
_512 = 512,
|
||||
|
||||
/// <summary>
|
||||
/// 1024x1024
|
||||
/// </summary>
|
||||
_1024 = 1024,
|
||||
|
||||
/// <summary>
|
||||
/// 2048x2048
|
||||
/// </summary>
|
||||
_2048 = 2048,
|
||||
|
||||
/// <summary>
|
||||
/// 4096x4096
|
||||
/// </summary>
|
||||
_4096 = 4096,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Controls how much all lights will contribute indirect lighting.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(0), Limit(0, 100.0f, 0.1f)")
|
||||
float IndirectLightingIntensity = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Global scale for objects in lightmap to increase quality
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(10), Limit(0, 100.0f, 0.1f)")
|
||||
float GlobalObjectsScale = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of pixels space between charts in lightmap atlas
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), Limit(0, 16, 0.1f)")
|
||||
int32 ChartsPadding = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Single lightmap atlas size (width and height in pixels)
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(30)")
|
||||
AtlasSizes AtlasSize = AtlasSizes::_1024;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of indirect light GI bounce passes
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(40), Limit(1, 16, 0.1f)")
|
||||
int32 BounceCount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable compressing lightmap textures (3 textures per lightmap with RGBA data in HDR)
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(45)")
|
||||
bool CompressLightmaps = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable rendering static light for geometry with missing or empty material slots
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(50)")
|
||||
bool UseGeometryWithNoMaterials = true;
|
||||
|
||||
/// <summary>
|
||||
/// GI quality (range [0;100])
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(60), Limit(0, 100, 0.1f)")
|
||||
int32 Quality = 10;
|
||||
|
||||
public:
|
||||
|
||||
// [ISerializable]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
};
|
||||
462
Source/Engine/Renderer/MotionBlurPass.cpp
Normal file
462
Source/Engine/Renderer/MotionBlurPass.cpp
Normal file
@@ -0,0 +1,462 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "MotionBlurPass.h"
|
||||
#include "GBufferPass.h"
|
||||
#include "Renderer.h"
|
||||
#include "Engine/Core/Config/GraphicsSettings.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
|
||||
MotionBlurPass::MotionBlurPass()
|
||||
: _motionVectorsFormat(PixelFormat::Unknown)
|
||||
, _velocityFormat(PixelFormat::Unknown)
|
||||
, _psCameraMotionVectors(nullptr)
|
||||
, _psMotionVectorsDebug(nullptr)
|
||||
, _psMotionVectorsDebugArrow(nullptr)
|
||||
, _psVelocitySetup(nullptr)
|
||||
, _psTileMax1(nullptr)
|
||||
, _psTileMax2(nullptr)
|
||||
, _psTileMax4(nullptr)
|
||||
, _psTileMaxV(nullptr)
|
||||
, _psNeighborMax(nullptr)
|
||||
, _psReconstruction(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String MotionBlurPass::ToString() const
|
||||
{
|
||||
return TEXT("MotionBlurPass");
|
||||
}
|
||||
|
||||
bool MotionBlurPass::Init()
|
||||
{
|
||||
// Create pipeline state
|
||||
_psCameraMotionVectors = GPUDevice::Instance->CreatePipelineState();
|
||||
_psMotionVectorsDebug = GPUDevice::Instance->CreatePipelineState();
|
||||
_psMotionVectorsDebugArrow = GPUDevice::Instance->CreatePipelineState();
|
||||
_psVelocitySetup = GPUDevice::Instance->CreatePipelineState();
|
||||
_psTileMax1 = GPUDevice::Instance->CreatePipelineState();
|
||||
_psTileMax2 = GPUDevice::Instance->CreatePipelineState();
|
||||
_psTileMax4 = GPUDevice::Instance->CreatePipelineState();
|
||||
_psTileMaxV = GPUDevice::Instance->CreatePipelineState();
|
||||
_psNeighborMax = GPUDevice::Instance->CreatePipelineState();
|
||||
_psReconstruction = GPUDevice::Instance->CreatePipelineState();
|
||||
|
||||
// Load shader
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/MotionBlur"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<MotionBlurPass, &MotionBlurPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
// Prepare formats for the buffers
|
||||
auto format = MOTION_VECTORS_PIXEL_FORMAT;
|
||||
if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(format).Support, (FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D)))
|
||||
{
|
||||
if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->GetFormatFeatures(PixelFormat::R32G32_Float).Support, (FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D)))
|
||||
format = PixelFormat::R32G32_Float;
|
||||
else
|
||||
format = PixelFormat::R32G32B32A32_Float;
|
||||
}
|
||||
_motionVectorsFormat = format;
|
||||
format = PixelFormat::R10G10B10A2_UNorm;
|
||||
if (FORMAT_FEATURES_ARE_NOT_SUPPORTED(GPUDevice::Instance->FeaturesPerFormat[(int32)format].Support, (FormatSupport::RenderTarget | FormatSupport::ShaderSample | FormatSupport::Texture2D)))
|
||||
{
|
||||
format = PixelFormat::R32G32B32A32_Float;
|
||||
}
|
||||
_velocityFormat = format;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MotionBlurPass::setupResources()
|
||||
{
|
||||
// Check 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 state
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (!_psCameraMotionVectors->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_CameraMotionVectors");
|
||||
if (_psCameraMotionVectors->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psVelocitySetup->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_VelocitySetup");
|
||||
if (_psVelocitySetup->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psTileMax1->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_TileMax1");
|
||||
if (_psTileMax1->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psTileMax2->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_TileMax2");
|
||||
if (_psTileMax2->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psTileMax4->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_TileMax4");
|
||||
if (_psTileMax4->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psTileMaxV->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_TileMaxV");
|
||||
if (_psTileMaxV->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psNeighborMax->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_NeighborMax");
|
||||
if (_psNeighborMax->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psReconstruction->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Reconstruction");
|
||||
if (_psReconstruction->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psMotionVectorsDebug->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_MotionVectorsDebug");
|
||||
if (_psMotionVectorsDebug->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psMotionVectorsDebugArrow->IsValid())
|
||||
{
|
||||
psDesc.PrimitiveTopologyType = PrimitiveTopologyType::Line;
|
||||
psDesc.VS = shader->GetVS("VS_DebugArrow");
|
||||
psDesc.PS = shader->GetPS("PS_DebugArrow");
|
||||
if (_psMotionVectorsDebugArrow->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MotionBlurPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline state
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCameraMotionVectors);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psMotionVectorsDebug);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psMotionVectorsDebugArrow);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psVelocitySetup);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psTileMax1);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psTileMax2);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psTileMax4);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psTileMaxV);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psNeighborMax);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psReconstruction);
|
||||
|
||||
// Release asset
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
void MotionBlurPass::RenderMotionVectors(RenderContext& renderContext)
|
||||
{
|
||||
// Prepare
|
||||
auto motionVectors = renderContext.Buffers->MotionVectors;
|
||||
ASSERT(motionVectors);
|
||||
MotionBlurSettings& settings = renderContext.List->Settings.MotionBlur;
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
const int32 screenWidth = renderContext.Buffers->GetWidth();
|
||||
const int32 screenHeight = renderContext.Buffers->GetHeight();
|
||||
const int32 motionVectorsWidth = screenWidth / static_cast<int32>(settings.MotionVectorsResolution);
|
||||
const int32 motionVectorsHeight = screenHeight / static_cast<int32>(settings.MotionVectorsResolution);
|
||||
|
||||
// Ensure to have valid data
|
||||
if (!Renderer::NeedMotionVectors(renderContext) || checkIfSkipPass())
|
||||
{
|
||||
// Skip pass (just clear motion vectors if texture is allocated)
|
||||
if (motionVectors->IsAllocated())
|
||||
{
|
||||
if (motionVectors->Width() == motionVectorsWidth && motionVectors->Height() == motionVectorsHeight)
|
||||
context->Clear(motionVectors->View(), Color::Black);
|
||||
else
|
||||
motionVectors->ReleaseGPU();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_GPU_CPU("Motion Vectors");
|
||||
|
||||
// Ensure to have valid motion vectors texture
|
||||
if (!motionVectors->IsAllocated() || motionVectors->Width() != motionVectorsWidth || motionVectors->Height() != motionVectorsHeight)
|
||||
{
|
||||
if (motionVectors->Init(GPUTextureDescription::New2D(motionVectorsWidth, motionVectorsHeight, _motionVectorsFormat, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)))
|
||||
{
|
||||
LOG(Warning, "Failed to create motion vectors render target.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare
|
||||
GPUTexture* depthBuffer;
|
||||
if (settings.MotionVectorsResolution != ResolutionMode::Full)
|
||||
{
|
||||
depthBuffer = renderContext.Buffers->RequestHalfResDepth(context);
|
||||
context->SetViewportAndScissors((float)motionVectorsWidth, (float)motionVectorsHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
depthBuffer = renderContext.Buffers->DepthBuffer;
|
||||
}
|
||||
|
||||
// Bind input
|
||||
Data data;
|
||||
GBufferPass::SetInputs(renderContext.View, data.GBuffer);
|
||||
Matrix::Transpose(renderContext.View.ViewProjection(), data.CurrentVP);
|
||||
Matrix::Transpose(renderContext.View.PrevViewProjection, data.PreviousVP);
|
||||
data.TemporalAAJitter = renderContext.View.TemporalAAJitter;
|
||||
auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
context->BindSR(0, depthBuffer);
|
||||
|
||||
// Render camera motion vectors (background)
|
||||
if (!data.TemporalAAJitter.IsZero() || data.CurrentVP != data.PreviousVP)
|
||||
{
|
||||
PROFILE_GPU_CPU("Camera Motion Vectors");
|
||||
context->SetRenderTarget(motionVectors->View());
|
||||
context->SetState(_psCameraMotionVectors);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple clear if camera is not moving
|
||||
context->Clear(motionVectors->View(), Color::Black);
|
||||
}
|
||||
|
||||
// Render per-object motion vectors (use depth buffer to discard dynamic objects pixels covered by the static geometry)
|
||||
context->ResetSR();
|
||||
context->SetRenderTarget(depthBuffer->View(), motionVectors->View());
|
||||
renderContext.View.Pass = DrawPass::MotionVectors;
|
||||
// TODO: maybe update material PerFrame data because render viewport can be other size than ScreenSize from render buffers
|
||||
renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::MotionVectors);
|
||||
renderContext.List->ExecuteDrawCalls(renderContext, DrawCallsListType::MotionVectors);
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
if (settings.MotionVectorsResolution != ResolutionMode::Full)
|
||||
{
|
||||
const auto viewport = renderContext.Task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
}
|
||||
}
|
||||
|
||||
void MotionBlurPass::RenderDebug(RenderContext& renderContext, GPUTextureView* frame)
|
||||
{
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
const auto motionVectors = renderContext.Buffers->MotionVectors;
|
||||
if (!motionVectors->IsAllocated() || setupResources())
|
||||
{
|
||||
context->Draw(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_GPU_CPU("Motion Vectors Debug");
|
||||
|
||||
// Bind input
|
||||
Data data;
|
||||
GBufferPass::SetInputs(renderContext.View, data.GBuffer);
|
||||
const float rows = 16.0f;
|
||||
const float cols = rows * renderContext.Buffers->GetWidth() / renderContext.Buffers->GetHeight();
|
||||
data.DebugBlend = 0.7f;
|
||||
data.DebugAmplitude = 2.0f;
|
||||
data.DebugRowCount = static_cast<int32>(rows);
|
||||
data.DebugColumnCount = static_cast<int32>(cols);
|
||||
auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
context->BindSR(0, frame);
|
||||
context->BindSR(1, renderContext.Buffers->MotionVectors);
|
||||
|
||||
// Draw motion gradient
|
||||
context->SetState(_psMotionVectorsDebug);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Draw arrows
|
||||
context->SetState(_psMotionVectorsDebugArrow);
|
||||
context->Draw(0, static_cast<uint32>(cols * rows * 6));
|
||||
|
||||
// Cleanup
|
||||
context->ResetSR();
|
||||
}
|
||||
|
||||
void MotionBlurPass::Render(RenderContext& renderContext, GPUTexture*& input, GPUTexture*& output)
|
||||
{
|
||||
const bool isCameraCut = renderContext.Task->IsCameraCut;
|
||||
const auto motionVectors = renderContext.Buffers->MotionVectors;
|
||||
ASSERT(motionVectors);
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
MotionBlurSettings& settings = renderContext.List->Settings.MotionBlur;
|
||||
const int32 screenWidth = renderContext.Buffers->GetWidth();
|
||||
const int32 screenHeight = renderContext.Buffers->GetHeight();
|
||||
const int32 motionVectorsWidth = screenWidth / static_cast<int32>(settings.MotionVectorsResolution);
|
||||
const int32 motionVectorsHeight = screenHeight / static_cast<int32>(settings.MotionVectorsResolution);
|
||||
|
||||
// Ensure to have valid data
|
||||
if ((renderContext.View.Flags & ViewFlags::MotionBlur) == 0 ||
|
||||
!_hasValidResources ||
|
||||
isCameraCut ||
|
||||
screenWidth < 16 ||
|
||||
screenHeight < 16 ||
|
||||
!settings.Enabled ||
|
||||
settings.Scale <= 0.0f)
|
||||
{
|
||||
// Skip pass
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to have valid motion vectors created and rendered before
|
||||
ASSERT(motionVectors->IsAllocated());
|
||||
|
||||
PROFILE_GPU_CPU("Motion Blur");
|
||||
|
||||
// Calculate the maximum blur radius in pixels
|
||||
const float maxBlurRadius = 5.0f;
|
||||
const int32 maxBlurPixels = static_cast<int32>(maxBlurRadius * motionVectorsHeight / 100.0f);
|
||||
|
||||
// Calculate the TileMax size (it should be a multiple of 8 and larger than maxBlur)
|
||||
const int32 tileSize = ((maxBlurPixels - 1) / 8 + 1) * 8;
|
||||
|
||||
// Bind input
|
||||
Data data;
|
||||
GBufferPass::SetInputs(renderContext.View, data.GBuffer);
|
||||
Matrix::Transpose(renderContext.View.ViewProjection(), data.CurrentVP);
|
||||
Matrix::Transpose(renderContext.View.PrevViewProjection, data.PreviousVP);
|
||||
data.TemporalAAJitter = renderContext.View.TemporalAAJitter;
|
||||
data.VelocityScale = settings.Scale;
|
||||
data.MaxBlurRadius = static_cast<float>(maxBlurPixels);
|
||||
data.RcpMaxBlurRadius = 1.0f / maxBlurPixels;
|
||||
data.TileMaxOffs = Vector2::One * (tileSize / 8.0f - 1.0f) * -0.5f;
|
||||
data.TileMaxLoop = static_cast<int32>(tileSize / 8.0f);
|
||||
data.LoopCount = Math::Clamp(settings.SampleCount / 2.0f, 1.0f, 64.0f);
|
||||
const float invWidth = 1.0f / motionVectorsWidth;
|
||||
const float invHeight = 1.0f / motionVectorsHeight;
|
||||
data.TexelSize1 = Vector2(invWidth, invHeight);
|
||||
data.TexelSize2 = Vector2(invWidth * 2.0f, invHeight * 2.0f);
|
||||
data.TexelSize4 = Vector2(invWidth * 4.0f, invHeight * 4.0f);
|
||||
data.TexelSizeV = Vector2(invWidth * 8.0f, invHeight * 8.0f);
|
||||
data.TexelSizeNM = Vector2(invWidth * tileSize, invHeight * tileSize);
|
||||
data.MotionVectorsTexelSize = Vector2(1.0f / motionVectorsWidth, invHeight * tileSize);
|
||||
auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
context->BindSR(0, renderContext.Buffers->DepthBuffer);
|
||||
auto rtDesc = GPUTextureDescription::New2D(motionVectorsWidth, motionVectorsHeight, _velocityFormat);
|
||||
|
||||
// Pass 1 - Velocity/depth packing
|
||||
auto vBuffer = RenderTargetPool::Get(rtDesc);
|
||||
context->SetRenderTarget(*vBuffer);
|
||||
context->SetViewportAndScissors((float)rtDesc.Width, (float)rtDesc.Height);
|
||||
context->BindSR(0, motionVectors->View());
|
||||
context->BindSR(1, renderContext.Buffers->DepthBuffer->View());
|
||||
context->SetState(_psVelocitySetup);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->UnBindSR(1);
|
||||
|
||||
// Pass 2 - First TileMax filter (1/2 downsize)
|
||||
rtDesc.Format = _motionVectorsFormat;
|
||||
rtDesc.Width = motionVectorsWidth / 2;
|
||||
rtDesc.Height = motionVectorsHeight / 2;
|
||||
auto tile2 = RenderTargetPool::Get(rtDesc);
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(tile2->View());
|
||||
context->SetViewportAndScissors((float)rtDesc.Width, (float)rtDesc.Height);
|
||||
context->BindSR(0, vBuffer->View());
|
||||
context->SetState(_psTileMax1);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Pass 3 - Second TileMax filter (1/4 downsize)
|
||||
rtDesc.Width = motionVectorsWidth / 4;
|
||||
rtDesc.Height = motionVectorsHeight / 4;
|
||||
auto tile4 = RenderTargetPool::Get(rtDesc);
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(tile4->View());
|
||||
context->SetViewportAndScissors((float)rtDesc.Width, (float)rtDesc.Height);
|
||||
context->BindSR(0, tile2->View());
|
||||
context->SetState(_psTileMax2);
|
||||
context->DrawFullscreenTriangle();
|
||||
RenderTargetPool::Release(tile2);
|
||||
|
||||
// Pass 4 - Third TileMax filter (1/8 downsize)
|
||||
rtDesc.Width = motionVectorsWidth / 8;
|
||||
rtDesc.Height = motionVectorsHeight / 8;
|
||||
auto tile8 = RenderTargetPool::Get(rtDesc);
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(tile8->View());
|
||||
context->SetViewportAndScissors((float)rtDesc.Width, (float)rtDesc.Height);
|
||||
context->BindSR(0, tile4->View());
|
||||
context->SetState(_psTileMax4);
|
||||
context->DrawFullscreenTriangle();
|
||||
RenderTargetPool::Release(tile4);
|
||||
|
||||
// Pass 5 - Fourth TileMax filter (reduce to tileSize)
|
||||
rtDesc.Width = motionVectorsWidth / tileSize;
|
||||
rtDesc.Height = motionVectorsHeight / tileSize;
|
||||
auto tile = RenderTargetPool::Get(rtDesc);
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(tile->View());
|
||||
context->SetViewportAndScissors((float)rtDesc.Width, (float)rtDesc.Height);
|
||||
context->BindSR(0, tile8->View());
|
||||
context->SetState(_psTileMaxV);
|
||||
context->DrawFullscreenTriangle();
|
||||
RenderTargetPool::Release(tile8);
|
||||
|
||||
// Pass 6 - NeighborMax filter
|
||||
rtDesc.Width = motionVectorsWidth / tileSize;
|
||||
rtDesc.Height = motionVectorsHeight / tileSize;
|
||||
auto neighborMax = RenderTargetPool::Get(rtDesc);
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(neighborMax->View());
|
||||
context->SetViewportAndScissors((float)rtDesc.Width, (float)rtDesc.Height);
|
||||
context->BindSR(0, tile->View());
|
||||
context->SetState(_psNeighborMax);
|
||||
context->DrawFullscreenTriangle();
|
||||
RenderTargetPool::Release(tile);
|
||||
|
||||
// Pass 7 - Reconstruction pass
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(*output);
|
||||
context->SetViewportAndScissors((float)screenWidth, (float)screenHeight);
|
||||
context->BindSR(0, input->View());
|
||||
context->BindSR(1, vBuffer->View());
|
||||
context->BindSR(2, neighborMax->View());
|
||||
context->SetState(_psReconstruction);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Cleanup
|
||||
context->ResetSR();
|
||||
context->ResetRenderTarget();
|
||||
RenderTargetPool::Release(vBuffer);
|
||||
RenderTargetPool::Release(neighborMax);
|
||||
Swap(output, input);
|
||||
}
|
||||
111
Source/Engine/Renderer/MotionBlurPass.h
Normal file
111
Source/Engine/Renderer/MotionBlurPass.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
|
||||
/// <summary>
|
||||
/// Anti aliasing rendering service
|
||||
/// </summary>
|
||||
class MotionBlurPass : public RendererPass<MotionBlurPass>
|
||||
{
|
||||
private:
|
||||
PACK_STRUCT(struct Data {
|
||||
GBufferData GBuffer;
|
||||
Matrix CurrentVP;
|
||||
Matrix PreviousVP;
|
||||
Vector4 TemporalAAJitter;
|
||||
Vector2 TileMaxOffs;
|
||||
float VelocityScale;
|
||||
int32 TileMaxLoop;
|
||||
float MaxBlurRadius;
|
||||
float RcpMaxBlurRadius;
|
||||
Vector2 TexelSize1;
|
||||
Vector2 TexelSize2;
|
||||
Vector2 TexelSize4;
|
||||
Vector2 TexelSizeV;
|
||||
Vector2 TexelSizeNM;
|
||||
float LoopCount;
|
||||
float Dummy0;
|
||||
Vector2 MotionVectorsTexelSize;
|
||||
float DebugBlend;
|
||||
float DebugAmplitude;
|
||||
int32 DebugColumnCount;
|
||||
int32 DebugRowCount;
|
||||
});
|
||||
|
||||
PixelFormat _motionVectorsFormat;
|
||||
PixelFormat _velocityFormat;
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psCameraMotionVectors;
|
||||
GPUPipelineState* _psMotionVectorsDebug;
|
||||
GPUPipelineState* _psMotionVectorsDebugArrow;
|
||||
GPUPipelineState* _psVelocitySetup;
|
||||
GPUPipelineState* _psTileMax1;
|
||||
GPUPipelineState* _psTileMax2;
|
||||
GPUPipelineState* _psTileMax4;
|
||||
GPUPipelineState* _psTileMaxV;
|
||||
GPUPipelineState* _psNeighborMax;
|
||||
GPUPipelineState* _psReconstruction;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
MotionBlurPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Renders the motion vectors texture for the current task. Skips if motion blur is disabled or no need to render motion vectors.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
void RenderMotionVectors(RenderContext& renderContext);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the motion vectors debug view.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="frame">The source frame.</param>
|
||||
void RenderDebug(RenderContext& renderContext, GPUTextureView* frame);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the motion blur. Swaps the input with output if rendering is performed. Does nothing if rendering is not performed.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="input">The input frame.</param>
|
||||
/// <param name="output">The output frame.</param>
|
||||
void Render(RenderContext& renderContext, GPUTexture*& input, GPUTexture*& output);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psCameraMotionVectors->ReleaseGPU();
|
||||
_psMotionVectorsDebug->ReleaseGPU();
|
||||
_psMotionVectorsDebugArrow->ReleaseGPU();
|
||||
_psVelocitySetup->ReleaseGPU();
|
||||
_psTileMax1->ReleaseGPU();
|
||||
_psTileMax2->ReleaseGPU();
|
||||
_psTileMax4->ReleaseGPU();
|
||||
_psTileMaxV->ReleaseGPU();
|
||||
_psNeighborMax->ReleaseGPU();
|
||||
_psReconstruction->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
496
Source/Engine/Renderer/PostProcessingPass.cpp
Normal file
496
Source/Engine/Renderer/PostProcessingPass.cpp
Normal file
@@ -0,0 +1,496 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "PostProcessingPass.h"
|
||||
#include "RenderList.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/PostProcessBase.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
|
||||
PostProcessingPass::PostProcessingPass()
|
||||
: _shader(nullptr)
|
||||
, _psThreshold(nullptr)
|
||||
, _psScale(nullptr)
|
||||
, _psBlurH(nullptr)
|
||||
, _psBlurV(nullptr)
|
||||
, _psGenGhosts(nullptr)
|
||||
, _defaultLensColor(nullptr)
|
||||
, _defaultLensStar(nullptr)
|
||||
, _defaultLensDirt(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String PostProcessingPass::ToString() const
|
||||
{
|
||||
return TEXT("PostProcessingPass");
|
||||
}
|
||||
|
||||
bool PostProcessingPass::Init()
|
||||
{
|
||||
// Create pipeline states
|
||||
_psThreshold = GPUDevice::Instance->CreatePipelineState();
|
||||
_psScale = GPUDevice::Instance->CreatePipelineState();
|
||||
_psBlurH = GPUDevice::Instance->CreatePipelineState();
|
||||
_psBlurV = GPUDevice::Instance->CreatePipelineState();
|
||||
_psGenGhosts = GPUDevice::Instance->CreatePipelineState();
|
||||
_psComposite.CreatePipelineStates();
|
||||
|
||||
// Load shader
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/PostProcessing"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<PostProcessingPass, &PostProcessingPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PostProcessingPass::setupResources()
|
||||
{
|
||||
// Wait for shader
|
||||
if (!_shader->IsLoaded())
|
||||
return true;
|
||||
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;
|
||||
}
|
||||
if (shader->GetCB(1)->GetSize() != sizeof(GaussianBlurData))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 1, GaussianBlurData);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline stages
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (!_psThreshold->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Threshold");
|
||||
if (_psThreshold->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psScale->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Scale");
|
||||
if (_psScale->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psBlurH->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_GaussainBlurH");
|
||||
if (_psBlurH->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psBlurV->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_GaussainBlurV");
|
||||
if (_psBlurV->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psGenGhosts->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_Ghosts");
|
||||
if (_psGenGhosts->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
if (!_psComposite.IsValid())
|
||||
{
|
||||
if (_psComposite.Create(psDesc, shader, "PS_Composite"))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
GPUTexture* PostProcessingPass::getCustomOrDefault(Texture* customTexture, AssetReference<Texture>& defaultTexture, const Char* defaultName)
|
||||
{
|
||||
// Check if use custom texture
|
||||
if (customTexture)
|
||||
return customTexture->GetTexture();
|
||||
|
||||
// Check if need to load default texture
|
||||
if (defaultTexture == nullptr)
|
||||
{
|
||||
// Load default
|
||||
defaultTexture = Content::LoadAsyncInternal<Texture>(defaultName);
|
||||
}
|
||||
|
||||
// Use default texture or nothing
|
||||
return defaultTexture ? defaultTexture->GetTexture() : nullptr;
|
||||
}
|
||||
|
||||
void PostProcessingPass::GB_ComputeKernel(float sigma, float width, float height)
|
||||
{
|
||||
float total = 0.0f;
|
||||
float twoSigmaSquare = 2.0f * sigma * sigma;
|
||||
float sigmaRoot = Math::Sqrt(twoSigmaSquare * PI);
|
||||
float xOffset = 1.0f / width;
|
||||
float yOffset = 1.0f / height;
|
||||
|
||||
// Calculate weights and offsets
|
||||
for (int32 i = -GB_RADIUS; i <= GB_RADIUS; i++)
|
||||
{
|
||||
// Calculate pixel distance and index
|
||||
const float distance = static_cast<float>(i * i);
|
||||
const int32 index = i + GB_RADIUS;
|
||||
|
||||
// Calculate pixel weight
|
||||
const float weight = Math::Exp(-distance / twoSigmaSquare) / sigmaRoot;
|
||||
|
||||
// Calculate total weights sum
|
||||
total += weight;
|
||||
|
||||
GaussianBlurCacheH[index] = Vector4(weight, i * xOffset, 0, 0);
|
||||
GaussianBlurCacheV[index] = Vector4(weight, i * yOffset, 0, 0);
|
||||
}
|
||||
|
||||
// Normalize weights
|
||||
for (int32 i = 0; i < GB_KERNEL_SIZE; i++)
|
||||
{
|
||||
GaussianBlurCacheH[i].X /= total;
|
||||
GaussianBlurCacheV[i].X /= total;
|
||||
}
|
||||
|
||||
// Assign size
|
||||
_gbData.Size = Vector2(width, height);
|
||||
}
|
||||
|
||||
void PostProcessingPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_psThreshold);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psScale);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psBlurH);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psBlurV);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psGenGhosts);
|
||||
_psComposite.Delete();
|
||||
|
||||
// Release assets
|
||||
_shader.Unlink();
|
||||
_defaultLensColor.Unlink();
|
||||
_defaultLensDirt.Unlink();
|
||||
_defaultLensStar.Unlink();
|
||||
}
|
||||
|
||||
void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output, GPUTexture* colorGradingLUT)
|
||||
{
|
||||
ASSERT(output->Format() == PixelFormat::R11G11B10_Float);
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
|
||||
PROFILE_GPU_CPU("Post Processing");
|
||||
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Ensure to have valid data
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Resources are missing. Do not perform rendering. Just copy raw frame
|
||||
context->SetRenderTarget(*output);
|
||||
context->Draw(input);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache data
|
||||
PostProcessSettings& settings = renderContext.List->Settings;
|
||||
bool useBloom = (view.Flags & ViewFlags::Bloom) != 0 && settings.Bloom.Enabled && settings.Bloom.Intensity > 0.0f;
|
||||
bool useToneMapping = (view.Flags & ViewFlags::ToneMapping) != 0;
|
||||
bool useCameraArtifacts = (view.Flags & ViewFlags::CameraArtifacts) != 0;
|
||||
bool useLensFlares = (view.Flags & ViewFlags::LensFlares) != 0 && settings.LensFlares.Intensity > 0.0f && useBloom;
|
||||
|
||||
// Ensure to have valid data and if at least one effect should be applied
|
||||
if (!(useBloom || useToneMapping || useCameraArtifacts))
|
||||
{
|
||||
// Resources are missing. Do not perform rendering. Just copy raw frame
|
||||
context->SetRenderTarget(*output);
|
||||
context->Draw(input);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache data
|
||||
auto shader = _shader->GetShader();
|
||||
auto cb0 = shader->GetCB(0);
|
||||
auto cb1 = shader->GetCB(1);
|
||||
|
||||
// Cache viewport sizes
|
||||
int32 w1 = input->Width();
|
||||
int32 w2 = w1 >> 1;
|
||||
int32 w4 = w2 >> 1;
|
||||
int32 w8 = w4 >> 1;
|
||||
int32 h1 = input->Height();
|
||||
int32 h2 = h1 >> 1;
|
||||
int32 h4 = h2 >> 1;
|
||||
int32 h8 = h4 >> 1;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Setup shader
|
||||
|
||||
Data data;
|
||||
float time = Time::Draw.UnscaledTime.GetTotalSeconds();
|
||||
data.Time = Math::Fractional(time);
|
||||
if (useCameraArtifacts)
|
||||
{
|
||||
data.VignetteColor = settings.CameraArtifacts.VignetteColor;
|
||||
data.VignetteIntensity = settings.CameraArtifacts.VignetteIntensity;
|
||||
data.VignetteShapeFactor = settings.CameraArtifacts.VignetteShapeFactor;
|
||||
data.GrainAmount = settings.CameraArtifacts.GrainAmount;
|
||||
data.GrainParticleSize = Math::Max(0.0001f, settings.CameraArtifacts.GrainParticleSize);
|
||||
data.GrainTime = time * 0.5f * settings.CameraArtifacts.GrainSpeed;
|
||||
data.ChromaticDistortion = Math::Saturate(settings.CameraArtifacts.ChromaticDistortion);
|
||||
data.ScreenFadeColor = settings.CameraArtifacts.ScreenFadeColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.VignetteIntensity = 0;
|
||||
data.GrainAmount = 0;
|
||||
data.ChromaticDistortion = 0;
|
||||
data.ScreenFadeColor = Color::Transparent;
|
||||
}
|
||||
if (useBloom)
|
||||
{
|
||||
data.BloomMagnitude = settings.Bloom.Intensity;
|
||||
data.BloomThreshold = settings.Bloom.Threshold;
|
||||
data.BloomBlurSigma = Math::Max(settings.Bloom.BlurSigma, 0.0001f);
|
||||
data.BloomLimit = settings.Bloom.Limit;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.BloomMagnitude = 0;
|
||||
}
|
||||
if (useLensFlares)
|
||||
{
|
||||
data.LensFlareIntensity = settings.LensFlares.Intensity;
|
||||
data.LensDirtIntensity = settings.LensFlares.LensDirtIntensity;
|
||||
data.Ghosts = settings.LensFlares.Ghosts;
|
||||
|
||||
data.HaloWidth = settings.LensFlares.HaloWidth;
|
||||
data.HaloIntensity = settings.LensFlares.HaloIntensity;
|
||||
data.Distortion = settings.LensFlares.Distortion;
|
||||
data.GhostDispersal = settings.LensFlares.GhostDispersal;
|
||||
|
||||
data.LensBias = settings.LensFlares.ThresholdBias;
|
||||
data.LensScale = settings.LensFlares.ThresholdScale;
|
||||
data.LensInputDistortion = Vector2(-(1.0f / w4) * settings.LensFlares.Distortion, (1.0f / w4) * settings.LensFlares.Distortion);
|
||||
|
||||
// Calculate star texture rotation matrix
|
||||
Vector3 camX = renderContext.View.View.GetRight();
|
||||
Vector3 camZ = renderContext.View.View.GetForward();
|
||||
float camRot = Vector3::Dot(camX, Vector3::Forward) + Vector3::Dot(camZ, Vector3::Up);
|
||||
float camRotCos = Math::Cos(camRot) * 0.8f;
|
||||
float camRotSin = Math::Sin(camRot) * 0.8f;
|
||||
Matrix rotation(
|
||||
camRotCos, -camRotSin, 0.0f, 0.0f,
|
||||
camRotSin, camRotCos, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.01f, 1.0f
|
||||
);
|
||||
data.LensFlareStarMat = rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.LensFlareIntensity = 0;
|
||||
data.LensDirtIntensity = 0;
|
||||
}
|
||||
data.PostExposure = Math::Exp2(settings.EyeAdaptation.PostExposure);
|
||||
data.InputSize = Vector2(static_cast<float>(w1), static_cast<float>(h1));
|
||||
data.InvInputSize = Vector2(1.0f / static_cast<float>(w1), 1.0f / static_cast<float>(h1));
|
||||
data.InputAspect = static_cast<float>(w1) / h1;
|
||||
context->UpdateCB(cb0, &data);
|
||||
context->BindCB(0, cb0);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Bloom
|
||||
|
||||
auto tempDesc = GPUTextureDescription::New2D(w2, h2, 0, PixelFormat::R11G11B10_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews);
|
||||
auto bloomTmp1 = RenderTargetPool::Get(tempDesc);
|
||||
// TODO: bloomTmp2 could be quarter res because we don't use it's first mip
|
||||
auto bloomTmp2 = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
// Check if use bloom
|
||||
if (useBloom)
|
||||
{
|
||||
// Bloom Threshold and downscale to 1/2
|
||||
context->SetRenderTarget(bloomTmp1->View(0, 0));
|
||||
context->SetViewportAndScissors((float)w2, (float)h2);
|
||||
context->BindSR(0, input->View());
|
||||
context->SetState(_psThreshold);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Downscale to 1/4
|
||||
context->SetRenderTarget(bloomTmp1->View(0, 1));
|
||||
context->SetViewportAndScissors((float)w4, (float)h4);
|
||||
context->BindSR(0, bloomTmp1->View(0, 0));
|
||||
context->SetState(_psScale);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Downscale to 1/8
|
||||
context->SetRenderTarget(bloomTmp1->View(0, 2));
|
||||
context->SetViewportAndScissors((float)w8, (float)h8);
|
||||
context->BindSR(0, bloomTmp1->View(0, 1));
|
||||
context->SetState(_psScale);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// TODO: perform blur when downscaling (13 tap) and when upscaling? (9 tap)
|
||||
|
||||
// Gaussian Blur
|
||||
GB_ComputeKernel(data.BloomBlurSigma, static_cast<float>(w8), static_cast<float>(h8));
|
||||
//int32 blurStages = (int)Rendering.Quality + 1;
|
||||
int32 blurStages = 2;
|
||||
for (int32 i = 0; i < blurStages; i++)
|
||||
{
|
||||
// Horizontal Bloom Blur
|
||||
Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheH, sizeof(GaussianBlurCacheH));
|
||||
context->UpdateCB(cb1, &_gbData);
|
||||
context->BindCB(1, cb1);
|
||||
//
|
||||
context->SetRenderTarget(bloomTmp2->View(0, 2));
|
||||
context->BindSR(0, bloomTmp1->View(0, 2));
|
||||
context->SetState(_psBlurH);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Vertical Bloom Blur
|
||||
Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheV, sizeof(GaussianBlurCacheV));
|
||||
context->UpdateCB(cb1, &_gbData);
|
||||
context->BindCB(1, cb1);
|
||||
//
|
||||
context->SetRenderTarget(bloomTmp1->View(0, 2));
|
||||
context->BindSR(0, bloomTmp2->View(0, 2));
|
||||
context->SetState(_psBlurV);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
// Upscale to 1/4 (use second tmp target to cache that downscale thress data for lens flares)
|
||||
context->SetRenderTarget(bloomTmp2->View(0, 1));
|
||||
context->SetViewportAndScissors((float)w4, (float)h4);
|
||||
context->BindSR(0, bloomTmp1->View(0, 2));
|
||||
context->SetState(_psScale);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Upscale to 1/2
|
||||
context->SetRenderTarget(bloomTmp1->View(0, 0));
|
||||
context->SetViewportAndScissors((float)w2, (float)h2);
|
||||
context->BindSR(0, bloomTmp2->View(0, 1));
|
||||
context->SetState(_psScale);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Set bloom
|
||||
context->UnBindSR(0);
|
||||
context->BindSR(2, bloomTmp1->View(0, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No bloom texture
|
||||
context->UnBindSR(2);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Lens Flares
|
||||
|
||||
// Check if use lens flares
|
||||
if (useLensFlares)
|
||||
{
|
||||
// Prepare lens flares helper textures
|
||||
context->BindSR(5, getCustomOrDefault(settings.LensFlares.LensStar, _defaultLensStar, TEXT("Engine/Textures/DefaultLensStarburst")));
|
||||
context->BindSR(6, getCustomOrDefault(settings.LensFlares.LensColor, _defaultLensColor, TEXT("Engine/Textures/DefaultLensColor")));
|
||||
|
||||
// Render lens flares
|
||||
context->SetRenderTarget(bloomTmp2->View(0, 1));
|
||||
context->SetViewportAndScissors((float)w4, (float)h4);
|
||||
context->BindSR(3, bloomTmp1->View(0, 1));
|
||||
context->SetState(_psGenGhosts);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
context->UnBindSR(3);
|
||||
|
||||
// Gaussian blur kernel
|
||||
GB_ComputeKernel(2.0f, static_cast<float>(w4), static_cast<float>(h4));
|
||||
|
||||
// Gaussian blur H
|
||||
Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheH, sizeof(GaussianBlurCacheH));
|
||||
context->UpdateCB(cb1, &_gbData);
|
||||
context->BindCB(1, cb1);
|
||||
context->SetRenderTarget(bloomTmp1->View(0, 1));
|
||||
context->BindSR(0, bloomTmp2->View(0, 1));
|
||||
context->SetState(_psBlurH);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Gaussian blur V
|
||||
Platform::MemoryCopy(_gbData.GaussianBlurCache, GaussianBlurCacheV, sizeof(GaussianBlurCacheV));
|
||||
context->UpdateCB(cb1, &_gbData);
|
||||
context->BindCB(1, cb1);
|
||||
context->SetRenderTarget(bloomTmp2->View(0, 1));
|
||||
context->BindSR(0, bloomTmp1->View(0, 1));
|
||||
context->SetState(_psBlurV);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Set lens flares output
|
||||
context->BindSR(3, bloomTmp2->View(0, 1));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// Final composite
|
||||
|
||||
// TODO: consider to use more compute shader for post processing
|
||||
|
||||
// TODO: maybe don't use this rt swap and start using GetTempRt to make this design easier
|
||||
|
||||
// Check if use Tone Mapping + Color Grading LUT
|
||||
int32 compositePermutationIndex = 0;
|
||||
GPUTextureView* colorGradingLutView = nullptr;
|
||||
if (colorGradingLUT)
|
||||
{
|
||||
if (colorGradingLUT->IsVolume())
|
||||
{
|
||||
compositePermutationIndex = 1;
|
||||
colorGradingLutView = colorGradingLUT->ViewVolume();
|
||||
}
|
||||
else
|
||||
{
|
||||
compositePermutationIndex = 2;
|
||||
colorGradingLutView = colorGradingLUT->View();
|
||||
}
|
||||
}
|
||||
|
||||
// Composite pass inputs mapping:
|
||||
// - 0 - Input0 - scene color
|
||||
// - 1 - Input1 - <unused>
|
||||
// - 2 - Input2 - bloom
|
||||
// - 3 - Input3 - lens flare color
|
||||
// - 4 - LensDirt - lens dirt texture
|
||||
// - 5 - LensStar - lens star texture
|
||||
// - 7 - ColorGradingLUT
|
||||
context->BindSR(0, input->View());
|
||||
context->BindSR(4, getCustomOrDefault(settings.LensFlares.LensDirt, _defaultLensDirt, TEXT("Engine/Textures/DefaultLensDirt")));
|
||||
context->BindSR(7, colorGradingLutView);
|
||||
|
||||
// Composite final frame during single pass (done in full resolution)
|
||||
auto viewport = renderContext.Task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
context->SetRenderTarget(*output);
|
||||
context->SetState(_psComposite.Get(compositePermutationIndex));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Cleanup
|
||||
RenderTargetPool::Release(bloomTmp1);
|
||||
RenderTargetPool::Release(bloomTmp2);
|
||||
}
|
||||
139
Source/Engine/Renderer/PostProcessingPass.h
Normal file
139
Source/Engine/Renderer/PostProcessingPass.h
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
|
||||
#define GB_RADIUS 6
|
||||
#define GB_KERNEL_SIZE (GB_RADIUS * 2 + 1)
|
||||
|
||||
/// <summary>
|
||||
/// Post processing rendering service
|
||||
/// </summary>
|
||||
class PostProcessingPass : public RendererPass<PostProcessingPass>
|
||||
{
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct Data {
|
||||
float BloomLimit;
|
||||
float BloomThreshold;
|
||||
float BloomMagnitude;
|
||||
float BloomBlurSigma;
|
||||
|
||||
Vector3 VignetteColor;
|
||||
float VignetteShapeFactor;
|
||||
|
||||
Vector2 InputSize;
|
||||
float InputAspect;
|
||||
float GrainAmount;
|
||||
|
||||
float GrainTime;
|
||||
float GrainParticleSize;
|
||||
int32 Ghosts;
|
||||
float HaloWidth;
|
||||
|
||||
float HaloIntensity;
|
||||
float Distortion;
|
||||
float GhostDispersal;
|
||||
float LensFlareIntensity;
|
||||
|
||||
Vector2 LensInputDistortion;
|
||||
float LensScale;
|
||||
float LensBias;
|
||||
|
||||
Vector2 InvInputSize;
|
||||
float ChromaticDistortion;
|
||||
float Time;
|
||||
|
||||
float Dummy1;
|
||||
float PostExposure;
|
||||
float VignetteIntensity;
|
||||
float LensDirtIntensity;
|
||||
|
||||
Color ScreenFadeColor;
|
||||
|
||||
Matrix LensFlareStarMat;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct GaussianBlurData {
|
||||
Vector2 Size;
|
||||
float Dummy3;
|
||||
float Dummy4;
|
||||
Vector4 GaussianBlurCache[GB_KERNEL_SIZE]; // x-weight, y-offset
|
||||
});
|
||||
|
||||
// Post Processing
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psThreshold;
|
||||
GPUPipelineState* _psScale;
|
||||
GPUPipelineState* _psBlurH;
|
||||
GPUPipelineState* _psBlurV;
|
||||
GPUPipelineState* _psGenGhosts;
|
||||
GPUPipelineStatePermutationsPs<3> _psComposite;
|
||||
|
||||
GaussianBlurData _gbData;
|
||||
Vector4 GaussianBlurCacheH[GB_KERNEL_SIZE];
|
||||
Vector4 GaussianBlurCacheV[GB_KERNEL_SIZE];
|
||||
|
||||
AssetReference<Texture> _defaultLensColor;
|
||||
AssetReference<Texture> _defaultLensStar;
|
||||
AssetReference<Texture> _defaultLensDirt;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
PostProcessingPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Perform postFx rendering for the input task
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="input">Target with rendered HDR frame to post process</param>
|
||||
/// <param name="output">Output frame</param>
|
||||
/// <param name="colorGradingLUT">The prebaked LUT for color grading and tonemapping.</param>
|
||||
void Render(RenderContext& renderContext, GPUTexture* input, GPUTexture* output, GPUTexture* colorGradingLUT);
|
||||
|
||||
private:
|
||||
|
||||
GPUTexture* getCustomOrDefault(Texture* customTexture, AssetReference<Texture>& defaultTexture, const Char* defaultName);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the Gaussian blur filter kernel. This implementation is
|
||||
/// ported from the original Java code appearing in chapter 16 of
|
||||
/// "Filthy Rich Clients: Developing Animated and Graphical Effects for Desktop Java".
|
||||
/// </summary>
|
||||
/// <param name="sigma">Gaussian Blur sigma parameter</param>
|
||||
/// <param name="width">Texture to blur width in pixels</param>
|
||||
/// <param name="height">Texture to blur height in pixels</param>
|
||||
void GB_ComputeKernel(float sigma, float width, float height);
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psThreshold->ReleaseGPU();
|
||||
_psScale->ReleaseGPU();
|
||||
_psBlurH->ReleaseGPU();
|
||||
_psBlurV->ReleaseGPU();
|
||||
_psGenGhosts->ReleaseGPU();
|
||||
_psComposite.Release();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
603
Source/Engine/Renderer/ProbesRenderer.cpp
Normal file
603
Source/Engine/Renderer/ProbesRenderer.cpp
Normal file
@@ -0,0 +1,603 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if COMPILE_WITH_PROBES_BAKING
|
||||
|
||||
#include "ProbesRenderer.h"
|
||||
#include "Renderer.h"
|
||||
#include "ReflectionsPass.h"
|
||||
#include "Engine/Threading/ThreadPoolTask.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Level/Actors/PointLight.h"
|
||||
#include "Engine/Level/Actors/EnvironmentProbe.h"
|
||||
#include "Engine/Level/Actors/SkyLight.h"
|
||||
#include "Engine/Level/SceneQuery.h"
|
||||
#include "Engine/ContentExporters/AssetExporters.h"
|
||||
#include "Engine/Serialization/FileWriteStream.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/AssetReference.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
|
||||
/// <summary>
|
||||
/// Custom task called after downloading probe texture data to save it.
|
||||
/// </summary>
|
||||
/// <seealso cref="ThreadPoolTask" />
|
||||
class DownloadProbeTask : public ThreadPoolTask
|
||||
{
|
||||
private:
|
||||
|
||||
GPUTexture* _texture;
|
||||
TextureData _data;
|
||||
ProbesRenderer::Entry _entry;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DownloadProbeTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="entry">The entry.</param>
|
||||
DownloadProbeTask(GPUTexture* target, const ProbesRenderer::Entry& entry)
|
||||
: _texture(target)
|
||||
, _entry(entry)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture data container.
|
||||
/// </summary>
|
||||
FORCE_INLINE TextureData& GetData()
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// [ThreadPoolTask]
|
||||
bool Run() override
|
||||
{
|
||||
// Switch type
|
||||
if (_entry.Type == ProbesRenderer::EntryType::EnvProbe)
|
||||
{
|
||||
if (_entry.Actor)
|
||||
((EnvironmentProbe*)_entry.Actor.Get())->SetProbeData(_data);
|
||||
}
|
||||
else if (_entry.Type == ProbesRenderer::EntryType::SkyLight)
|
||||
{
|
||||
if (_entry.Actor)
|
||||
((SkyLight*)_entry.Actor.Get())->SetProbeData(_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fire event
|
||||
ProbesRenderer::OnFinishBake(_entry);
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
PACK_STRUCT(struct Data
|
||||
{
|
||||
Vector2 Dummy0;
|
||||
int32 CubeFace;
|
||||
int32 SourceMipIndex;
|
||||
Vector4 Sample01;
|
||||
Vector4 Sample23;
|
||||
Vector4 CoefficientMask0;
|
||||
Vector4 CoefficientMask1;
|
||||
Vector3 Dummy1;
|
||||
float CoefficientMask2;
|
||||
});
|
||||
|
||||
namespace ProbesRendererImpl
|
||||
{
|
||||
TimeSpan _lastProbeUpdate(0);
|
||||
Array<ProbesRenderer::Entry> _probesToBake;
|
||||
|
||||
ProbesRenderer::Entry _current;
|
||||
|
||||
bool _isReady = false;
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psFilterFace = nullptr;
|
||||
GPUPipelineState* _psCopyFace = nullptr;
|
||||
GPUPipelineState* _psCalcDiffuseIrradiance = nullptr;
|
||||
GPUPipelineState* _psAccDiffuseIrradiance = nullptr;
|
||||
GPUPipelineState* _psAccumulateCubeFaces = nullptr;
|
||||
GPUPipelineState* _psCopyFrameLHB = nullptr;
|
||||
SceneRenderTask* _task = nullptr;
|
||||
GPUTexture* _output = nullptr;
|
||||
GPUTexture* _probe = nullptr;
|
||||
GPUTexture* _tmpFace = nullptr;
|
||||
GPUTexture* _skySHIrradianceMap = nullptr;
|
||||
uint64 _updateFrameNumber = 0;
|
||||
|
||||
FORCE_INLINE bool isUpdateSynced()
|
||||
{
|
||||
return _updateFrameNumber > 0 && _updateFrameNumber + PROBES_RENDERER_LATENCY_FRAMES <= Engine::FrameCount;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace ProbesRendererImpl;
|
||||
|
||||
class ProbesRendererService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
ProbesRendererService()
|
||||
: EngineService(TEXT("Probes Renderer"), 70)
|
||||
{
|
||||
}
|
||||
|
||||
void Update() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
ProbesRendererService ProbesRendererServiceInstance;
|
||||
|
||||
TimeSpan ProbesRenderer::ProbesUpdatedBreak(0, 0, 0, 0, 500);
|
||||
TimeSpan ProbesRenderer::ProbesReleaseDataTime(0, 0, 0, 60);
|
||||
Delegate<const ProbesRenderer::Entry&> ProbesRenderer::OnRegisterBake;
|
||||
Delegate<const ProbesRenderer::Entry&> ProbesRenderer::OnFinishBake;
|
||||
|
||||
void ProbesRenderer::Bake(EnvironmentProbe* probe, float timeout)
|
||||
{
|
||||
ASSERT(probe && dynamic_cast<EnvironmentProbe*>(probe));
|
||||
|
||||
// Check if already registered for bake
|
||||
for (int32 i = 0; i < _probesToBake.Count(); i++)
|
||||
{
|
||||
auto& p = _probesToBake[i];
|
||||
if (p.Type == EntryType::EnvProbe && p.Actor == probe)
|
||||
{
|
||||
p.Timeout = timeout;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Register probe
|
||||
Entry e;
|
||||
e.Type = EntryType::EnvProbe;
|
||||
e.Actor = probe;
|
||||
e.Timeout = timeout;
|
||||
_probesToBake.Add(e);
|
||||
|
||||
// Fire event
|
||||
OnRegisterBake(e);
|
||||
}
|
||||
|
||||
void ProbesRenderer::Bake(SkyLight* probe, float timeout)
|
||||
{
|
||||
ASSERT(probe && dynamic_cast<SkyLight*>(probe));
|
||||
|
||||
// Check if already registered for bake
|
||||
for (int32 i = 0; i < _probesToBake.Count(); i++)
|
||||
{
|
||||
auto& p = _probesToBake[i];
|
||||
if (p.Type == EntryType::SkyLight && p.Actor == probe)
|
||||
{
|
||||
p.Timeout = timeout;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Register probe
|
||||
Entry e;
|
||||
e.Type = EntryType::SkyLight;
|
||||
e.Actor = probe;
|
||||
e.Timeout = timeout;
|
||||
_probesToBake.Add(e);
|
||||
|
||||
// Fire event
|
||||
OnRegisterBake(e);
|
||||
}
|
||||
|
||||
int32 ProbesRenderer::GetBakeQueueSize()
|
||||
{
|
||||
return _probesToBake.Count();
|
||||
}
|
||||
|
||||
bool ProbesRenderer::HasReadyResources()
|
||||
{
|
||||
return _isReady && _shader->IsLoaded();
|
||||
}
|
||||
|
||||
bool ProbesRenderer::Init()
|
||||
{
|
||||
if (_isReady)
|
||||
return false;
|
||||
|
||||
// Load shader
|
||||
if (_shader == nullptr)
|
||||
{
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/ProbesFilter"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
}
|
||||
if (!_shader->IsLoaded())
|
||||
return false;
|
||||
const auto shader = _shader->GetShader();
|
||||
|
||||
LOG(Info, "Starting Probes Renderer service");
|
||||
|
||||
// Validate shader constant buffers sizes
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(Data))
|
||||
{
|
||||
LOG(Fatal, "Shader {0} has incorrect constant buffer {1} size: {2} bytes. Expected: {3} bytes", _shader.ToString(), 0, shader->GetCB(0)->GetSize(), sizeof(Data));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline stages
|
||||
_psFilterFace = GPUDevice::Instance->CreatePipelineState();
|
||||
_psCopyFace = GPUDevice::Instance->CreatePipelineState();
|
||||
_psCalcDiffuseIrradiance = GPUDevice::Instance->CreatePipelineState();
|
||||
_psAccDiffuseIrradiance = GPUDevice::Instance->CreatePipelineState();
|
||||
_psAccumulateCubeFaces = GPUDevice::Instance->CreatePipelineState();
|
||||
_psCopyFrameLHB = GPUDevice::Instance->CreatePipelineState();
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_FilterFace");
|
||||
if (_psFilterFace->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_CopyFace");
|
||||
if (_psCopyFace->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_CalcDiffuseIrradiance");
|
||||
if (_psCalcDiffuseIrradiance->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_AccDiffuseIrradiance");
|
||||
if (_psAccDiffuseIrradiance->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_AccumulateCubeFaces");
|
||||
if (_psAccumulateCubeFaces->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_CopyFrameLHB");
|
||||
if (_psCopyFrameLHB->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Init rendering pipeline
|
||||
_output = GPUTexture::New();
|
||||
if (_output->Init(GPUTextureDescription::New2D(ENV_PROBES_RESOLUTION, ENV_PROBES_RESOLUTION, ENV_PROBES_FORMAT)))
|
||||
return true;
|
||||
_task = New<SceneRenderTask>();
|
||||
auto task = _task;
|
||||
task->Enabled = false;
|
||||
task->Output = _output;
|
||||
auto& view = task->View;
|
||||
view.Flags =
|
||||
ViewFlags::AO |
|
||||
ViewFlags::GI |
|
||||
ViewFlags::DirectionalLights |
|
||||
ViewFlags::PointLights |
|
||||
ViewFlags::SpotLights |
|
||||
ViewFlags::SkyLights |
|
||||
ViewFlags::Decals |
|
||||
ViewFlags::Shadows |
|
||||
ViewFlags::Fog;
|
||||
view.Mode = ViewMode::NoPostFx;
|
||||
view.IsOfflinePass = true;
|
||||
view.StaticFlagsMask = StaticFlags::ReflectionProbe;
|
||||
view.MaxShadowsQuality = Quality::Low;
|
||||
task->IsCameraCut = true;
|
||||
task->Resize(ENV_PROBES_RESOLUTION, ENV_PROBES_RESOLUTION);
|
||||
task->Render.Bind(onRender);
|
||||
|
||||
// Init render targets
|
||||
_probe = GPUDevice::Instance->CreateTexture(TEXT("ProbesUpdate.Probe"));
|
||||
if (_probe->Init(GPUTextureDescription::NewCube(ENV_PROBES_RESOLUTION, ENV_PROBES_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews, 0)))
|
||||
return true;
|
||||
_tmpFace = GPUDevice::Instance->CreateTexture(TEXT("ProbesUpdate.TmpFae"));
|
||||
if (_tmpFace->Init(GPUTextureDescription::New2D(ENV_PROBES_RESOLUTION, ENV_PROBES_RESOLUTION, 0, ENV_PROBES_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget | GPUTextureFlags::PerMipViews)))
|
||||
return true;
|
||||
|
||||
// Mark as ready
|
||||
_isReady = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProbesRenderer::Release()
|
||||
{
|
||||
if (!_isReady)
|
||||
return;
|
||||
ASSERT(_updateFrameNumber == 0);
|
||||
|
||||
LOG(Info, "Disposing Probes Renderer service");
|
||||
|
||||
// Release GPU data
|
||||
if (_output)
|
||||
_output->ReleaseGPU();
|
||||
|
||||
// Release data
|
||||
SAFE_DELETE_GPU_RESOURCE(_psFilterFace);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCopyFace);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCalcDiffuseIrradiance);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psAccDiffuseIrradiance);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psAccumulateCubeFaces);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCopyFrameLHB);
|
||||
_shader.Unlink();
|
||||
SAFE_DELETE_GPU_RESOURCE(_output);
|
||||
SAFE_DELETE(_task);
|
||||
SAFE_DELETE_GPU_RESOURCE(_probe);
|
||||
SAFE_DELETE_GPU_RESOURCE(_tmpFace);
|
||||
SAFE_DELETE_GPU_RESOURCE(_skySHIrradianceMap);
|
||||
|
||||
_isReady = false;
|
||||
}
|
||||
|
||||
void ProbesRendererService::Update()
|
||||
{
|
||||
// Calculate time delta since last update
|
||||
auto timeNow = Time::Update.UnscaledTime;
|
||||
auto timeSinceUpdate = timeNow - _lastProbeUpdate;
|
||||
|
||||
// Check if render job is done
|
||||
if (isUpdateSynced())
|
||||
{
|
||||
// Create async job to gather probe data from the GPU
|
||||
GPUTexture* texture = nullptr;
|
||||
switch (_current.Type)
|
||||
{
|
||||
case ProbesRenderer::EntryType::SkyLight:
|
||||
case ProbesRenderer::EntryType::EnvProbe:
|
||||
texture = _probe;
|
||||
break;
|
||||
}
|
||||
ASSERT(texture);
|
||||
auto taskB = New<DownloadProbeTask>(texture, _current);
|
||||
auto taskA = texture->DownloadDataAsync(taskB->GetData());
|
||||
if (taskA == nullptr)
|
||||
{
|
||||
LOG(Fatal, "Failed to create async tsk to download env probe texture data fro mthe GPU.");
|
||||
}
|
||||
taskA->ContinueWith(taskB);
|
||||
taskA->Start();
|
||||
|
||||
// Clear flag
|
||||
_updateFrameNumber = 0;
|
||||
_current.Type = ProbesRenderer::EntryType::Invalid;
|
||||
}
|
||||
else if (_current.Type == ProbesRenderer::EntryType::Invalid)
|
||||
{
|
||||
int32 firstValidEntryIndex = -1;
|
||||
auto dt = (float)Time::Update.UnscaledDeltaTime.GetTotalSeconds();
|
||||
for (int32 i = 0; i < _probesToBake.Count(); i++)
|
||||
{
|
||||
_probesToBake[i].Timeout -= dt;
|
||||
if (_probesToBake[i].Timeout <= 0)
|
||||
{
|
||||
firstValidEntryIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if need to update probe
|
||||
if (firstValidEntryIndex >= 0 && timeSinceUpdate > ProbesRenderer::ProbesUpdatedBreak)
|
||||
{
|
||||
// Init service
|
||||
if (ProbesRenderer::Init())
|
||||
{
|
||||
LOG(Fatal, "Cannot setup Probes Renderer!");
|
||||
}
|
||||
if (ProbesRenderer::HasReadyResources() == false)
|
||||
return;
|
||||
|
||||
// Mark probe to update
|
||||
_current = _probesToBake[firstValidEntryIndex];
|
||||
_probesToBake.RemoveAtKeepOrder(firstValidEntryIndex);
|
||||
_task->Enabled = true;
|
||||
_updateFrameNumber = 0;
|
||||
|
||||
// Store time of the last probe update
|
||||
_lastProbeUpdate = timeNow;
|
||||
}
|
||||
// Check if need to release data
|
||||
else if (_isReady && timeSinceUpdate > ProbesRenderer::ProbesReleaseDataTime)
|
||||
{
|
||||
// Release service
|
||||
ProbesRenderer::Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProbesRendererService::Dispose()
|
||||
{
|
||||
ProbesRenderer::Release();
|
||||
}
|
||||
|
||||
bool fixFarPlaneTreeExecute(Actor* actor, const Vector3& position, float& farPlane)
|
||||
{
|
||||
if (auto* pointLight = dynamic_cast<PointLight*>(actor))
|
||||
{
|
||||
const float dst = Vector3::Distance(pointLight->GetPosition(), position) + pointLight->GetScaledRadius();
|
||||
if (dst > farPlane)
|
||||
{
|
||||
farPlane = dst;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProbesRenderer::onRender(RenderTask* task, GPUContext* context)
|
||||
{
|
||||
ASSERT(_current.Type != EntryType::Invalid && _updateFrameNumber == 0);
|
||||
switch (_current.Type)
|
||||
{
|
||||
case EntryType::EnvProbe:
|
||||
case EntryType::SkyLight:
|
||||
{
|
||||
if (_current.Actor == nullptr)
|
||||
{
|
||||
// Probe has been unlinked (or deleted)
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Canceled
|
||||
return;
|
||||
}
|
||||
|
||||
bool setLowerHemisphereToBlack = false;
|
||||
auto shader = _shader->GetShader();
|
||||
|
||||
// Init
|
||||
float customCullingNear = -1;
|
||||
if (_current.Type == EntryType::EnvProbe)
|
||||
{
|
||||
auto envProbe = (EnvironmentProbe*)_current.Actor.Get();
|
||||
LOG(Info, "Updating Env probe '{0}'...", envProbe->ToString());
|
||||
Vector3 position = envProbe->GetPosition();
|
||||
float radius = envProbe->GetScaledRadius();
|
||||
float nearPlane = Math::Max(0.1f, envProbe->CaptureNearPlane);
|
||||
|
||||
// Fix far plane distance
|
||||
// TODO: investigate performance of this action, maybe we could skip it?
|
||||
float farPlane = Math::Max(radius, nearPlane + 100.0f);
|
||||
Function<bool(Actor*, const Vector3&, float&)> f(&fixFarPlaneTreeExecute);
|
||||
SceneQuery::TreeExecute<const Vector3&, float&>(f, position, farPlane);
|
||||
|
||||
// Setup view
|
||||
_task->View.SetUpCube(nearPlane, farPlane, position);
|
||||
}
|
||||
else if (_current.Type == EntryType::SkyLight)
|
||||
{
|
||||
auto skyLight = (SkyLight*)_current.Actor.Get();
|
||||
LOG(Info, "Updating sky light '{0}'...", skyLight->ToString());
|
||||
Vector3 position = skyLight->GetPosition();
|
||||
float nearPlane = 10.0f;
|
||||
float farPlane = Math::Max(nearPlane + 1000.0f, skyLight->SkyDistanceThreshold * 2.0f);
|
||||
customCullingNear = skyLight->SkyDistanceThreshold;
|
||||
|
||||
// TODO: use setLowerHemisphereToBlack feature for SkyLight?
|
||||
|
||||
// Setup view
|
||||
_task->View.SetUpCube(nearPlane, farPlane, position);
|
||||
}
|
||||
|
||||
// Disable actor during baking (it cannot influence own results)
|
||||
const bool isActorActive = _current.Actor->GetIsActive();
|
||||
_current.Actor->SetIsActive(false);
|
||||
|
||||
// Render scene for all faces
|
||||
for (int32 faceIndex = 0; faceIndex < 6; faceIndex++)
|
||||
{
|
||||
// Set view
|
||||
_task->View.SetFace(faceIndex);
|
||||
|
||||
// Handle custom frustum for the culling (used to skip objects near the camera)
|
||||
if (customCullingNear > 0)
|
||||
{
|
||||
Matrix p;
|
||||
Matrix::PerspectiveFov(PI_OVER_2, 1.0f, customCullingNear, _task->View.Far, p);
|
||||
_task->View.CullingFrustum.SetMatrix(_task->View.View, p);
|
||||
}
|
||||
|
||||
// Render frame
|
||||
Renderer::Render(_task);
|
||||
context->ClearState();
|
||||
|
||||
// Copy frame to cube face
|
||||
context->SetRenderTarget(_probe->View(faceIndex));
|
||||
auto probeFrame = _output->View();
|
||||
if (setLowerHemisphereToBlack && faceIndex != 2)
|
||||
{
|
||||
MISSING_CODE("set lower hemisphere of the probe to black");
|
||||
/*
|
||||
ProbesFilter_Tmp.Set(_core.Rendering.Pool.RT2_FloatRGB.Handle);
|
||||
ProbesFilter_CoefficientMask2.Set(f != 3 ? 1.0f : 0.0f);
|
||||
ProbesFilter_Shader.Apply(5);
|
||||
_device.DrawFullscreenTriangle();
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
context->Draw(probeFrame);
|
||||
}
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
// Enable actor back
|
||||
_current.Actor->SetIsActive(isActorActive);
|
||||
|
||||
// Filter all lower mip levels
|
||||
{
|
||||
Data data;
|
||||
int32 mipLevels = _probe->MipLevels();
|
||||
auto cb = shader->GetCB(0);
|
||||
for (int32 mipIndex = 1; mipIndex < mipLevels; mipIndex++)
|
||||
{
|
||||
// Cache data
|
||||
int32 srcMipIndex = mipIndex - 1;
|
||||
int32 mipSize = 1 << (mipLevels - mipIndex - 1);
|
||||
data.SourceMipIndex = srcMipIndex;
|
||||
|
||||
// Set viewport
|
||||
float mipSizeFloat = (float)mipSize;
|
||||
context->SetViewportAndScissors(mipSizeFloat, mipSizeFloat);
|
||||
|
||||
// Filter all faces
|
||||
for (int32 faceIndex = 0; faceIndex < 6; faceIndex++)
|
||||
{
|
||||
context->ResetSR();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Filter face
|
||||
data.CubeFace = faceIndex;
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
context->BindSR(0, _probe->ViewArray());
|
||||
context->SetRenderTarget(_tmpFace->View(0, mipIndex));
|
||||
context->SetState(_psFilterFace);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
context->ResetSR();
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Copy face back to the cubemap
|
||||
copyTmpToFace(context, mipIndex, faceIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
context->ClearState();
|
||||
|
||||
// Mark as rendered
|
||||
_updateFrameNumber = Engine::FrameCount;
|
||||
_task->Enabled = false;
|
||||
}
|
||||
|
||||
void ProbesRenderer::copyTmpToFace(GPUContext* context, int32 mipIndex, int32 faceIndex)
|
||||
{
|
||||
// Set destination render target
|
||||
context->SetRenderTarget(_probe->View(faceIndex, mipIndex));
|
||||
|
||||
// Setup shader constants
|
||||
context->BindSR(1, _tmpFace->View(0, mipIndex));
|
||||
|
||||
// Copy pixels
|
||||
context->SetState(_psCopyFace);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
#endif
|
||||
109
Source/Engine/Renderer/ProbesRenderer.h
Normal file
109
Source/Engine/Renderer/ProbesRenderer.h
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if COMPILE_WITH_PROBES_BAKING
|
||||
|
||||
#include "Engine/Scripting/ScriptingObjectReference.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
|
||||
// Amount of frames to wait for data from probe update job
|
||||
#define PROBES_RENDERER_LATENCY_FRAMES 1
|
||||
|
||||
class EnvironmentProbe;
|
||||
class SkyLight;
|
||||
class RenderTask;
|
||||
|
||||
/// <summary>
|
||||
/// Probes rendering service
|
||||
/// </summary>
|
||||
class ProbesRenderer
|
||||
{
|
||||
public:
|
||||
|
||||
enum class EntryType
|
||||
{
|
||||
Invalid = 0,
|
||||
EnvProbe = 1,
|
||||
SkyLight = 2,
|
||||
};
|
||||
|
||||
struct Entry
|
||||
{
|
||||
EntryType Type;
|
||||
ScriptingObjectReference<Actor> Actor;
|
||||
float Timeout;
|
||||
|
||||
Entry()
|
||||
{
|
||||
Type = EntryType::Invalid;
|
||||
}
|
||||
|
||||
Entry(const Entry& other)
|
||||
{
|
||||
Type = other.Type;
|
||||
Actor = other.Actor;
|
||||
Timeout = other.Timeout;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Minimum amount of time between two updated of probes
|
||||
/// </summary>
|
||||
static TimeSpan ProbesUpdatedBreak;
|
||||
|
||||
/// <summary>
|
||||
/// Time after last probe update when probes updating content will be released
|
||||
/// </summary>
|
||||
static TimeSpan ProbesReleaseDataTime;
|
||||
|
||||
int32 GetBakeQueueSize();
|
||||
|
||||
static Delegate<const Entry&> OnRegisterBake;
|
||||
|
||||
static Delegate<const Entry&> OnFinishBake;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Checks if resources are ready to render probes (shaders or textures may be during loading).
|
||||
/// </summary>
|
||||
/// <returns>True if is ready, otherwise false.</returns>
|
||||
static bool HasReadyResources();
|
||||
|
||||
/// <summary>
|
||||
/// Init probes content
|
||||
/// </summary>
|
||||
/// <returns>True if cannot init service</returns>
|
||||
static bool Init();
|
||||
|
||||
/// <summary>
|
||||
/// Release probes content
|
||||
/// </summary>
|
||||
static void Release();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Register probe to baking service.
|
||||
/// </summary>
|
||||
/// <param name="probe">Probe to bake</param>
|
||||
/// <param name="timeout">Timeout in seconds left to bake it.</param>
|
||||
static void Bake(EnvironmentProbe* probe, float timeout = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Register probe to baking service.
|
||||
/// </summary>
|
||||
/// <param name="probe">Probe to bake</param>
|
||||
/// <param name="timeout">Timeout in seconds left to bake it.</param>
|
||||
static void Bake(SkyLight* probe, float timeout = 0);
|
||||
|
||||
private:
|
||||
|
||||
static void onRender(RenderTask* task, GPUContext* context);
|
||||
static void copyTmpToFace(GPUContext* context, int32 mipIndex, int32 faceIndex);
|
||||
};
|
||||
|
||||
#endif
|
||||
490
Source/Engine/Renderer/ReflectionsPass.cpp
Normal file
490
Source/Engine/Renderer/ReflectionsPass.cpp
Normal file
@@ -0,0 +1,490 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ReflectionsPass.h"
|
||||
#include "GBufferPass.h"
|
||||
#include "ScreenSpaceReflectionsPass.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Level/Actors/EnvironmentProbe.h"
|
||||
|
||||
#if GENERATE_GF_CACHE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
namespace PreIntegratedGF
|
||||
{
|
||||
static const int Resolution = 128;
|
||||
static const int NumSamples = 512;
|
||||
|
||||
struct vec2 {
|
||||
double x, y;
|
||||
vec2(double _x, double _y) :x(_x), y(_y) { };
|
||||
|
||||
vec2& operator /=(const double& b)
|
||||
{
|
||||
x /= b;
|
||||
y /= b;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct ivec2 {
|
||||
int x, y;
|
||||
};
|
||||
|
||||
struct vec3 {
|
||||
double x, y, z;
|
||||
vec3(double _x, double _y, double _z) :x(_x), y(_y), z(_z) { };
|
||||
|
||||
double dot(const vec3& b)
|
||||
{
|
||||
return x*b.x + y*b.y + z*b.z;
|
||||
}
|
||||
};
|
||||
|
||||
vec3 operator*(const double& a, const vec3& b)
|
||||
{
|
||||
return vec3(b.x * a, b.y * a, b.z * a);
|
||||
}
|
||||
vec3 operator-(const vec3& a, const vec3& b)
|
||||
{
|
||||
return vec3(a.x - b.x, a.y - b.y, a.z - b.z);
|
||||
}
|
||||
|
||||
inline double saturate(double x)
|
||||
{
|
||||
if (x < 0) x = 0;
|
||||
if (x > 1) x = 1;
|
||||
return x;
|
||||
}
|
||||
|
||||
unsigned int ReverseBits32(unsigned int bits)
|
||||
{
|
||||
bits = (bits << 16) | (bits >> 16);
|
||||
bits = ((bits & 0x00ff00ff) << 8) | ((bits & 0xff00ff00) >> 8);
|
||||
bits = ((bits & 0x0f0f0f0f) << 4) | ((bits & 0xf0f0f0f0) >> 4);
|
||||
bits = ((bits & 0x33333333) << 2) | ((bits & 0xcccccccc) >> 2);
|
||||
bits = ((bits & 0x55555555) << 1) | ((bits & 0xaaaaaaaa) >> 1);
|
||||
return bits;
|
||||
}
|
||||
|
||||
inline double rand_0_1()
|
||||
{
|
||||
return 1.0 * rand() / RAND_MAX;
|
||||
}
|
||||
|
||||
inline unsigned int rand_32bit()
|
||||
{
|
||||
unsigned int x = rand() & 0xff;
|
||||
x |= (rand() & 0xff) << 8;
|
||||
x |= (rand() & 0xff) << 16;
|
||||
x |= (rand() & 0xff) << 24;
|
||||
return x;
|
||||
}
|
||||
|
||||
// using uniform randomness :(
|
||||
double t1 = rand_0_1();
|
||||
unsigned int t2 = rand_32bit();
|
||||
vec2 Hammersley(int Index, int NumSamples)
|
||||
{
|
||||
double E1 = 1.0 * Index / NumSamples + t1;
|
||||
E1 = E1 - int(E1);
|
||||
double E2 = double(ReverseBits32(Index) ^ t2) * 2.3283064365386963e-10;
|
||||
return vec2(E1, E2);
|
||||
}
|
||||
|
||||
vec3 ImportanceSampleGGX(vec2 E, double Roughness)
|
||||
{
|
||||
double m = Roughness * Roughness;
|
||||
double m2 = m * m;
|
||||
|
||||
double phi = 2 * PI * E.x;
|
||||
double cosTheta = sqrt((1 - E.y) / (1 + (m2 - 1) * E.y));
|
||||
double sinTheta = sqrt(1 - cosTheta * cosTheta);
|
||||
|
||||
vec3 H(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
|
||||
|
||||
double d = (cosTheta * m2 - cosTheta) * cosTheta + 1;
|
||||
double D = m2 / (M_PI*d*d);
|
||||
double PDF = D * cosTheta;
|
||||
|
||||
return H;
|
||||
}
|
||||
|
||||
double Vis_SmithJointApprox(double Roughness, double NoV, double NoL)
|
||||
{
|
||||
double a = Roughness * Roughness;
|
||||
double Vis_SmithV = NoL * (NoV * (1 - a) + a);
|
||||
double Vis_SmithL = NoV * (NoL * (1 - a) + a);
|
||||
return 0.5 / (Vis_SmithV + Vis_SmithL);
|
||||
}
|
||||
|
||||
vec2 IntegrateBRDF(double Roughness, double NoV)
|
||||
{
|
||||
if (Roughness < 0.04) Roughness = 0.04;
|
||||
|
||||
vec3 V(sqrt(1 - NoV*NoV), 0, NoV);
|
||||
double A = 0, B = 0;
|
||||
for (int i = 0; i < NumSamples; i++)
|
||||
{
|
||||
vec2 E = Hammersley(i, NumSamples);
|
||||
vec3 H = ImportanceSampleGGX(E, Roughness);
|
||||
vec3 L = 2 * V.dot(H) * H - V;
|
||||
|
||||
double NoL = saturate(L.z);
|
||||
double NoH = saturate(H.z);
|
||||
double VoH = saturate(V.dot(H));
|
||||
|
||||
if (NoL > 0)
|
||||
{
|
||||
double Vis = Vis_SmithJointApprox(Roughness, NoV, NoL);
|
||||
|
||||
double a = Roughness * Roughness;
|
||||
double a2 = a*a;
|
||||
double Vis_SmithV = NoL * sqrt(NoV * (NoV - NoV * a2) + a2);
|
||||
double Vis_SmithL = NoV * sqrt(NoL * (NoL - NoL * a2) + a2);
|
||||
|
||||
double NoL_Vis_PDF = NoL * Vis * (4 * VoH / NoH);
|
||||
|
||||
double Fc = pow(1 - VoH, 5);
|
||||
A += (1 - Fc) * NoL_Vis_PDF;
|
||||
B += Fc * NoL_Vis_PDF;
|
||||
}
|
||||
}
|
||||
vec2 res(A, B);
|
||||
res /= NumSamples;
|
||||
return res;
|
||||
}
|
||||
|
||||
void Generate()
|
||||
{
|
||||
String path = Globals::TemporaryFolder / TEXT("PreIntegratedGF.bmp");
|
||||
|
||||
FILE* pFile = fopen(path.ToSTD().c_str(), "wb");
|
||||
|
||||
byte data[Resolution * 3 * Resolution];
|
||||
int c = 0;
|
||||
for (int x = 0; x < Resolution; x++)
|
||||
{
|
||||
for (int y = 0; y < Resolution; y++)
|
||||
{
|
||||
vec2 brdf = IntegrateBRDF(1 - 1.0 * x / (Resolution - 1), 1.0 * y / (Resolution - 1));
|
||||
data[c + 2] = byte(brdf.x * 255);
|
||||
data[c + 1] = byte(brdf.y * 255);
|
||||
data[c + 0] = 0;
|
||||
c += 3;
|
||||
}
|
||||
}
|
||||
|
||||
BITMAPINFOHEADER BMIH;
|
||||
BMIH.biSize = sizeof(BITMAPINFOHEADER);
|
||||
BMIH.biSizeImage = Resolution * Resolution * 3;
|
||||
BMIH.biSize = sizeof(BITMAPINFOHEADER);
|
||||
BMIH.biWidth = Resolution;
|
||||
BMIH.biHeight = Resolution;
|
||||
BMIH.biPlanes = 1;
|
||||
BMIH.biBitCount = 24;
|
||||
BMIH.biCompression = BI_RGB;
|
||||
BMIH.biSizeImage = Resolution * Resolution * 3;
|
||||
|
||||
BITMAPFILEHEADER bmfh;
|
||||
int nBitsOffset = sizeof(BITMAPFILEHEADER) + BMIH.biSize;
|
||||
LONG lImageSize = BMIH.biSizeImage;
|
||||
LONG lFileSize = nBitsOffset + lImageSize;
|
||||
|
||||
bmfh.bfType = 'B' + ('M' << 8);
|
||||
bmfh.bfOffBits = nBitsOffset;
|
||||
bmfh.bfSize = lFileSize;
|
||||
bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
|
||||
|
||||
//Write the bitmap file header
|
||||
UINT nWrittenFileHeaderSize = fwrite(&bmfh, 1, sizeof(BITMAPFILEHEADER), pFile);
|
||||
|
||||
//And then the bitmap info header
|
||||
UINT nWrittenInfoHeaderSize = fwrite(&BMIH, 1, sizeof(BITMAPINFOHEADER), pFile);
|
||||
|
||||
//Finally, write the image data itself
|
||||
|
||||
//-- the data represents our drawing
|
||||
UINT nWrittenDIBDataSize = fwrite(data, 1, lImageSize, pFile);
|
||||
|
||||
fclose(pFile);
|
||||
|
||||
Guid id;
|
||||
Importers::TextureImportArgument arg;
|
||||
arg.Options.Type = FormatType::HdrRGB;
|
||||
arg.Options.IndependentChannels = true;
|
||||
arg.Options.IsAtlas = false;
|
||||
arg.Options.IsSRGB = false;
|
||||
arg.Options.NeverStream = true;
|
||||
Content::Import(path, Globals:... + PRE_INTEGRATED_GF_ASSET_NAME, &id, &arg);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class Model;
|
||||
|
||||
ReflectionsPass::ReflectionsPass()
|
||||
: _psProbeNormal(nullptr)
|
||||
, _psProbeInverted(nullptr)
|
||||
, _psCombinePass(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String ReflectionsPass::ToString() const
|
||||
{
|
||||
return TEXT("ReflectionsPass");
|
||||
}
|
||||
|
||||
bool ReflectionsPass::Init()
|
||||
{
|
||||
#if GENERATE_GF_CACHE
|
||||
|
||||
// Generate cache
|
||||
PreIntegratedGF::Generate();
|
||||
|
||||
#endif
|
||||
|
||||
// Create pipeline states
|
||||
_psProbeNormal = GPUDevice::Instance->CreatePipelineState();
|
||||
_psProbeInverted = GPUDevice::Instance->CreatePipelineState();
|
||||
_psCombinePass = GPUDevice::Instance->CreatePipelineState();
|
||||
|
||||
// Load assets
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/Reflections"));
|
||||
_sphereModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/SphereLowPoly"));
|
||||
_preIntegratedGF = Content::LoadAsyncInternal<Texture>(PRE_INTEGRATED_GF_ASSET_NAME);
|
||||
if (_shader == nullptr || _sphereModel == nullptr || _preIntegratedGF == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<ReflectionsPass, &ReflectionsPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReflectionsPass::setupResources()
|
||||
{
|
||||
// Wait for the assets
|
||||
if (!_sphereModel->CanBeRendered() || !_preIntegratedGF->IsLoaded() || !_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;
|
||||
if (!_psProbeNormal->IsValid() || !_psProbeInverted->IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc.BlendMode = BlendingMode::AlphaBlend;
|
||||
psDesc.CullMode = CullMode::Normal;
|
||||
psDesc.VS = shader->GetVS("VS_Model");
|
||||
psDesc.PS = shader->GetPS("PS_EnvProbe");
|
||||
if (_psProbeNormal->Init(psDesc))
|
||||
return true;
|
||||
psDesc.CullMode = CullMode::Inverted;
|
||||
if (_psProbeInverted->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (!_psCombinePass->IsValid())
|
||||
{
|
||||
psDesc.BlendMode = BlendingMode::Add;
|
||||
psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB;
|
||||
psDesc.PS = shader->GetPS("PS_CombinePass");
|
||||
if (_psCombinePass->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReflectionsPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_psProbeNormal);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psProbeInverted);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCombinePass);
|
||||
|
||||
// Release assets
|
||||
_shader.Unlink();
|
||||
_sphereModel.Unlink();
|
||||
_preIntegratedGF.Unlink();
|
||||
}
|
||||
|
||||
bool sortProbes(EnvironmentProbe* const& p1, EnvironmentProbe* const& p2)
|
||||
{
|
||||
// Compare by radius
|
||||
int32 res = static_cast<int32>(p2->GetScaledRadius() - p1->GetScaledRadius());
|
||||
|
||||
// Check if are the same
|
||||
if (res == 0)
|
||||
{
|
||||
// Compare by ID to prevent flickering
|
||||
res = GetHash(p2->GetID()) - GetHash(p1->GetID());
|
||||
}
|
||||
|
||||
// Return result
|
||||
return res < 0;
|
||||
}
|
||||
|
||||
void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* lightBuffer)
|
||||
{
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
|
||||
// Skip pass if resources aren't ready
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
if (renderContext.View.Mode == ViewMode::Reflections)
|
||||
{
|
||||
context->Clear(lightBuffer, Color::Black);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache data
|
||||
auto& view = renderContext.View;
|
||||
bool useReflections = ((view.Flags & ViewFlags::Reflections) != 0);
|
||||
bool useSSR = ((view.Flags & ViewFlags::SSR) != 0) && (renderContext.List->Settings.ScreenSpaceReflections.Intensity > ZeroTolerance);
|
||||
int32 probesCount = (int32)renderContext.List->EnvironmentProbes.Count();
|
||||
bool renderProbes = probesCount > 0;
|
||||
auto shader = _shader->GetShader();
|
||||
auto cb = shader->GetCB(0);
|
||||
|
||||
// Check if no need to render reflection environment
|
||||
if (!useReflections || !(renderProbes || useSSR))
|
||||
return;
|
||||
|
||||
// Setup data
|
||||
Data data;
|
||||
GBufferPass::SetInputs(view, data.GBuffer);
|
||||
|
||||
// Bind GBuffer inputs
|
||||
context->BindSR(0, renderContext.Buffers->GBuffer0);
|
||||
context->BindSR(1, renderContext.Buffers->GBuffer1);
|
||||
context->BindSR(2, renderContext.Buffers->GBuffer2);
|
||||
context->BindSR(3, renderContext.Buffers->DepthBuffer);
|
||||
|
||||
auto tempDesc = GPUTextureDescription::New2D(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight(), REFLECTIONS_PASS_OUTPUT_FORMAT);
|
||||
auto reflectionsBuffer = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
context->Clear(*reflectionsBuffer, Color::Black);
|
||||
|
||||
// Reflection Probes pass
|
||||
if (renderProbes)
|
||||
{
|
||||
PROFILE_GPU_CPU("Env Probes");
|
||||
|
||||
context->SetRenderTarget(*reflectionsBuffer);
|
||||
|
||||
// Sort probes by the radius
|
||||
Sorting::QuickSort(renderContext.List->EnvironmentProbes.Get(), (int32)renderContext.List->EnvironmentProbes.Count(), &sortProbes);
|
||||
|
||||
// TODO: don't render too far probes, check area of the screen and apply culling!
|
||||
|
||||
// Render all env probes
|
||||
for (int32 probeIndex = 0; probeIndex < probesCount; probeIndex++)
|
||||
{
|
||||
// Cache data
|
||||
auto probe = renderContext.List->EnvironmentProbes[probeIndex];
|
||||
if (!probe->HasProbeLoaded())
|
||||
continue;
|
||||
float probeRadius = probe->GetScaledRadius();
|
||||
Vector3 probePosition = probe->GetPosition();
|
||||
|
||||
// Get distance from view center to light center less radius (check if view is inside a sphere)
|
||||
const float sphereModelScale = 2.0f;
|
||||
float distance = ViewToCenterLessRadius(view, probePosition, probeRadius);
|
||||
bool isViewInside = distance < 0;
|
||||
|
||||
// Calculate world view projection matrix for the light sphere
|
||||
Matrix world, wvp, matrix;
|
||||
Matrix::Scaling(probeRadius * sphereModelScale, wvp);
|
||||
Matrix::Translation(probePosition, matrix);
|
||||
Matrix::Multiply(wvp, matrix, world);
|
||||
Matrix::Multiply(world, view.ViewProjection(), wvp);
|
||||
|
||||
// Pack probe properties buffer
|
||||
probe->SetupProbeData(&data.PData);
|
||||
Matrix::Transpose(wvp, data.WVP);
|
||||
|
||||
// Render reflections
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
context->BindSR(4, probe->GetProbe()->GetTexture());
|
||||
|
||||
context->SetState(isViewInside ? _psProbeInverted : _psProbeNormal);
|
||||
_sphereModel->Render(context);
|
||||
}
|
||||
|
||||
context->UnBindSR(4);
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
// Screen Space Reflections pass
|
||||
if (useSSR)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// Override light buffer with the reflections buffer
|
||||
context->SetRenderTarget(lightBuffer);
|
||||
context->Draw(reflectionsBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Combine reflections and light buffer (additive mode)
|
||||
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->SetState(_psCombinePass);
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
RenderTargetPool::Release(reflectionsBuffer);
|
||||
}
|
||||
76
Source/Engine/Renderer/ReflectionsPass.h
Normal file
76
Source/Engine/Renderer/ReflectionsPass.h
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
|
||||
// Reflections buffer format used for rendering env probes and screen space reflections
|
||||
#define REFLECTIONS_PASS_OUTPUT_FORMAT PixelFormat::R11G11B10_Float
|
||||
#define ENV_PROBES_RESOLUTION 128
|
||||
//#define ENV_PROBES_FORMAT PixelFormat::R11G11B10_Float
|
||||
#define ENV_PROBES_FORMAT PixelFormat::R8G8B8A8_UNorm
|
||||
|
||||
#define GENERATE_GF_CACHE 0
|
||||
#define PRE_INTEGRATED_GF_ASSET_NAME TEXT("Engine/Textures/PreIntegratedGF")
|
||||
|
||||
/// <summary>
|
||||
/// Reflections rendering service
|
||||
/// </summary>
|
||||
class ReflectionsPass : public RendererPass<ReflectionsPass>
|
||||
{
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct Data {
|
||||
ProbeData PData;
|
||||
Matrix WVP;
|
||||
GBufferData GBuffer;
|
||||
});
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psProbeNormal;
|
||||
GPUPipelineState* _psProbeInverted;
|
||||
GPUPipelineState* _psCombinePass;
|
||||
|
||||
AssetReference<Model> _sphereModel;
|
||||
AssetReference<Texture> _preIntegratedGF;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
ReflectionsPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Perform reflections pass rendering for the input task.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="lightBuffer">The light buffer.</param>
|
||||
void Render(RenderContext& renderContext, GPUTextureView* lightBuffer);
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psProbeNormal->ReleaseGPU();
|
||||
_psProbeInverted->ReleaseGPU();
|
||||
_psCombinePass->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
744
Source/Engine/Renderer/RenderList.cpp
Normal file
744
Source/Engine/Renderer/RenderList.cpp
Normal file
@@ -0,0 +1,744 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "RenderList.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Graphics/Materials/IMaterial.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/PostProcessBase.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Profiler/Profiler.h"
|
||||
#include "Engine/Content/Assets/CubeTexture.h"
|
||||
#include "Engine/Level/Scene/Lightmap.h"
|
||||
#include "Engine/Level/Actors/PostFxVolume.h"
|
||||
|
||||
// Amount of bits to use for draw calls batches hash key
|
||||
#define USE_BATCH_KEY_MASK 0
|
||||
#define BATCH_KEY_BITS 32
|
||||
#define BATCH_KEY_MASK ((1 << BATCH_KEY_BITS) - 1)
|
||||
|
||||
namespace
|
||||
{
|
||||
// Cached data for the draw calls sorting
|
||||
Array<uint64> SortingKeys[2];
|
||||
Array<int32> SortingIndices;
|
||||
Array<RenderList*> FreeRenderList;
|
||||
}
|
||||
|
||||
#define PREPARE_CACHE(list) (list).Clear(); (list).Resize(listSize)
|
||||
|
||||
void RendererDirectionalLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const
|
||||
{
|
||||
data->SpotAngles.X = -2.0f;
|
||||
data->SpotAngles.Y = 1.0f;
|
||||
data->SourceRadius = 0;
|
||||
data->SourceLength = 0;
|
||||
data->Color = Color;
|
||||
data->MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
|
||||
data->Position = Vector3::Zero;
|
||||
data->CastShadows = useShadow ? 1.0f : 0.0f;
|
||||
data->Direction = -Direction;
|
||||
data->Radius = 0;
|
||||
data->FalloffExponent = 0;
|
||||
data->InverseSquared = 0;
|
||||
data->RadiusInv = 0;
|
||||
}
|
||||
|
||||
void RendererSpotLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const
|
||||
{
|
||||
data->SpotAngles.X = CosOuterCone;
|
||||
data->SpotAngles.Y = InvCosConeDifference;
|
||||
data->SourceRadius = SourceRadius;
|
||||
data->SourceLength = 0.0f;
|
||||
data->Color = Color;
|
||||
data->MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
|
||||
data->Position = Position;
|
||||
data->CastShadows = useShadow ? 1.0f : 0.0f;
|
||||
data->Direction = Direction;
|
||||
data->Radius = Radius;
|
||||
data->FalloffExponent = FallOffExponent;
|
||||
data->InverseSquared = UseInverseSquaredFalloff ? 1.0f : 0.0f;
|
||||
data->RadiusInv = 1.0f / Radius;
|
||||
}
|
||||
|
||||
void RendererPointLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const
|
||||
{
|
||||
data->SpotAngles.X = -2.0f;
|
||||
data->SpotAngles.Y = 1.0f;
|
||||
data->SourceRadius = SourceRadius;
|
||||
data->SourceLength = SourceLength;
|
||||
data->Color = Color;
|
||||
data->MinRoughness = Math::Max(MinRoughness, MIN_ROUGHNESS);
|
||||
data->Position = Position;
|
||||
data->CastShadows = useShadow ? 1.0f : 0.0f;
|
||||
data->Direction = Direction;
|
||||
data->Radius = Radius;
|
||||
data->FalloffExponent = FallOffExponent;
|
||||
data->InverseSquared = UseInverseSquaredFalloff ? 1.0f : 0.0f;
|
||||
data->RadiusInv = 1.0f / Radius;
|
||||
}
|
||||
|
||||
void RendererSkyLightData::SetupLightData(LightData* data, const RenderView& view, bool useShadow) const
|
||||
{
|
||||
data->SpotAngles.X = AdditiveColor.X;
|
||||
data->SpotAngles.Y = AdditiveColor.Y;
|
||||
data->SourceRadius = AdditiveColor.Z;
|
||||
data->SourceLength = Image ? Image->StreamingTexture()->TotalMipLevels() - 2.0f : 0.0f;
|
||||
data->Color = Color;
|
||||
data->MinRoughness = MIN_ROUGHNESS;
|
||||
data->Position = Position;
|
||||
data->CastShadows = useShadow ? 1.0f : 0.0f;
|
||||
data->Direction = Vector3::Forward;
|
||||
data->Radius = Radius;
|
||||
data->FalloffExponent = 0;
|
||||
data->InverseSquared = 0;
|
||||
data->RadiusInv = 1.0f / Radius;
|
||||
}
|
||||
|
||||
RenderList* RenderList::GetFromPool()
|
||||
{
|
||||
if (FreeRenderList.HasItems())
|
||||
{
|
||||
const auto result = FreeRenderList.Last();
|
||||
FreeRenderList.RemoveLast();
|
||||
return result;
|
||||
}
|
||||
|
||||
return New<RenderList>();
|
||||
}
|
||||
|
||||
void RenderList::ReturnToPool(RenderList* cache)
|
||||
{
|
||||
if (!cache)
|
||||
return;
|
||||
|
||||
ASSERT(!FreeRenderList.Contains(cache));
|
||||
FreeRenderList.Add(cache);
|
||||
cache->Clear();
|
||||
}
|
||||
|
||||
void RenderList::CleanupCache()
|
||||
{
|
||||
// Don't call it during rendering (data may be already in use)
|
||||
ASSERT(GPUDevice::Instance == nullptr || GPUDevice::Instance->CurrentTask == nullptr);
|
||||
|
||||
SortingKeys[0].Resize(0);
|
||||
SortingKeys[1].Resize(0);
|
||||
SortingIndices.Resize(0);
|
||||
FreeRenderList.ClearDelete();
|
||||
}
|
||||
|
||||
bool RenderList::BlendableSettings::operator<(const BlendableSettings& other) const
|
||||
{
|
||||
// Sort by higher priority
|
||||
if (Priority != other.Priority)
|
||||
return Priority < other.Priority;
|
||||
|
||||
// Sort by lower size
|
||||
return other.VolumeSizeSqr < VolumeSizeSqr;
|
||||
}
|
||||
|
||||
void RenderList::AddSettingsBlend(IPostFxSettingsProvider* provider, float weight, int32 priority, float volumeSizeSqr)
|
||||
{
|
||||
BlendableSettings blend;
|
||||
blend.Provider = provider;
|
||||
blend.Weight = weight;
|
||||
blend.Priority = priority;
|
||||
blend.VolumeSizeSqr = volumeSizeSqr;
|
||||
Blendable.Add(blend);
|
||||
}
|
||||
|
||||
void RenderList::BlendSettings()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
Sorting::QuickSort(Blendable.Get(), Blendable.Count());
|
||||
|
||||
for (auto& b : Blendable)
|
||||
{
|
||||
b.Provider->Blend(Settings, b.Weight);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderList::RunPostFxPass(GPUContext* context, RenderContext& renderContext, MaterialPostFxLocation locationA, PostProcessEffectLocation locationB, GPUTexture*& inputOutput)
|
||||
{
|
||||
// Note: during this stage engine is using additive rendering to the light buffer (given as inputOutput parameter).
|
||||
// Materials PostFx and Custom PostFx prefer sampling the input texture while rendering to the output.
|
||||
// So we need to allocate a temporary render target (or reuse from cache) and use it as a ping pong buffer.
|
||||
|
||||
bool skipPass = true;
|
||||
bool needTempTarget = true;
|
||||
for (int32 i = 0; i < Settings.PostFxMaterials.Materials.Count(); i++)
|
||||
{
|
||||
const auto material = Settings.PostFxMaterials.Materials[i].Get();
|
||||
if (material && material->IsReady() && material->IsPostFx() && material->GetInfo().PostFxLocation == locationA)
|
||||
{
|
||||
skipPass = false;
|
||||
needTempTarget = true;
|
||||
}
|
||||
}
|
||||
if (renderContext.View.Flags & ViewFlags::CustomPostProcess)
|
||||
{
|
||||
for (int32 i = 0; i < renderContext.List->PostFx.Count(); i++)
|
||||
{
|
||||
const auto fx = renderContext.List->PostFx[i];
|
||||
if (fx->IsReady() && fx->GetLocation() == locationB)
|
||||
{
|
||||
skipPass = false;
|
||||
needTempTarget |= !fx->GetUseSingleTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (skipPass)
|
||||
return;
|
||||
|
||||
auto tempDesc = inputOutput->GetDescription();
|
||||
auto temp = needTempTarget ? RenderTargetPool::Get(tempDesc) : nullptr;
|
||||
|
||||
auto input = inputOutput;
|
||||
auto output = temp;
|
||||
|
||||
context->ResetRenderTarget();
|
||||
|
||||
MaterialBase::BindParameters bindParams(context, renderContext);
|
||||
for (int32 i = 0; i < Settings.PostFxMaterials.Materials.Count(); i++)
|
||||
{
|
||||
auto material = Settings.PostFxMaterials.Materials[i].Get();
|
||||
if (material && material->IsReady() && material->IsPostFx() && material->GetInfo().PostFxLocation == locationA)
|
||||
{
|
||||
ASSERT(needTempTarget);
|
||||
context->SetRenderTarget(*output);
|
||||
bindParams.Input = *input;
|
||||
material->Bind(bindParams);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
Swap(output, input);
|
||||
}
|
||||
}
|
||||
if (renderContext.View.Flags & ViewFlags::CustomPostProcess)
|
||||
{
|
||||
for (int32 i = 0; i < renderContext.List->PostFx.Count(); i++)
|
||||
{
|
||||
auto fx = renderContext.List->PostFx[i];
|
||||
if (fx->IsReady() && fx->GetLocation() == locationB)
|
||||
{
|
||||
if (fx->GetUseSingleTarget())
|
||||
{
|
||||
fx->Render(renderContext, input, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(needTempTarget);
|
||||
fx->Render(renderContext, input, output);
|
||||
Swap(input, output);
|
||||
}
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputOutput = input;
|
||||
|
||||
if (needTempTarget)
|
||||
RenderTargetPool::Release(output);
|
||||
}
|
||||
|
||||
void RenderList::RunMaterialPostFxPass(GPUContext* context, RenderContext& renderContext, MaterialPostFxLocation location, GPUTexture*& input, GPUTexture*& output)
|
||||
{
|
||||
MaterialBase::BindParameters bindParams(context, renderContext);
|
||||
for (int32 i = 0; i < Settings.PostFxMaterials.Materials.Count(); i++)
|
||||
{
|
||||
auto material = Settings.PostFxMaterials.Materials[i].Get();
|
||||
if (material && material->IsReady() && material->IsPostFx() && material->GetInfo().PostFxLocation == location)
|
||||
{
|
||||
context->SetRenderTarget(*output);
|
||||
bindParams.Input = *input;
|
||||
material->Bind(bindParams);
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
Swap(output, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderList::RunCustomPostFxPass(GPUContext* context, RenderContext& renderContext, PostProcessEffectLocation location, GPUTexture*& input, GPUTexture*& output)
|
||||
{
|
||||
if (!(renderContext.View.Flags & ViewFlags::CustomPostProcess))
|
||||
return;
|
||||
|
||||
for (int32 i = 0; i < renderContext.List->PostFx.Count(); i++)
|
||||
{
|
||||
auto fx = renderContext.List->PostFx[i];
|
||||
if (fx->IsReady() && fx->GetLocation() == location)
|
||||
{
|
||||
if (fx->GetUseSingleTarget())
|
||||
{
|
||||
fx->Render(renderContext, input, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
fx->Render(renderContext, input, output);
|
||||
Swap(input, output);
|
||||
}
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderList::HasAnyPostAA(RenderContext& renderContext) const
|
||||
{
|
||||
for (int32 i = 0; i < Settings.PostFxMaterials.Materials.Count(); i++)
|
||||
{
|
||||
auto material = Settings.PostFxMaterials.Materials[i].Get();
|
||||
if (material && material->IsReady() && material->IsPostFx() && material->GetInfo().PostFxLocation == MaterialPostFxLocation::AfterAntiAliasingPass)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (renderContext.View.Flags & ViewFlags::CustomPostProcess)
|
||||
{
|
||||
for (int32 i = 0; i < renderContext.List->PostFx.Count(); i++)
|
||||
{
|
||||
auto fx = renderContext.List->PostFx[i];
|
||||
if (fx->IsReady() && fx->GetLocation() == PostProcessEffectLocation::AfterAntiAliasingPass)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderList::RenderList(const SpawnParams& params)
|
||||
: PersistentScriptingObject(params)
|
||||
, DirectionalLights(4)
|
||||
, PointLights(32)
|
||||
, SpotLights(32)
|
||||
, SkyLights(4)
|
||||
, EnvironmentProbes(32)
|
||||
, Decals(64)
|
||||
, Sky(nullptr)
|
||||
, AtmosphericFog(nullptr)
|
||||
, Fog(nullptr)
|
||||
, Blendable(32)
|
||||
, _instanceBuffer(1024 * sizeof(InstanceData), sizeof(InstanceData), TEXT("Instance Buffer"))
|
||||
{
|
||||
}
|
||||
|
||||
void RenderList::Init(RenderContext& renderContext)
|
||||
{
|
||||
renderContext.View.Frustum.GetCorners(FrustumCornersWs);
|
||||
Vector3::Transform(FrustumCornersWs, renderContext.View.View, FrustumCornersVs, 8);
|
||||
}
|
||||
|
||||
void RenderList::Clear()
|
||||
{
|
||||
DrawCalls.Clear();
|
||||
for (auto& list : DrawCallsLists)
|
||||
list.Clear();
|
||||
PointLights.Clear();
|
||||
SpotLights.Clear();
|
||||
SkyLights.Clear();
|
||||
DirectionalLights.Clear();
|
||||
EnvironmentProbes.Clear();
|
||||
Decals.Clear();
|
||||
Sky = nullptr;
|
||||
AtmosphericFog = nullptr;
|
||||
Fog = nullptr;
|
||||
PostFx.Clear();
|
||||
Settings = PostProcessSettings();
|
||||
Blendable.Clear();
|
||||
_instanceBuffer.Clear();
|
||||
}
|
||||
|
||||
void RenderList::AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, DrawCall& drawCall, bool receivesDecals)
|
||||
{
|
||||
ASSERT_LOW_LAYER(drawCall.Geometry.IndexBuffer);
|
||||
|
||||
// Mix object mask with material mask
|
||||
const auto mask = (DrawPass)(drawModes & drawCall.Material->GetDrawModes());
|
||||
if (mask == DrawPass::None)
|
||||
return;
|
||||
|
||||
// Append draw call data
|
||||
const int32 index = DrawCalls.Count();
|
||||
DrawCalls.Add(drawCall);
|
||||
|
||||
// Add draw call to proper draw lists
|
||||
if (mask & DrawPass::Depth)
|
||||
{
|
||||
DrawCallsLists[(int32)DrawCallsListType::Depth].Indices.Add(index);
|
||||
}
|
||||
if (mask & DrawPass::GBuffer)
|
||||
{
|
||||
if (receivesDecals)
|
||||
DrawCallsLists[(int32)DrawCallsListType::GBuffer].Indices.Add(index);
|
||||
else
|
||||
DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].Indices.Add(index);
|
||||
}
|
||||
if (mask & DrawPass::Forward)
|
||||
{
|
||||
DrawCallsLists[(int32)DrawCallsListType::Forward].Indices.Add(index);
|
||||
}
|
||||
if (mask & DrawPass::Distortion)
|
||||
{
|
||||
DrawCallsLists[(int32)DrawCallsListType::Distortion].Indices.Add(index);
|
||||
}
|
||||
if (mask & DrawPass::MotionVectors && (staticFlags & StaticFlags::Transform) == 0)
|
||||
{
|
||||
DrawCallsLists[(int32)DrawCallsListType::MotionVectors].Indices.Add(index);
|
||||
}
|
||||
}
|
||||
|
||||
uint32 ComputeDistance(float distance)
|
||||
{
|
||||
// Compute sort key (http://aras-p.info/blog/2014/01/16/rough-sorting-by-depth/)
|
||||
uint32 distanceI = *((uint32*)&distance);
|
||||
return ((uint32)(-(int32)(distanceI >> 31)) | 0x80000000) ^ distanceI;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Radix Sort algorithm (uses temporary keys collection).
|
||||
/// </summary>
|
||||
/// <param name="inputKeys">The data pointer to the input sorting keys array. When this method completes it contains a pointer to the original data or the temporary depending on the algorithm passes count. Use it as a results container.</param>
|
||||
/// <param name="inputValues">The data pointer to the input values array. When this method completes it contains a pointer to the original data or the temporary depending on the algorithm passes count. Use it as a results container.</param>
|
||||
/// <param name="tmpKeys">The data pointer to the temporary sorting keys array.</param>
|
||||
/// <param name="tmpValues">The data pointer to the temporary values array.</param>
|
||||
/// <param name="count">The elements count.</param>
|
||||
template<typename T, typename U>
|
||||
static void RadixSort(T*& inputKeys, U* inputValues, T* tmpKeys, U* tmpValues, int32 count)
|
||||
{
|
||||
// Based on: https://github.com/bkaradzic/bx/blob/master/include/bx/inline/sort.inl
|
||||
enum
|
||||
{
|
||||
RADIXSORT_BITS = 11,
|
||||
RADIXSORT_HISTOGRAM_SIZE = 1 << RADIXSORT_BITS,
|
||||
RADIXSORT_BIT_MASK = RADIXSORT_HISTOGRAM_SIZE - 1
|
||||
};
|
||||
|
||||
if (count < 2)
|
||||
return;
|
||||
|
||||
T* keys = inputKeys;
|
||||
T* tempKeys = tmpKeys;
|
||||
U* values = inputValues;
|
||||
U* tempValues = tmpValues;
|
||||
|
||||
uint32 histogram[RADIXSORT_HISTOGRAM_SIZE];
|
||||
uint16 shift = 0;
|
||||
int32 pass = 0;
|
||||
for (; pass < 6; pass++)
|
||||
{
|
||||
Platform::MemoryClear(histogram, sizeof(uint32) * RADIXSORT_HISTOGRAM_SIZE);
|
||||
|
||||
bool sorted = true;
|
||||
T key = keys[0];
|
||||
T prevKey = key;
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
key = keys[i];
|
||||
const uint16 index = (key >> shift) & RADIXSORT_BIT_MASK;
|
||||
++histogram[index];
|
||||
sorted &= prevKey <= key;
|
||||
prevKey = key;
|
||||
}
|
||||
|
||||
if (sorted)
|
||||
{
|
||||
goto end;
|
||||
}
|
||||
|
||||
uint32 offset = 0;
|
||||
for (int32 i = 0; i < RADIXSORT_HISTOGRAM_SIZE; ++i)
|
||||
{
|
||||
const uint32 cnt = histogram[i];
|
||||
histogram[i] = offset;
|
||||
offset += cnt;
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
const T k = keys[i];
|
||||
const uint16 index = (k >> shift) & RADIXSORT_BIT_MASK;
|
||||
const uint32 dest = histogram[index]++;
|
||||
tempKeys[dest] = k;
|
||||
tempValues[dest] = values[i];
|
||||
}
|
||||
|
||||
T* const swapKeys = tempKeys;
|
||||
tempKeys = keys;
|
||||
keys = swapKeys;
|
||||
|
||||
U* const swapValues = tempValues;
|
||||
tempValues = values;
|
||||
values = swapValues;
|
||||
|
||||
shift += RADIXSORT_BITS;
|
||||
}
|
||||
|
||||
end:
|
||||
if (pass & 1)
|
||||
{
|
||||
// Use temporary keys as a result
|
||||
inputKeys = tmpKeys;
|
||||
|
||||
#if 0
|
||||
// Use temporary values as a result
|
||||
inputValues = tmpValues;
|
||||
#else
|
||||
// Odd number of passes needs to do copy to the destination
|
||||
Platform::MemoryCopy(inputValues, tmpValues, sizeof(U) * count);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this draw call be batched together with the other one.
|
||||
/// </summary>
|
||||
/// <param name="a">The first draw call.</param>
|
||||
/// <param name="b">The second draw call.</param>
|
||||
/// <returns>True if can merge them, otherwise false.</returns>
|
||||
FORCE_INLINE bool CanBatchWith(const DrawCall& a, const DrawCall& b)
|
||||
{
|
||||
return Platform::MemoryCompare(&a.Geometry, &b.Geometry, sizeof(a.Geometry)) == 0 &&
|
||||
a.Material == b.Material &&
|
||||
a.Lightmap == b.Lightmap &&
|
||||
// TODO: add batch.CanBatch flag computed in AddDrawCall to remove those checks here for Skinning and IndirectDrawArgs
|
||||
a.Skinning == nullptr &&
|
||||
b.Skinning == nullptr &&
|
||||
a.IndirectArgsBuffer == nullptr &&
|
||||
b.IndirectArgsBuffer == nullptr &&
|
||||
a.WorldDeterminantSign == b.WorldDeterminantSign &&
|
||||
a.Material->CanUseInstancing();
|
||||
}
|
||||
|
||||
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
const int32 listSize = (int32)list.Indices.Count();
|
||||
const Plane plane(renderContext.View.Position, renderContext.View.Direction);
|
||||
|
||||
// Peek shared memory
|
||||
PREPARE_CACHE(SortingKeys[0]);
|
||||
PREPARE_CACHE(SortingKeys[1]);
|
||||
PREPARE_CACHE(SortingIndices);
|
||||
uint64* sortedKeys = SortingKeys[0].Get();
|
||||
|
||||
// Generate sort keys (by depth) and batch keys (higher bits)
|
||||
const uint32 sortKeyXor = reverseDistance ? MAX_uint32 : 0;
|
||||
for (int32 i = 0; i < listSize; i++)
|
||||
{
|
||||
auto& drawCall = DrawCalls[list.Indices[i]];
|
||||
const auto distance = CollisionsHelper::DistancePlanePoint(plane, drawCall.ObjectPosition);
|
||||
const uint32 sortKey = ComputeDistance(distance) ^ sortKeyXor;
|
||||
int32 batchKey = GetHash(drawCall.Geometry.IndexBuffer);
|
||||
batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[0]);
|
||||
batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[1]);
|
||||
batchKey = (batchKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[2]);
|
||||
batchKey = (batchKey * 397) ^ GetHash(drawCall.Material);
|
||||
batchKey = (batchKey * 397) ^ GetHash(drawCall.Lightmap);
|
||||
batchKey += (int32)(471 * drawCall.WorldDeterminantSign);
|
||||
#if USE_BATCH_KEY_MASK
|
||||
const uint32 batchHashKey = (uint32)batchKey & BATCH_KEY_MASK;
|
||||
#else
|
||||
const uint32 batchHashKey = (uint32)batchKey;
|
||||
#endif
|
||||
sortedKeys[i] = (uint64)batchHashKey << 32 | (uint64)sortKey;
|
||||
}
|
||||
|
||||
// Sort draw calls indices
|
||||
RadixSort(sortedKeys, list.Indices.Get(), SortingKeys[1].Get(), SortingIndices.Get(), listSize);
|
||||
|
||||
// Perform draw calls batching
|
||||
list.Batches.Clear();
|
||||
for (int32 i = 0; i < listSize;)
|
||||
{
|
||||
const auto& drawCall = DrawCalls[list.Indices[i]];
|
||||
int32 batchSize = 1;
|
||||
int32 instanceCount = drawCall.InstanceCount;
|
||||
|
||||
// Check the following draw calls to merge them (using instancing)
|
||||
for (int32 j = i + 1; j < listSize; j++)
|
||||
{
|
||||
const auto& other = DrawCalls[list.Indices[j]];
|
||||
if (!CanBatchWith(drawCall, other))
|
||||
break;
|
||||
|
||||
batchSize++;
|
||||
instanceCount += other.InstanceCount;
|
||||
}
|
||||
|
||||
DrawBatch batch;
|
||||
batch.SortKey = sortedKeys[i] & MAX_uint32;
|
||||
batch.StartIndex = i;
|
||||
batch.BatchSize = batchSize;
|
||||
batch.InstanceCount = instanceCount;
|
||||
list.Batches.Add(batch);
|
||||
|
||||
i += batchSize;
|
||||
}
|
||||
|
||||
// Sort draw calls batches by depth
|
||||
Sorting::QuickSort(list.Batches.Get(), list.Batches.Count());
|
||||
}
|
||||
|
||||
bool CanUseInstancing(DrawPass pass)
|
||||
{
|
||||
return pass == DrawPass::GBuffer || pass == DrawPass::Depth;
|
||||
}
|
||||
|
||||
void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list)
|
||||
{
|
||||
// Skip if no rendering to perform
|
||||
if (list.Batches.IsEmpty())
|
||||
return;
|
||||
|
||||
PROFILE_GPU_CPU("Drawing");
|
||||
|
||||
const int32 batchesSize = list.Batches.Count();
|
||||
const auto context = GPUDevice::Instance->GetMainContext();
|
||||
bool useInstancing = list.CanUseInstancing && CanUseInstancing(renderContext.View.Pass) && GPUDevice::Instance->Limits.HasInstancing;
|
||||
|
||||
// Clear SR slots to prevent any resources binding issues (leftovers from the previous passes)
|
||||
context->ResetSR();
|
||||
|
||||
// Prepare instance buffer
|
||||
if (useInstancing)
|
||||
{
|
||||
// Prepare buffer memory
|
||||
int32 batchesCount = 0;
|
||||
for (int32 i = 0; i < batchesSize; i++)
|
||||
{
|
||||
auto& batch = list.Batches[i];
|
||||
if (batch.BatchSize > 1)
|
||||
{
|
||||
batchesCount += batch.BatchSize;
|
||||
}
|
||||
}
|
||||
if (batchesCount == 0)
|
||||
{
|
||||
// Faster path if none of the draw batches requires instancing
|
||||
useInstancing = false;
|
||||
goto DRAW;
|
||||
}
|
||||
_instanceBuffer.Clear();
|
||||
_instanceBuffer.Data.Resize(batchesCount * sizeof(InstanceData));
|
||||
auto instanceData = (InstanceData*)_instanceBuffer.Data.Get();
|
||||
|
||||
// Write to instance buffer
|
||||
for (int32 i = 0; i < batchesSize; i++)
|
||||
{
|
||||
auto& batch = list.Batches[i];
|
||||
if (batch.BatchSize > 1)
|
||||
{
|
||||
for (int32 j = 0; j < batch.BatchSize; j++)
|
||||
{
|
||||
auto& drawCall = DrawCalls[list.Indices[batch.StartIndex + j]];
|
||||
instanceData->InstanceOrigin = Vector4(drawCall.World.M41, drawCall.World.M42, drawCall.World.M43, drawCall.PerInstanceRandom);
|
||||
instanceData->InstanceTransform1 = Vector4(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13, drawCall.LODDitherFactor);
|
||||
instanceData->InstanceTransform2 = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23);
|
||||
instanceData->InstanceTransform3 = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33);
|
||||
instanceData->InstanceLightmapArea = Half4(drawCall.LightmapUVsArea);
|
||||
instanceData++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upload data
|
||||
_instanceBuffer.Flush(context);
|
||||
}
|
||||
|
||||
DRAW:
|
||||
|
||||
// Execute draw calls
|
||||
MaterialBase::BindParameters bindParams(context, renderContext);
|
||||
if (useInstancing)
|
||||
{
|
||||
int32 instanceBufferOffset = 0;
|
||||
GPUBuffer* vb[4];
|
||||
uint32 vbOffsets[4];
|
||||
for (int32 i = 0; i < batchesSize; i++)
|
||||
{
|
||||
auto& batch = list.Batches[i];
|
||||
auto& drawCall = DrawCalls[list.Indices[batch.StartIndex]];
|
||||
|
||||
int32 vbCount = 0;
|
||||
while (drawCall.Geometry.VertexBuffers[vbCount] && vbCount < ARRAY_COUNT(drawCall.Geometry.VertexBuffers))
|
||||
{
|
||||
vb[vbCount] = drawCall.Geometry.VertexBuffers[vbCount];
|
||||
vbOffsets[vbCount] = drawCall.Geometry.VertexBuffersOffsets[vbCount];
|
||||
vbCount++;
|
||||
}
|
||||
for (int32 j = vbCount; j < ARRAY_COUNT(drawCall.Geometry.VertexBuffers); j++)
|
||||
{
|
||||
vb[vbCount] = nullptr;
|
||||
vbOffsets[vbCount] = 0;
|
||||
}
|
||||
|
||||
bindParams.FirstDrawCall = &drawCall;
|
||||
bindParams.DrawCallsCount = batch.BatchSize;
|
||||
drawCall.Material->Bind(bindParams);
|
||||
|
||||
context->BindIB(drawCall.Geometry.IndexBuffer);
|
||||
|
||||
if (drawCall.IndirectArgsBuffer)
|
||||
{
|
||||
// No support for batching indirect draw calls
|
||||
ASSERT(batch.BatchSize == 1);
|
||||
|
||||
context->BindVB(ToSpan(vb, vbCount), vbOffsets);
|
||||
context->DrawIndexedInstancedIndirect(drawCall.IndirectArgsBuffer, drawCall.IndirectArgsOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (batch.BatchSize == 1)
|
||||
{
|
||||
context->BindVB(ToSpan(vb, vbCount), vbOffsets);
|
||||
context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, batch.InstanceCount, 0, 0, drawCall.Geometry.StartIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
vbCount = 3;
|
||||
vb[vbCount] = _instanceBuffer.GetBuffer();
|
||||
vbOffsets[vbCount] = 0;
|
||||
vbCount++;
|
||||
context->BindVB(ToSpan(vb, vbCount), vbOffsets);
|
||||
context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, batch.InstanceCount, instanceBufferOffset, 0, drawCall.Geometry.StartIndex);
|
||||
|
||||
instanceBufferOffset += batch.BatchSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bindParams.DrawCallsCount = 1;
|
||||
for (int32 i = 0; i < batchesSize; i++)
|
||||
{
|
||||
auto& batch = list.Batches[i];
|
||||
|
||||
for (int32 j = 0; j < batch.BatchSize; j++)
|
||||
{
|
||||
auto& drawCall = DrawCalls[list.Indices[batch.StartIndex + j]];
|
||||
bindParams.FirstDrawCall = &drawCall;
|
||||
drawCall.Material->Bind(bindParams);
|
||||
|
||||
context->BindIB(drawCall.Geometry.IndexBuffer);
|
||||
context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 3), drawCall.Geometry.VertexBuffersOffsets);
|
||||
|
||||
if (drawCall.IndirectArgsBuffer)
|
||||
{
|
||||
context->DrawIndexedInstancedIndirect(drawCall.IndirectArgsBuffer, drawCall.IndirectArgsOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
context->DrawIndexedInstanced(drawCall.Geometry.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Geometry.StartIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
477
Source/Engine/Renderer/RenderList.h
Normal file
477
Source/Engine/Renderer/RenderList.h
Normal file
@@ -0,0 +1,477 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Math/Half.h"
|
||||
#include "Engine/Graphics/PostProcessSettings.h"
|
||||
#include "Engine/Graphics/DynamicBuffer.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "DrawCall.h"
|
||||
|
||||
enum class StaticFlags;
|
||||
class RenderBuffers;
|
||||
class LightWithShadow;
|
||||
class IPostFxSettingsProvider;
|
||||
class CubeTexture;
|
||||
class PostProcessBase;
|
||||
struct RenderContext;
|
||||
|
||||
struct RendererDirectionalLightData
|
||||
{
|
||||
Vector3 Position;
|
||||
float MinRoughness;
|
||||
|
||||
Vector3 Color;
|
||||
float ShadowsStrength;
|
||||
|
||||
Vector3 Direction;
|
||||
float ShadowsFadeDistance;
|
||||
|
||||
float ShadowsNormalOffsetScale;
|
||||
float ShadowsDepthBias;
|
||||
float ShadowsSharpness;
|
||||
float VolumetricScatteringIntensity;
|
||||
|
||||
int8 CastVolumetricShadow : 1;
|
||||
int8 RenderedVolumetricFog : 1;
|
||||
|
||||
float ShadowsDistance;
|
||||
int32 CascadeCount;
|
||||
float ContactShadowsLength;
|
||||
ShadowsCastingMode ShadowsMode;
|
||||
|
||||
void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const;
|
||||
};
|
||||
|
||||
struct RendererSpotLightData
|
||||
{
|
||||
Vector3 Position;
|
||||
float MinRoughness;
|
||||
|
||||
Vector3 Color;
|
||||
float ShadowsStrength;
|
||||
|
||||
Vector3 Direction;
|
||||
float ShadowsFadeDistance;
|
||||
|
||||
float ShadowsNormalOffsetScale;
|
||||
float ShadowsDepthBias;
|
||||
float ShadowsSharpness;
|
||||
float VolumetricScatteringIntensity;
|
||||
|
||||
float ShadowsDistance;
|
||||
float Radius;
|
||||
float FallOffExponent;
|
||||
float SourceRadius;
|
||||
|
||||
Vector3 UpVector;
|
||||
float OuterConeAngle;
|
||||
|
||||
float CosOuterCone;
|
||||
float InvCosConeDifference;
|
||||
float ContactShadowsLength;
|
||||
ShadowsCastingMode ShadowsMode;
|
||||
|
||||
int8 CastVolumetricShadow : 1;
|
||||
int8 RenderedVolumetricFog : 1;
|
||||
int8 UseInverseSquaredFalloff : 1;
|
||||
|
||||
GPUTexture* IESTexture;
|
||||
|
||||
void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const;
|
||||
};
|
||||
|
||||
struct RendererPointLightData
|
||||
{
|
||||
Vector3 Position;
|
||||
float MinRoughness;
|
||||
|
||||
Vector3 Color;
|
||||
float ShadowsStrength;
|
||||
|
||||
Vector3 Direction;
|
||||
float ShadowsFadeDistance;
|
||||
|
||||
float ShadowsNormalOffsetScale;
|
||||
float ShadowsDepthBias;
|
||||
float ShadowsSharpness;
|
||||
float VolumetricScatteringIntensity;
|
||||
|
||||
float ShadowsDistance;
|
||||
float Radius;
|
||||
float FallOffExponent;
|
||||
float SourceRadius;
|
||||
|
||||
float SourceLength;
|
||||
float ContactShadowsLength;
|
||||
ShadowsCastingMode ShadowsMode;
|
||||
|
||||
int8 CastVolumetricShadow : 1;
|
||||
int8 RenderedVolumetricFog : 1;
|
||||
int8 UseInverseSquaredFalloff : 1;
|
||||
|
||||
GPUTexture* IESTexture;
|
||||
|
||||
void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const;
|
||||
};
|
||||
|
||||
struct RendererSkyLightData
|
||||
{
|
||||
Vector3 Position;
|
||||
float VolumetricScatteringIntensity;
|
||||
|
||||
Vector3 Color;
|
||||
float Radius;
|
||||
|
||||
Vector3 AdditiveColor;
|
||||
|
||||
int8 CastVolumetricShadow : 1;
|
||||
int8 RenderedVolumetricFog : 1;
|
||||
|
||||
CubeTexture* Image;
|
||||
|
||||
void SetupLightData(LightData* data, const RenderView& view, bool useShadow) const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The draw calls list types.
|
||||
/// </summary>
|
||||
API_ENUM() enum class DrawCallsListType
|
||||
{
|
||||
/// <summary>
|
||||
/// Hardware depth rendering.
|
||||
/// </summary>
|
||||
Depth,
|
||||
|
||||
/// <summary>
|
||||
/// GBuffer rendering.
|
||||
/// </summary>
|
||||
GBuffer,
|
||||
|
||||
/// <summary>
|
||||
/// GBuffer rendering after decals.
|
||||
/// </summary>
|
||||
GBufferNoDecals,
|
||||
|
||||
/// <summary>
|
||||
/// Transparency rendering.
|
||||
/// </summary>
|
||||
Forward,
|
||||
|
||||
/// <summary>
|
||||
/// Distortion accumulation rendering.
|
||||
/// </summary>
|
||||
Distortion,
|
||||
|
||||
/// <summary>
|
||||
/// Motion vectors rendering.
|
||||
/// </summary>
|
||||
MotionVectors,
|
||||
|
||||
MAX,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Represents a patch of draw calls that can be submitted to rendering.
|
||||
/// </summary>
|
||||
struct DrawBatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Draw calls sorting key (shared by the all draw calls withing a patch).
|
||||
/// </summary>
|
||||
uint32 SortKey;
|
||||
|
||||
/// <summary>
|
||||
/// The first draw call index.
|
||||
/// </summary>
|
||||
int32 StartIndex;
|
||||
|
||||
/// <summary>
|
||||
/// A number of draw calls to be submitted at once.
|
||||
/// </summary>
|
||||
int32 BatchSize;
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of instances (sum from all draw calls in this batch).
|
||||
/// </summary>
|
||||
int32 InstanceCount;
|
||||
|
||||
bool operator<(const DrawBatch& other) const
|
||||
{
|
||||
return SortKey < other.SortKey;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Represents a list of draw calls.
|
||||
/// </summary>
|
||||
struct DrawCallsList
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of draw calls indices to render.
|
||||
/// </summary>
|
||||
Array<int32> Indices;
|
||||
|
||||
/// <summary>
|
||||
/// The draw calls batches (for instancing).
|
||||
/// </summary>
|
||||
Array<DrawBatch> Batches;
|
||||
|
||||
/// <summary>
|
||||
/// True if draw calls batches list can be rendered using hardware instancing, otherwise false.
|
||||
/// </summary>
|
||||
bool CanUseInstancing;
|
||||
|
||||
void Clear()
|
||||
{
|
||||
Indices.Clear();
|
||||
Batches.Clear();
|
||||
CanUseInstancing = true;
|
||||
}
|
||||
|
||||
bool IsEmpty() const
|
||||
{
|
||||
return Indices.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Rendering cache container object for the draw calls collecting, sorting and executing.
|
||||
/// </summary>
|
||||
API_CLASS(Sealed) class FLAXENGINE_API RenderList : public PersistentScriptingObject
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(RenderList);
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Allocates the new renderer list object or reuses already allocated one.
|
||||
/// </summary>
|
||||
/// <returns>The cache object.</returns>
|
||||
API_FUNCTION() static RenderList* GetFromPool();
|
||||
|
||||
/// <summary>
|
||||
/// Frees the list back to the pool.
|
||||
/// </summary>
|
||||
/// <param name="cache">The cache.</param>
|
||||
API_FUNCTION() static void ReturnToPool(RenderList* cache);
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups the static data cache used to accelerate draw calls sorting. Use it to reduce memory pressure.
|
||||
/// </summary>
|
||||
static void CleanupCache();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Draw calls list (for all draw passes).
|
||||
/// </summary>
|
||||
Array<DrawCall> DrawCalls;
|
||||
|
||||
/// <summary>
|
||||
/// The draw calls lists. Each for the separate draw pass.
|
||||
/// </summary>
|
||||
DrawCallsList DrawCallsLists[(int32)DrawCallsListType::MAX];
|
||||
|
||||
/// <summary>
|
||||
/// Light pass members - directional lights
|
||||
/// </summary>
|
||||
Array<RendererDirectionalLightData> DirectionalLights;
|
||||
|
||||
/// <summary>
|
||||
/// Light pass members - point lights
|
||||
/// </summary>
|
||||
Array<RendererPointLightData> PointLights;
|
||||
|
||||
/// <summary>
|
||||
/// Light pass members - spot lights
|
||||
/// </summary>
|
||||
Array<RendererSpotLightData> SpotLights;
|
||||
|
||||
/// <summary>
|
||||
/// Light pass members - sky lights
|
||||
/// </summary>
|
||||
Array<RendererSkyLightData> SkyLights;
|
||||
|
||||
/// <summary>
|
||||
/// Environment probes to use for rendering reflections
|
||||
/// </summary>
|
||||
Array<EnvironmentProbe*> EnvironmentProbes;
|
||||
|
||||
/// <summary>
|
||||
/// Decals registered for the rendering.
|
||||
/// </summary>
|
||||
Array<Decal*> Decals;
|
||||
|
||||
/// <summary>
|
||||
/// Sky/skybox renderer proxy to use (only one per frame)
|
||||
/// </summary>
|
||||
ISkyRenderer* Sky;
|
||||
|
||||
/// <summary>
|
||||
/// Atmospheric fog renderer proxy to use (only one per frame)
|
||||
/// </summary>
|
||||
IAtmosphericFogRenderer* AtmosphericFog;
|
||||
|
||||
/// <summary>
|
||||
/// Fog renderer proxy to use (only one per frame)
|
||||
/// </summary>
|
||||
IFogRenderer* Fog;
|
||||
|
||||
/// <summary>
|
||||
/// Post effect to render (TEMPORARY! cleared after and before rendering).
|
||||
/// </summary>
|
||||
Array<PostProcessBase*> PostFx;
|
||||
|
||||
/// <summary>
|
||||
/// The post process settings.
|
||||
/// </summary>
|
||||
PostProcessSettings Settings;
|
||||
|
||||
struct FLAXENGINE_API BlendableSettings
|
||||
{
|
||||
IPostFxSettingsProvider* Provider;
|
||||
float Weight;
|
||||
int32 Priority;
|
||||
float VolumeSizeSqr;
|
||||
|
||||
bool operator<(const BlendableSettings& other) const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The blendable postFx volumes collected during frame draw calls gather pass.
|
||||
/// </summary>
|
||||
Array<BlendableSettings> Blendable;
|
||||
|
||||
void AddSettingsBlend(IPostFxSettingsProvider* provider, float weight, int32 priority, float volumeSizeSqr);
|
||||
|
||||
/// <summary>
|
||||
/// Camera frustum corners in World Space
|
||||
/// </summary>
|
||||
Vector3 FrustumCornersWs[8];
|
||||
|
||||
/// <summary>
|
||||
/// Camera frustum corners in View Space
|
||||
/// </summary>
|
||||
Vector3 FrustumCornersVs[8];
|
||||
|
||||
private:
|
||||
|
||||
/// <summary>
|
||||
/// Represents data per instance element used for instanced rendering.
|
||||
/// </summary>
|
||||
struct InstanceData
|
||||
{
|
||||
Vector4 InstanceOrigin; // .w contains PerInstanceRandom
|
||||
Vector4 InstanceTransform1; // .w contains LODDitherFactor
|
||||
Vector3 InstanceTransform2;
|
||||
Vector3 InstanceTransform3;
|
||||
Half4 InstanceLightmapArea;
|
||||
};
|
||||
|
||||
DynamicVertexBuffer _instanceBuffer;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Blends the postprocessing settings into the final options.
|
||||
/// </summary>
|
||||
void BlendSettings();
|
||||
|
||||
/// <summary>
|
||||
/// Runs the post fx materials pass. Uses input/output buffer to render all materials. Uses temporary render target as a ping pong buffer if required (the same format and description).
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="locationA">The material postFx location.</param>
|
||||
/// <param name="locationB">The custom postFx location.</param>
|
||||
/// <param name="inputOutput">The input and output texture.</param>
|
||||
void RunPostFxPass(GPUContext* context, RenderContext& renderContext, MaterialPostFxLocation locationA, PostProcessEffectLocation locationB, GPUTexture*& inputOutput);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the material post fx pass. Uses input and output buffers as a ping pong to render all materials.
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="location">The material postFx location.</param>
|
||||
/// <param name="input">The input texture.</param>
|
||||
/// <param name="output">The output texture.</param>
|
||||
void RunMaterialPostFxPass(GPUContext* context, RenderContext& renderContext, MaterialPostFxLocation location, GPUTexture*& input, GPUTexture*& output);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the custom post fx pass. Uses input and output buffers as a ping pong to render all effects.
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="location">The custom postFx location.</param>
|
||||
/// <param name="input">The input texture.</param>
|
||||
/// <param name="output">The output texture.</param>
|
||||
void RunCustomPostFxPass(GPUContext* context, RenderContext& renderContext, PostProcessEffectLocation location, GPUTexture*& input, GPUTexture*& output);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether any Custom PostFx or Material PostFx has to be rendered after AA pass. Used to pick a faster rendering path by the frame rendering module.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <returns>True if render any postFx after AA, otherwise false.</returns>
|
||||
bool HasAnyPostAA(RenderContext& renderContext) const;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init cache for given task
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
void Init(RenderContext& renderContext);
|
||||
|
||||
/// <summary>
|
||||
/// Clear cached data
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Adds the draw call to the draw lists.
|
||||
/// </summary>
|
||||
/// <param name="drawModes">The object draw modes.</param>
|
||||
/// <param name="staticFlags">The object static flags.</param>
|
||||
/// <param name="drawCall">The draw call data.</param>
|
||||
/// <param name="receivesDecals">True if the rendered mesh can receive decals.</param>
|
||||
void AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, DrawCall& drawCall, bool receivesDecals);
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the collected draw calls list.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="reverseDistance">If set to <c>true</c> reverse draw call distance to the view. Results in back to front sorting.</param>
|
||||
/// <param name="listType">The collected draw calls list type.</param>
|
||||
API_FUNCTION() FORCE_INLINE void SortDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, bool reverseDistance, DrawCallsListType listType)
|
||||
{
|
||||
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the collected draw calls list.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="reverseDistance">If set to <c>true</c> reverse draw call distance to the view. Results in back to front sorting.</param>
|
||||
/// <param name="list">The collected draw calls list.</param>
|
||||
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the collected draw calls.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="listType">The collected draw calls list type.</param>
|
||||
API_FUNCTION() FORCE_INLINE void ExecuteDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, DrawCallsListType listType)
|
||||
{
|
||||
ExecuteDrawCalls(renderContext, DrawCallsLists[(int32)listType]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the collected draw calls.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="list">The collected draw calls list.</param>
|
||||
void ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list);
|
||||
};
|
||||
24
Source/Engine/Renderer/Renderer.Build.cs
Normal file
24
Source/Engine/Renderer/Renderer.Build.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using Flax.Build;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
/// <summary>
|
||||
/// 3D graphics rendering module.
|
||||
/// </summary>
|
||||
public class Renderer : EngineModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Setup(BuildOptions options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
options.PrivateDependencies.Add("Graphics");
|
||||
options.PrivateDependencies.Add("Content");
|
||||
|
||||
if (options.Target.IsEditor)
|
||||
{
|
||||
options.PublicDefinitions.Add("COMPILE_WITH_PROBES_BAKING");
|
||||
}
|
||||
}
|
||||
}
|
||||
514
Source/Engine/Renderer/Renderer.cpp
Normal file
514
Source/Engine/Renderer/Renderer.cpp
Normal file
@@ -0,0 +1,514 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "Renderer.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "GBufferPass.h"
|
||||
#include "ForwardPass.h"
|
||||
#include "ShadowsPass.h"
|
||||
#include "LightPass.h"
|
||||
#include "ReflectionsPass.h"
|
||||
#include "ScreenSpaceReflectionsPass.h"
|
||||
#include "AmbientOcclusionPass.h"
|
||||
#include "DepthOfFieldPass.h"
|
||||
#include "EyeAdaptationPass.h"
|
||||
#include "PostProcessingPass.h"
|
||||
#include "ColorGradingPass.h"
|
||||
#include "MotionBlurPass.h"
|
||||
#include "VolumetricFogPass.h"
|
||||
#include "HistogramPass.h"
|
||||
#include "AtmospherePreCompute.h"
|
||||
#include "Utils/MultiScaler.h"
|
||||
#include "Utils/BitonicSort.h"
|
||||
#include "AntiAliasing/FXAA.h"
|
||||
#include "AntiAliasing/TAA.h"
|
||||
#include "AntiAliasing/SMAA.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#if USE_EDITOR
|
||||
#include "Editor/Editor.h"
|
||||
#endif
|
||||
|
||||
// It must use less or the same amount of memory
|
||||
static_assert(sizeof(DrawCall::TerrainData) <= sizeof(DrawCall::PrevWorld), "Invalid size of the terrain data in the draw call.");
|
||||
|
||||
#if USE_EDITOR
|
||||
// Additional options used in editor for lightmaps baking
|
||||
bool IsRunningRadiancePass = false;
|
||||
bool IsBakingLightmaps = false;
|
||||
bool EnableLightmapsUsage = true;
|
||||
#endif
|
||||
|
||||
Array<RendererPassBase*> PassList(64);
|
||||
|
||||
class RendererService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
RendererService()
|
||||
: EngineService(TEXT("Renderer"), 20)
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
RendererService RendererServiceInstance;
|
||||
|
||||
void RenderInner(SceneRenderTask* task, RenderContext& renderContext);
|
||||
|
||||
bool RendererService::Init()
|
||||
{
|
||||
// Register passes
|
||||
PassList.Add(GBufferPass::Instance());
|
||||
PassList.Add(ShadowsPass::Instance());
|
||||
PassList.Add(LightPass::Instance());
|
||||
PassList.Add(ForwardPass::Instance());
|
||||
PassList.Add(ReflectionsPass::Instance());
|
||||
PassList.Add(ScreenSpaceReflectionsPass::Instance());
|
||||
PassList.Add(AmbientOcclusionPass::Instance());
|
||||
PassList.Add(DepthOfFieldPass::Instance());
|
||||
PassList.Add(ColorGradingPass::Instance());
|
||||
PassList.Add(VolumetricFogPass::Instance());
|
||||
PassList.Add(EyeAdaptationPass::Instance());
|
||||
PassList.Add(PostProcessingPass::Instance());
|
||||
PassList.Add(MotionBlurPass::Instance());
|
||||
PassList.Add(MultiScaler::Instance());
|
||||
PassList.Add(BitonicSort::Instance());
|
||||
PassList.Add(FXAA::Instance());
|
||||
PassList.Add(TAA::Instance());
|
||||
PassList.Add(SMAA::Instance());
|
||||
PassList.Add(HistogramPass::Instance());
|
||||
|
||||
// Skip when using Null renderer
|
||||
if (GPUDevice::Instance->GetRendererType() == RendererType::Null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init child services
|
||||
for (int32 i = 0; i < PassList.Count(); i++)
|
||||
{
|
||||
if (PassList[i]->Init())
|
||||
{
|
||||
LOG(Fatal, "Cannot init {0}. Please see a log file for more info.", PassList[i]->ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void RendererService::Dispose()
|
||||
{
|
||||
// Dispose child services
|
||||
for (int32 i = 0; i < PassList.Count(); i++)
|
||||
{
|
||||
PassList[i]->Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderAntiAliasingPass(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output)
|
||||
{
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
const auto width = (float)renderContext.Buffers->GetWidth();
|
||||
const auto height = (float)renderContext.Buffers->GetHeight();
|
||||
context->SetViewportAndScissors(width, height);
|
||||
|
||||
const auto aaMode = renderContext.List->Settings.AntiAliasing.Mode;
|
||||
if (aaMode == AntialiasingMode::FastApproximateAntialiasing)
|
||||
{
|
||||
FXAA::Instance()->Render(renderContext, input, output);
|
||||
}
|
||||
else if (aaMode == AntialiasingMode::SubpixelMorphologicalAntialiasing)
|
||||
{
|
||||
SMAA::Instance()->Render(renderContext, input, output);
|
||||
}
|
||||
else
|
||||
{
|
||||
PROFILE_GPU("Copy frame");
|
||||
|
||||
context->SetRenderTarget(output);
|
||||
context->Draw(input);
|
||||
}
|
||||
}
|
||||
|
||||
bool Renderer::IsReady()
|
||||
{
|
||||
// Warm up first (state getters initialize content loading so do it for all first)
|
||||
AtmosphereCache atmosphereCache;
|
||||
AtmospherePreCompute::GetCache(&atmosphereCache);
|
||||
for (int32 i = 0; i < PassList.Count(); i++)
|
||||
PassList[i]->IsReady();
|
||||
|
||||
// Now check state
|
||||
if (!AtmospherePreCompute::GetCache(&atmosphereCache))
|
||||
return false;
|
||||
for (int32 i = 0; i < PassList.Count(); i++)
|
||||
{
|
||||
if (!PassList[i]->IsReady())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::Render(SceneRenderTask* task)
|
||||
{
|
||||
PROFILE_GPU_CPU_NAMED("Render Frame");
|
||||
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
|
||||
context->ClearState();
|
||||
context->FlushState();
|
||||
|
||||
const Viewport viewport = task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
|
||||
// Prepare
|
||||
RenderContext renderContext(task);
|
||||
renderContext.List = RenderList::GetFromPool();
|
||||
|
||||
#if USE_EDITOR
|
||||
// Turn on low quality rendering during baking lightmaps (leave more GPU power for baking)
|
||||
const auto flags = renderContext.View.Flags;
|
||||
if (!renderContext.View.IsOfflinePass && IsBakingLightmaps)
|
||||
{
|
||||
renderContext.View.Flags &= ~(ViewFlags::AO
|
||||
| ViewFlags::Shadows
|
||||
| ViewFlags::AntiAliasing
|
||||
| ViewFlags::CustomPostProcess
|
||||
| ViewFlags::Bloom
|
||||
| ViewFlags::ToneMapping
|
||||
| ViewFlags::EyeAdaptation
|
||||
| ViewFlags::CameraArtifacts
|
||||
| ViewFlags::Reflections
|
||||
| ViewFlags::SSR
|
||||
| ViewFlags::LensFlares
|
||||
| ViewFlags::MotionBlur
|
||||
| ViewFlags::Fog
|
||||
| ViewFlags::PhysicsDebug
|
||||
| ViewFlags::Decals
|
||||
| ViewFlags::GI
|
||||
| ViewFlags::DebugDraw
|
||||
| ViewFlags::ContactShadows
|
||||
| ViewFlags::DepthOfField);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Perform the actual rendering
|
||||
RenderInner(task, renderContext);
|
||||
|
||||
// Custom additional rendering
|
||||
task->OnPostRender(context, renderContext);
|
||||
|
||||
#if USE_EDITOR
|
||||
// Restore flags
|
||||
renderContext.View.Flags = flags;
|
||||
#endif
|
||||
|
||||
// Copy back the view (modified during rendering with rendering state like TAA frame index and jitter)
|
||||
task->View = renderContext.View;
|
||||
|
||||
// Cleanup
|
||||
RenderList::ReturnToPool(renderContext.List);
|
||||
}
|
||||
|
||||
bool Renderer::NeedMotionVectors(RenderContext& renderContext)
|
||||
{
|
||||
const int32 screenWidth = renderContext.Buffers->GetWidth();
|
||||
const int32 screenHeight = renderContext.Buffers->GetHeight();
|
||||
if (screenWidth < 16 || screenHeight < 16 || renderContext.Task->IsCameraCut)
|
||||
return false;
|
||||
MotionBlurSettings& motionBlurSettings = renderContext.List->Settings.MotionBlur;
|
||||
return ((renderContext.View.Flags & ViewFlags::MotionBlur) != 0 && motionBlurSettings.Enabled && motionBlurSettings.Scale > ZeroTolerance) ||
|
||||
renderContext.View.Mode == ViewMode::MotionVectors ||
|
||||
ScreenSpaceReflectionsPass::NeedMotionVectors(renderContext) ||
|
||||
TAA::NeedMotionVectors(renderContext);
|
||||
}
|
||||
|
||||
void Renderer::DrawSceneDepth(GPUContext* context, SceneRenderTask* task, GPUTexture* output, const Array<Actor*>& customActors)
|
||||
{
|
||||
CHECK(context && task && output && output->IsDepthStencil());
|
||||
|
||||
// Prepare
|
||||
RenderContext renderContext(task);
|
||||
renderContext.List = RenderList::GetFromPool();
|
||||
renderContext.View.Pass = DrawPass::Depth;
|
||||
renderContext.View.Prepare(renderContext);
|
||||
|
||||
// Call drawing (will collect draw calls)
|
||||
if (customActors.HasItems())
|
||||
{
|
||||
// Draw custom actors
|
||||
for (auto actor : customActors)
|
||||
{
|
||||
if (actor && actor->GetIsActive())
|
||||
actor->Draw(renderContext);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw scene actors
|
||||
Level::DrawActors(renderContext);
|
||||
}
|
||||
|
||||
// Sort draw calls
|
||||
renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::Depth);
|
||||
|
||||
// Execute draw calls
|
||||
const float width = (float)output->Width();
|
||||
const float height = (float)output->Height();
|
||||
context->SetViewport(width, height);
|
||||
context->SetRenderTarget(output->View(), static_cast<GPUTextureView*>(nullptr));
|
||||
renderContext.List->ExecuteDrawCalls(renderContext, DrawCallsListType::Depth);
|
||||
|
||||
// Cleanup
|
||||
RenderList::ReturnToPool(renderContext.List);
|
||||
}
|
||||
|
||||
void Renderer::DrawPostFxMaterial(GPUContext* context, const RenderContext& renderContext, MaterialBase* material, GPUTexture* output, GPUTextureView* input)
|
||||
{
|
||||
CHECK(material && material->IsPostFx());
|
||||
CHECK(context && output);
|
||||
|
||||
context->ResetSR();
|
||||
context->SetViewport((float)output->Width(), (float)output->Height());
|
||||
context->SetRenderTarget(output->View());
|
||||
context->FlushState();
|
||||
|
||||
MaterialBase::BindParameters bindParams(context, renderContext);
|
||||
bindParams.Input = input;
|
||||
material->Bind(bindParams);
|
||||
|
||||
context->DrawFullscreenTriangle();
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
void RenderInner(SceneRenderTask* task, RenderContext& renderContext)
|
||||
{
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
ASSERT(renderContext.Buffers && renderContext.Buffers->GetWidth() > 0);
|
||||
|
||||
// Perform postFx volumes blending and query before rendering
|
||||
task->CollectPostFxVolumes(renderContext);
|
||||
renderContext.List->BlendSettings();
|
||||
auto aaMode = (renderContext.View.Flags & ViewFlags::AntiAliasing) != 0 ? renderContext.List->Settings.AntiAliasing.Mode : AntialiasingMode::None;
|
||||
if (view.IsOrthographicProjection() && aaMode == AntialiasingMode::TemporalAntialiasing)
|
||||
aaMode = AntialiasingMode::None; // TODO: support TAA in ortho projection
|
||||
#if USE_EDITOR
|
||||
// Disable temporal AA effect in editor without play mode enabled to hide minor artifacts on objects moving and lack of valid motion vectors
|
||||
if (!Editor::IsPlayMode && aaMode == AntialiasingMode::TemporalAntialiasing)
|
||||
aaMode = AntialiasingMode::FastApproximateAntialiasing;
|
||||
#endif
|
||||
renderContext.List->Settings.AntiAliasing.Mode = aaMode;
|
||||
|
||||
// Prepare
|
||||
renderContext.View.Prepare(renderContext);
|
||||
renderContext.Buffers->Prepare();
|
||||
for (auto& postFx : task->CustomPostFx)
|
||||
{
|
||||
if (postFx.Target)
|
||||
renderContext.List->PostFx.Add(&postFx);
|
||||
}
|
||||
|
||||
// Collect renderable objects and construct draw call list
|
||||
view.Pass = DrawPass::GBuffer | DrawPass::Forward | DrawPass::Distortion;
|
||||
if (Renderer::NeedMotionVectors(renderContext))
|
||||
view.Pass |= DrawPass::MotionVectors;
|
||||
task->OnCollectDrawCalls(renderContext);
|
||||
|
||||
// Sort draw calls
|
||||
renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::GBuffer);
|
||||
renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::GBufferNoDecals);
|
||||
renderContext.List->SortDrawCalls(renderContext, true, DrawCallsListType::Forward);
|
||||
renderContext.List->SortDrawCalls(renderContext, false, DrawCallsListType::Distortion);
|
||||
|
||||
// Get the light accumulation buffer
|
||||
auto tempDesc = GPUTextureDescription::New2D(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight(), PixelFormat::R11G11B10_Float);
|
||||
auto lightBuffer = RenderTargetPool::Get(tempDesc);
|
||||
|
||||
// Fill GBuffer
|
||||
GBufferPass::Instance()->Fill(renderContext, lightBuffer->View());
|
||||
|
||||
// Check if debug emissive light
|
||||
if (renderContext.View.Mode == ViewMode::Emissive || renderContext.View.Mode == ViewMode::LightmapUVsDensity)
|
||||
{
|
||||
// Render reflections debug view
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors((float)renderContext.Buffers->GetWidth(), (float)renderContext.Buffers->GetHeight());
|
||||
context->Draw(lightBuffer->View());
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Render motion vectors
|
||||
MotionBlurPass::Instance()->RenderMotionVectors(renderContext);
|
||||
|
||||
// Render ambient occlusion
|
||||
AmbientOcclusionPass::Instance()->Render(renderContext);
|
||||
|
||||
// Check if use custom view mode
|
||||
if (GBufferPass::IsDebugView(renderContext.View.Mode))
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors((float)renderContext.Buffers->GetWidth(), (float)renderContext.Buffers->GetHeight());
|
||||
GBufferPass::Instance()->RenderDebug(renderContext);
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Render lighting
|
||||
LightPass::Instance()->RenderLight(renderContext, *lightBuffer);
|
||||
if (renderContext.View.Mode == ViewMode::LightBuffer)
|
||||
{
|
||||
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
|
||||
GPUTexture* tempBuffer = renderContext.Buffers->RT2_FloatRGB;
|
||||
EyeAdaptationPass::Instance()->Render(renderContext, lightBuffer);
|
||||
PostProcessingPass::Instance()->Render(renderContext, lightBuffer, tempBuffer, colorGradingLUT);
|
||||
RenderTargetPool::Release(colorGradingLUT);
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors((float)renderContext.Buffers->GetWidth(), (float)renderContext.Buffers->GetHeight());
|
||||
context->Draw(tempBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Material and Custom PostFx
|
||||
renderContext.List->RunPostFxPass(context, renderContext, MaterialPostFxLocation::BeforeReflectionsPass, PostProcessEffectLocation::BeforeReflectionsPass, lightBuffer);
|
||||
|
||||
// Render reflections
|
||||
ReflectionsPass::Instance()->Render(renderContext, *lightBuffer);
|
||||
if (renderContext.View.Mode == ViewMode::Reflections)
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors((float)renderContext.Buffers->GetWidth(), (float)renderContext.Buffers->GetHeight());
|
||||
context->Draw(lightBuffer);
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Material and Custom PostFx
|
||||
renderContext.List->RunPostFxPass(context, renderContext, MaterialPostFxLocation::BeforeForwardPass, PostProcessEffectLocation::BeforeForwardPass, lightBuffer);
|
||||
|
||||
// Render fog
|
||||
context->ResetSR();
|
||||
if (renderContext.List->AtmosphericFog)
|
||||
{
|
||||
PROFILE_GPU_CPU("Atmospheric Fog");
|
||||
renderContext.List->AtmosphericFog->DrawFog(context, renderContext, *lightBuffer);
|
||||
context->ResetSR();
|
||||
}
|
||||
if (renderContext.List->Fog)
|
||||
{
|
||||
VolumetricFogPass::Instance()->Render(renderContext);
|
||||
|
||||
PROFILE_GPU_CPU("Fog");
|
||||
renderContext.List->Fog->DrawFog(context, renderContext, *lightBuffer);
|
||||
context->ResetSR();
|
||||
}
|
||||
|
||||
// Run forward pass
|
||||
auto forwardPassResult = renderContext.Buffers->RT1_FloatRGB;
|
||||
ForwardPass::Instance()->Render(renderContext, lightBuffer, forwardPassResult);
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
context->FlushState();
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
|
||||
// Check if skip post-processing
|
||||
if (renderContext.View.Mode == ViewMode::NoPostFx || renderContext.View.Mode == ViewMode::Wireframe)
|
||||
{
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors((float)renderContext.Buffers->GetWidth(), (float)renderContext.Buffers->GetHeight());
|
||||
context->Draw(forwardPassResult);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare buffers for post processing frame
|
||||
GPUTexture* frameBuffer = renderContext.Buffers->RT1_FloatRGB;
|
||||
GPUTexture* tempBuffer = renderContext.Buffers->RT2_FloatRGB;
|
||||
if (forwardPassResult == tempBuffer)
|
||||
Swap(frameBuffer, tempBuffer);
|
||||
|
||||
// Material and Custom PostFx
|
||||
renderContext.List->RunMaterialPostFxPass(context, renderContext, MaterialPostFxLocation::BeforePostProcessingPass, frameBuffer, tempBuffer);
|
||||
renderContext.List->RunCustomPostFxPass(context, renderContext, PostProcessEffectLocation::BeforePostProcessingPass, frameBuffer, tempBuffer);
|
||||
|
||||
// Temporal Anti-Aliasing (goes before post processing)
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing)
|
||||
{
|
||||
TAA::Instance()->Render(renderContext, frameBuffer, tempBuffer->View());
|
||||
Swap(frameBuffer, tempBuffer);
|
||||
}
|
||||
|
||||
// Depth of Field
|
||||
auto dofTemporary = DepthOfFieldPass::Instance()->Render(renderContext, frameBuffer);
|
||||
frameBuffer = dofTemporary ? dofTemporary : frameBuffer;
|
||||
|
||||
// Motion Blur
|
||||
MotionBlurPass::Instance()->Render(renderContext, frameBuffer, tempBuffer);
|
||||
|
||||
// Color Grading LUT generation
|
||||
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
|
||||
|
||||
// Post processing
|
||||
EyeAdaptationPass::Instance()->Render(renderContext, frameBuffer);
|
||||
PostProcessingPass::Instance()->Render(renderContext, frameBuffer, tempBuffer, colorGradingLUT);
|
||||
RenderTargetPool::Release(colorGradingLUT);
|
||||
RenderTargetPool::Release(dofTemporary);
|
||||
Swap(frameBuffer, tempBuffer);
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->ResetSR();
|
||||
context->FlushState();
|
||||
|
||||
// Custom Post Processing
|
||||
renderContext.List->RunMaterialPostFxPass(context, renderContext, MaterialPostFxLocation::AfterPostProcessingPass, frameBuffer, tempBuffer);
|
||||
renderContext.List->RunCustomPostFxPass(context, renderContext, PostProcessEffectLocation::Default, frameBuffer, tempBuffer);
|
||||
renderContext.List->RunMaterialPostFxPass(context, renderContext, MaterialPostFxLocation::AfterCustomPostEffects, frameBuffer, tempBuffer);
|
||||
|
||||
// Debug motion vectors
|
||||
if (renderContext.View.Mode == ViewMode::MotionVectors)
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors((float)renderContext.Buffers->GetWidth(), (float)renderContext.Buffers->GetHeight());
|
||||
MotionBlurPass::Instance()->RenderDebug(renderContext, frameBuffer->View());
|
||||
return;
|
||||
}
|
||||
|
||||
// Anti Aliasing
|
||||
if (!renderContext.List->HasAnyPostAA(renderContext))
|
||||
{
|
||||
// AA -> Back Buffer
|
||||
RenderAntiAliasingPass(renderContext, frameBuffer, task->GetOutputView());
|
||||
}
|
||||
else
|
||||
{
|
||||
// AA -> PostFx
|
||||
RenderAntiAliasingPass(renderContext, frameBuffer, *tempBuffer);
|
||||
context->ResetRenderTarget();
|
||||
Swap(frameBuffer, tempBuffer);
|
||||
renderContext.List->RunCustomPostFxPass(context, renderContext, PostProcessEffectLocation::AfterAntiAliasingPass, frameBuffer, tempBuffer);
|
||||
renderContext.List->RunMaterialPostFxPass(context, renderContext, MaterialPostFxLocation::AfterAntiAliasingPass, frameBuffer, tempBuffer);
|
||||
|
||||
// PostFx -> Back Buffer
|
||||
{
|
||||
PROFILE_GPU("Copy frame");
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors((float)renderContext.Buffers->GetWidth(), (float)renderContext.Buffers->GetHeight());
|
||||
context->Draw(frameBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Source/Engine/Renderer/Renderer.cs
Normal file
21
Source/Engine/Renderer/Renderer.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial class Renderer
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws scene objects depth (to the output Z buffer). The output must be depth texture to write hardware depth to it.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU commands context to use.</param>
|
||||
/// <param name="task">Render task to use it's view description and the render buffers.</param>
|
||||
/// <param name="output">The output texture. Must be valid and created.</param>
|
||||
/// <param name="customActors">The custom set of actors to render. If empty, the loaded scenes will be rendered.</param>
|
||||
public static void DrawSceneDepth(GPUContext context, SceneRenderTask task, GPUTexture output, List<Actor> customActors)
|
||||
{
|
||||
Internal_DrawSceneDepth(FlaxEngine.Object.GetUnmanagedPtr(context), FlaxEngine.Object.GetUnmanagedPtr(task), FlaxEngine.Object.GetUnmanagedPtr(output), Utils.ExtractArrayFromList(customActors));
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Source/Engine/Renderer/Renderer.h
Normal file
63
Source/Engine/Renderer/Renderer.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
|
||||
class GPUContext;
|
||||
class GPUTexture;
|
||||
class GPUTextureView;
|
||||
struct RenderContext;
|
||||
class RenderTask;
|
||||
class SceneRenderTask;
|
||||
class MaterialBase;
|
||||
class Actor;
|
||||
|
||||
/// <summary>
|
||||
/// High-level rendering service.
|
||||
/// </summary>
|
||||
API_CLASS(Static) class FLAXENGINE_API Renderer
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Renderer);
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the scene rendering system is ready (all shaders are loaded and helper resources are ready).
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if this rendering service is ready for scene rendering; otherwise, <c>false</c>.</returns>
|
||||
static bool IsReady();
|
||||
|
||||
/// <summary>
|
||||
/// Performs rendering for the input task.
|
||||
/// </summary>
|
||||
/// <param name="task">The scene rendering task.</param>
|
||||
static void Render(SceneRenderTask* task);
|
||||
|
||||
/// <summary>
|
||||
/// Determinates whenever any pass requires motion vectors rendering.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <returns>True if need to render motion vectors, otherwise false.</returns>
|
||||
static bool NeedMotionVectors(RenderContext& renderContext);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Draws scene objects depth (to the output Z buffer). The output must be depth texture to write hardware depth to it.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU commands context to use.</param>
|
||||
/// <param name="task">Render task to use it's view description and the render buffers.</param>
|
||||
/// <param name="output">The output texture. Must be valid and created.</param>
|
||||
/// <param name="customActors">The custom set of actors to render. If empty, the loaded scenes will be rendered.</param>
|
||||
API_FUNCTION() static void DrawSceneDepth(GPUContext* context, SceneRenderTask* task, GPUTexture* output, const Array<Actor*, HeapAllocation>& customActors);
|
||||
|
||||
/// <summary>
|
||||
/// Draws postFx material to the render target.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU commands context to use.</param>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="material">The material to render. It must be a post fx material.</param>
|
||||
/// <param name="output">The output texture. Must be valid and created.</param>
|
||||
/// <param name="input">The input texture. It's optional.</param>
|
||||
API_FUNCTION() static void DrawPostFxMaterial(GPUContext* context, API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, GPUTexture* output, GPUTextureView* input);
|
||||
};
|
||||
115
Source/Engine/Renderer/RendererPass.h
Normal file
115
Source/Engine/Renderer/RendererPass.h
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Singleton.h"
|
||||
#include "Engine/Core/Object.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Assets/Texture.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Graphics/GPUPipelineState.h"
|
||||
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
|
||||
#include "Engine/Profiler/Profiler.h"
|
||||
#include "Config.h"
|
||||
|
||||
class RendererUtils
|
||||
{
|
||||
public:
|
||||
|
||||
static float TemporalHalton(int32 index, int32 base)
|
||||
{
|
||||
float result = 0.0f;
|
||||
const float invBase = 1.0f / base;
|
||||
float fraction = invBase;
|
||||
while (index > 0)
|
||||
{
|
||||
result += (index % base) * fraction;
|
||||
index /= base;
|
||||
fraction *= invBase;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Base class for renderer components called render pass.
|
||||
/// Each render pass supports proper resources initialization and disposing.
|
||||
/// </summary>
|
||||
/// <seealso cref="Object" />
|
||||
class RendererPassBase : public Object
|
||||
{
|
||||
protected:
|
||||
|
||||
bool _hasValidResources;
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
RendererPassBase()
|
||||
{
|
||||
_hasValidResources = false;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initialize service.
|
||||
/// </summary>
|
||||
virtual bool Init()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup service data.
|
||||
/// </summary>
|
||||
virtual void Dispose()
|
||||
{
|
||||
// Clear flag
|
||||
_hasValidResources = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether can render this pass. Checks if pass is ready and has valid resources loaded.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if can render pass; otherwise, <c>false</c>.</returns>
|
||||
bool IsReady()
|
||||
{
|
||||
return !checkIfSkipPass();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
bool checkIfSkipPass()
|
||||
{
|
||||
if (_hasValidResources)
|
||||
return false;
|
||||
|
||||
const bool setupFailed = setupResources();
|
||||
_hasValidResources = !setupFailed;
|
||||
return setupFailed;
|
||||
}
|
||||
|
||||
void invalidateResources()
|
||||
{
|
||||
// Clear flag
|
||||
_hasValidResources = false;
|
||||
}
|
||||
|
||||
virtual bool setupResources()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Singleton render pass template.
|
||||
/// </summary>
|
||||
/// <seealso cref="Object" />
|
||||
template<class T>
|
||||
class RendererPass : public Singleton<T>, public RendererPassBase
|
||||
{
|
||||
};
|
||||
|
||||
#define REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, index, dataType) LOG(Fatal, "Shader {0} has incorrect constant buffer {1} size: {2} bytes. Expected: {3} bytes", ToString(), index, shader->GetCB(index)->GetSize(), sizeof(dataType));
|
||||
411
Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp
Normal file
411
Source/Engine/Renderer/ScreenSpaceReflectionsPass.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ScreenSpaceReflectionsPass.h"
|
||||
#include "ReflectionsPass.h"
|
||||
#include "GBufferPass.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"
|
||||
|
||||
#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;
|
||||
|
||||
Vector2 SSRtexelSize;
|
||||
float TemporalTime;
|
||||
float BRDFBias;
|
||||
|
||||
float WorldAntiSelfOcclusionBias;
|
||||
float EdgeFadeFactor;
|
||||
float TemporalResponse;
|
||||
float TemporalScale;
|
||||
|
||||
float RayTraceStep;
|
||||
float NoTemporalEffect;
|
||||
float Intensity;
|
||||
float FadeOutDistance;
|
||||
|
||||
Vector3 Dummy0;
|
||||
float InvFadeDistance;
|
||||
|
||||
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();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_psRayTracePass);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psCombinePass);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psTemporalPass);
|
||||
SAFE_DELETE_GPU_RESOURCE(_psMixPass);
|
||||
_psResolvePass.Delete();
|
||||
|
||||
// Release assets
|
||||
_shader.Unlink();
|
||||
_preIntegratedGF.Unlink();
|
||||
}
|
||||
|
||||
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 = renderContext.Buffers->GetWidth();
|
||||
const int32 height = renderContext.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 = settings.FadeOutDistance;
|
||||
data.InvFadeDistance = 1.0f / settings.FadeDistance;
|
||||
data.TemporalScale = settings.TemporalScale;
|
||||
data.TemporalResponse = settings.TemporalResponse;
|
||||
data.NoTemporalEffect = useTemporal ? 0.0f : 1.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);
|
||||
|
||||
renderContext.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);
|
||||
}
|
||||
88
Source/Engine/Renderer/ScreenSpaceReflectionsPass.h
Normal file
88
Source/Engine/Renderer/ScreenSpaceReflectionsPass.h
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
|
||||
/// <summary>
|
||||
/// Screen Space Reflections rendering service
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The following implementation is using Stochastic Screen-Space Reflections algorithm based on:
|
||||
/// https://www.slideshare.net/DICEStudio/stochastic-screenspace-reflections
|
||||
/// It's well optimized and provides solid visual effect.
|
||||
///
|
||||
/// Algorithm steps:
|
||||
/// 1) Downscale depth [optional]
|
||||
/// 2) Ray trace
|
||||
/// 3) Resolve rays
|
||||
/// 4) Temporal blur [optional]
|
||||
/// 5) Combine final image (alpha blend into reflections buffer)
|
||||
/// </remarks>
|
||||
/// <seealso cref="RendererPass{ScreenSpaceReflectionsPass}" />
|
||||
class ScreenSpaceReflectionsPass : public RendererPass<ScreenSpaceReflectionsPass>
|
||||
{
|
||||
private:
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psRayTracePass;
|
||||
GPUPipelineState* _psCombinePass;
|
||||
GPUPipelineStatePermutationsPs<4> _psResolvePass;
|
||||
GPUPipelineState* _psTemporalPass;
|
||||
GPUPipelineState* _psMixPass;
|
||||
|
||||
AssetReference<Texture> _preIntegratedGF;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
ScreenSpaceReflectionsPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determinates whenever this pass requires motion vectors rendering.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <returns>True if need to render motion vectors, otherwise false.</returns>
|
||||
static bool NeedMotionVectors(RenderContext& renderContext);
|
||||
|
||||
/// <summary>
|
||||
/// Perform SSR rendering for the input task (blends reflections to given texture using alpha blending).
|
||||
/// </summary>
|
||||
/// <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);
|
||||
|
||||
private:
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psRayTracePass->ReleaseGPU();
|
||||
_psCombinePass->ReleaseGPU();
|
||||
_psResolvePass.Release();
|
||||
_psTemporalPass->ReleaseGPU();
|
||||
_psMixPass->ReleaseGPU();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
710
Source/Engine/Renderer/ShadowsPass.cpp
Normal file
710
Source/Engine/Renderer/ShadowsPass.cpp
Normal file
@@ -0,0 +1,710 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ShadowsPass.h"
|
||||
#include "GBufferPass.h"
|
||||
#include "VolumetricFogPass.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Renderer/Lightmaps.h"
|
||||
#endif
|
||||
|
||||
#define NormalOffsetScaleTweak 100.0f
|
||||
#define SpotLight_NearPlane 10.0f
|
||||
#define PointLight_NearPlane 10.0f
|
||||
|
||||
PACK_STRUCT(struct Data{
|
||||
GBufferData GBuffer;
|
||||
LightData Light;
|
||||
LightShadowData LightShadow;
|
||||
Matrix WVP;
|
||||
Matrix ViewProjectionMatrix;
|
||||
Vector2 Dummy0;
|
||||
float ContactShadowsDistance;
|
||||
float ContactShadowsLength;
|
||||
});
|
||||
|
||||
ShadowsPass::ShadowsPass()
|
||||
: _shader(nullptr)
|
||||
, _shadowMapsSizeCSM(0)
|
||||
, _shadowMapsSizeCube(0)
|
||||
, _shadowMapCSM(nullptr)
|
||||
, _shadowMapCube(nullptr)
|
||||
, _currentShadowMapsQuality((Quality)((int32)Quality::Ultra + 1))
|
||||
, _sphereModel(nullptr)
|
||||
, maxShadowsQuality(0)
|
||||
{
|
||||
}
|
||||
|
||||
uint64 ShadowsPass::GetShadowMapsMemoryUsage() const
|
||||
{
|
||||
uint64 result = 0;
|
||||
|
||||
if (_shadowMapCSM)
|
||||
result += _shadowMapCSM->GetMemoryUsage();
|
||||
if (_shadowMapCube)
|
||||
result += _shadowMapCube->GetMemoryUsage();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
String ShadowsPass::ToString() const
|
||||
{
|
||||
return TEXT("ShadowsPass");
|
||||
}
|
||||
|
||||
bool ShadowsPass::Init()
|
||||
{
|
||||
// Create pipeline states
|
||||
_psShadowDir.CreatePipelineStates();
|
||||
_psShadowPoint.CreatePipelineStates();
|
||||
_psShadowSpot.CreatePipelineStates();
|
||||
|
||||
// Load assets
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/Shadows"));
|
||||
_sphereModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/SphereLowPoly"));
|
||||
if (_shader == nullptr || _sphereModel == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create shadow maps
|
||||
_shadowMapCSM = GPUDevice::Instance->CreateTexture(TEXT("Shadow Map CSM"));
|
||||
_shadowMapCube = GPUDevice::Instance->CreateTexture(TEXT("Shadow Map Cube"));
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<ShadowsPass, &ShadowsPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShadowsPass::setupResources()
|
||||
{
|
||||
// Wait for the assets
|
||||
if (!_sphereModel->CanBeRendered() || !_shader->IsLoaded())
|
||||
return true;
|
||||
auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffers sizes
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(Data))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create pipeline stages
|
||||
GPUPipelineState::Description psDesc;
|
||||
if (!_psShadowPoint.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc.CullMode = CullMode::TwoSided;
|
||||
psDesc.VS = shader->GetVS("VS_Model");
|
||||
if (_psShadowPoint.Create(psDesc, shader, "PS_PointLight"))
|
||||
return true;
|
||||
}
|
||||
if (!_psShadowDir.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (_psShadowDir.Create(psDesc, shader, "PS_DirLight"))
|
||||
return true;
|
||||
}
|
||||
if (!_psShadowSpot.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultNoDepth;
|
||||
psDesc.CullMode = CullMode::TwoSided;
|
||||
psDesc.VS = shader->GetVS("VS_Model");
|
||||
if (_psShadowSpot.Create(psDesc, shader, "PS_SpotLight"))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShadowsPass::updateShadowMapSize()
|
||||
{
|
||||
// Temporary data
|
||||
int32 newSizeCSM = 0;
|
||||
int32 newSizeCube = 0;
|
||||
|
||||
// Select new size
|
||||
_currentShadowMapsQuality = Graphics::ShadowMapsQuality;
|
||||
switch (_currentShadowMapsQuality)
|
||||
{
|
||||
case Quality::Ultra:
|
||||
newSizeCSM = 2048;
|
||||
newSizeCube = 1024;
|
||||
break;
|
||||
case Quality::High:
|
||||
newSizeCSM = 1024;
|
||||
newSizeCube = 1024;
|
||||
break;
|
||||
case Quality::Medium:
|
||||
newSizeCSM = 1024;
|
||||
newSizeCube = 512;
|
||||
break;
|
||||
case Quality::Low:
|
||||
newSizeCSM = 512;
|
||||
newSizeCube = 256;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if size will change
|
||||
if (newSizeCSM > 0 && newSizeCSM != _shadowMapsSizeCSM)
|
||||
{
|
||||
if (_shadowMapCSM->Init(GPUTextureDescription::New2D(newSizeCSM, newSizeCSM, SHADOW_MAPS_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil, 1, MAX_CSM_CASCADES)))
|
||||
{
|
||||
LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("CSM"), newSizeCSM, (int32)SHADOW_MAPS_FORMAT);
|
||||
return;
|
||||
}
|
||||
_shadowMapsSizeCSM = newSizeCSM;
|
||||
}
|
||||
if (newSizeCube > 0 && newSizeCube != _shadowMapsSizeCube)
|
||||
{
|
||||
if (_shadowMapCube->Init(GPUTextureDescription::NewCube(newSizeCube, SHADOW_MAPS_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::DepthStencil)))
|
||||
{
|
||||
LOG(Fatal, "Cannot setup shadow map '{0}' Size: {1}, format: {2}.", TEXT("Cube"), newSizeCube, (int32)SHADOW_MAPS_FORMAT);
|
||||
return;
|
||||
}
|
||||
_shadowMapsSizeCube = newSizeCube;
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowsPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
_psShadowDir.Delete();
|
||||
_psShadowPoint.Delete();
|
||||
_psShadowSpot.Delete();
|
||||
_shader.Unlink();
|
||||
_sphereModel.Unlink();
|
||||
SAFE_DELETE_GPU_RESOURCE(_shadowMapCSM);
|
||||
SAFE_DELETE_GPU_RESOURCE(_shadowMapCube);
|
||||
}
|
||||
|
||||
bool ShadowsPass::CanRenderShadow(RenderContext& renderContext, const RendererPointLightData& light)
|
||||
{
|
||||
const Vector3 lightPosition = light.Position;
|
||||
const float dstLightToView = Vector3::Distance(lightPosition, renderContext.View.Position);
|
||||
|
||||
// Fade shadow on distance
|
||||
const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
|
||||
const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance);
|
||||
|
||||
return fade > ZeroTolerance;
|
||||
}
|
||||
|
||||
bool ShadowsPass::CanRenderShadow(RenderContext& renderContext, const RendererSpotLightData& light)
|
||||
{
|
||||
const Vector3 lightPosition = light.Position;
|
||||
const float dstLightToView = Vector3::Distance(lightPosition, renderContext.View.Position);
|
||||
|
||||
// Fade shadow on distance
|
||||
const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
|
||||
const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance);
|
||||
|
||||
return fade > ZeroTolerance;
|
||||
}
|
||||
|
||||
bool ShadowsPass::CanRenderShadow(RenderContext& renderContext, const RendererDirectionalLightData& light)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShadowsPass::Prepare(RenderContext& renderContext, GPUContext* context)
|
||||
{
|
||||
ASSERT(IsReady());
|
||||
auto& view = renderContext.View;
|
||||
const auto shader = _shader->GetShader();
|
||||
|
||||
const auto shadowMapsQuality = Graphics::ShadowMapsQuality;
|
||||
if (shadowMapsQuality != _currentShadowMapsQuality)
|
||||
updateShadowMapSize();
|
||||
auto shadowsQuality = Graphics::ShadowsQuality;
|
||||
maxShadowsQuality = Math::Min<int32>(static_cast<int32>(shadowsQuality), static_cast<int32>(view.MaxShadowsQuality));
|
||||
|
||||
// Use the current render view to sync model LODs with the shadow maps rendering stage
|
||||
_shadowContext.LodProxyView = &renderContext.View;
|
||||
|
||||
// Prepare properties
|
||||
auto& shadowView = _shadowContext.View;
|
||||
shadowView.Flags = view.Flags;
|
||||
shadowView.StaticFlagsMask = view.StaticFlagsMask;
|
||||
shadowView.IsOfflinePass = view.IsOfflinePass;
|
||||
shadowView.ModelLODBias = view.ModelLODBias + view.ShadowModelLODBias;
|
||||
shadowView.ModelLODDistanceFactor = view.ModelLODDistanceFactor * view.ShadowModelLODDistanceFactor;
|
||||
shadowView.Pass = DrawPass::Depth;
|
||||
_shadowContext.List = &_shadowCache;
|
||||
}
|
||||
|
||||
void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererPointLightData& light, GPUTextureView* shadowMask)
|
||||
{
|
||||
const float sphereModelScale = 3.0f;
|
||||
|
||||
PROFILE_GPU_CPU("Shadow");
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
auto shader = _shader->GetShader();
|
||||
Data sperLight;
|
||||
float lightRadius = light.Radius;
|
||||
Vector3 lightPosition = light.Position;
|
||||
Vector3 lightDirection = light.Direction;
|
||||
float dstLightToView = Vector3::Distance(lightPosition, view.Position);
|
||||
|
||||
// TODO: here we can use lower shadows quality based on light distance to view (LOD switching) and per light setting for max quality
|
||||
int32 shadowQuality = maxShadowsQuality;
|
||||
|
||||
// Fade shadow on distance
|
||||
const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
|
||||
const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance);
|
||||
|
||||
// Set up GPU context and render view
|
||||
const auto shadowMapsSizeCube = (float)_shadowMapsSizeCube;
|
||||
context->SetViewportAndScissors(shadowMapsSizeCube, shadowMapsSizeCube);
|
||||
_shadowContext.View.SetUpCube(PointLight_NearPlane, lightRadius, lightPosition);
|
||||
_shadowContext.View.PrepareCache(_shadowContext, shadowMapsSizeCube, shadowMapsSizeCube, Vector2::Zero);
|
||||
|
||||
// Render depth to all 6 faces of the cube map
|
||||
for (int32 faceIndex = 0; faceIndex < 6; faceIndex++)
|
||||
{
|
||||
// Set up view
|
||||
_shadowCache.Clear();
|
||||
_shadowContext.View.SetFace(faceIndex);
|
||||
Matrix::Transpose(_shadowContext.View.ViewProjection(), sperLight.LightShadow.ShadowVP[faceIndex]);
|
||||
|
||||
// Set render target
|
||||
auto rt = _shadowMapCube->View(faceIndex);
|
||||
context->ResetSR();
|
||||
context->SetRenderTarget(rt, static_cast<GPUTextureView*>(nullptr));
|
||||
context->ClearDepth(rt);
|
||||
|
||||
// Render actors to the shadow map
|
||||
renderContext.Task->OnCollectDrawCalls(_shadowContext);
|
||||
_shadowCache.SortDrawCalls(_shadowContext, false, DrawCallsListType::Depth);
|
||||
_shadowCache.ExecuteDrawCalls(_shadowContext, DrawCallsListType::Depth);
|
||||
}
|
||||
|
||||
// Restore GPU context
|
||||
context->ResetSR();
|
||||
context->ResetRenderTarget();
|
||||
const Viewport viewport = renderContext.Task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
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(4, renderContext.Buffers->GBuffer3);
|
||||
|
||||
// Setup shader data
|
||||
GBufferPass::SetInputs(view, sperLight.GBuffer);
|
||||
light.SetupLightData(&sperLight.Light, view, true);
|
||||
sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCube;
|
||||
sperLight.LightShadow.Sharpness = light.ShadowsSharpness;
|
||||
sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength * fade);
|
||||
sperLight.LightShadow.NormalOffsetScale = light.ShadowsNormalOffsetScale * NormalOffsetScaleTweak * (1.0f / shadowMapsSizeCube);
|
||||
sperLight.LightShadow.Bias = light.ShadowsDepthBias;
|
||||
sperLight.LightShadow.FadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
|
||||
sperLight.LightShadow.NumCascades = 1;
|
||||
sperLight.LightShadow.CascadeSplits = Vector4::Zero;
|
||||
Matrix::Transpose(view.ViewProjection(), sperLight.ViewProjectionMatrix);
|
||||
sperLight.ContactShadowsDistance = light.ShadowsDistance;
|
||||
sperLight.ContactShadowsLength = view.Flags & ViewFlags::ContactShadows ? light.ContactShadowsLength : 0.0f;
|
||||
|
||||
// Calculate world view projection matrix for the light sphere
|
||||
Matrix world, wvp, matrix;
|
||||
Matrix::Scaling(lightRadius * sphereModelScale, wvp);
|
||||
Matrix::Translation(lightPosition, matrix);
|
||||
Matrix::Multiply(wvp, matrix, world);
|
||||
Matrix::Multiply(world, view.ViewProjection(), wvp);
|
||||
Matrix::Transpose(wvp, sperLight.WVP);
|
||||
|
||||
// Render shadow in screen space
|
||||
context->UpdateCB(shader->GetCB(0), &sperLight);
|
||||
context->BindCB(0, shader->GetCB(0));
|
||||
context->BindCB(1, shader->GetCB(1));
|
||||
context->BindSR(5, _shadowMapCube->ViewArray());
|
||||
context->SetRenderTarget(shadowMask);
|
||||
context->SetState(_psShadowPoint.Get(shadowQuality + (sperLight.ContactShadowsLength > ZeroTolerance ? 4 : 0)));
|
||||
_sphereModel->Render(context);
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->UnBindSR(5);
|
||||
|
||||
// Render volumetric light with shadow
|
||||
VolumetricFogPass::Instance()->RenderLight(renderContext, context, light, _shadowMapCube->ViewArray(), sperLight.LightShadow);
|
||||
}
|
||||
|
||||
void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererSpotLightData& light, GPUTextureView* shadowMask)
|
||||
{
|
||||
const float sphereModelScale = 3.0f;
|
||||
|
||||
PROFILE_GPU_CPU("Shadow");
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
auto shader = _shader->GetShader();
|
||||
Data sperLight;
|
||||
float lightRadius = light.Radius;
|
||||
Vector3 lightPosition = light.Position;
|
||||
Vector3 lightDirection = light.Direction;
|
||||
float dstLightToView = Vector3::Distance(lightPosition, view.Position);
|
||||
|
||||
// TODO: here we can use lower shadows quality based on light distance to view (LOD switching) and per light setting for max quality
|
||||
int32 shadowQuality = maxShadowsQuality;
|
||||
|
||||
// Fade shadow on distance
|
||||
const float fadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
|
||||
const float fade = 1 - Math::Saturate((dstLightToView - light.Radius - light.ShadowsDistance + fadeDistance) / fadeDistance);
|
||||
|
||||
// Set up GPU context and render view
|
||||
const auto shadowMapsSizeCube = (float)_shadowMapsSizeCube;
|
||||
context->SetViewportAndScissors(shadowMapsSizeCube, shadowMapsSizeCube);
|
||||
_shadowContext.View.SetProjector(SpotLight_NearPlane, lightRadius, lightPosition, lightDirection, light.UpVector, light.OuterConeAngle * 2.0f);
|
||||
_shadowContext.View.PrepareCache(_shadowContext, shadowMapsSizeCube, shadowMapsSizeCube, Vector2::Zero);
|
||||
|
||||
// Render depth to all 1 face of the cube map
|
||||
const int32 cubeFaceIndex = 0;
|
||||
{
|
||||
// Set up view
|
||||
_shadowCache.Clear();
|
||||
Matrix::Transpose(_shadowContext.View.ViewProjection(), sperLight.LightShadow.ShadowVP[cubeFaceIndex]);
|
||||
|
||||
// Set render target
|
||||
auto rt = _shadowMapCube->View(cubeFaceIndex);
|
||||
context->ResetSR();
|
||||
context->SetRenderTarget(rt, static_cast<GPUTextureView*>(nullptr));
|
||||
context->ClearDepth(rt);
|
||||
|
||||
// Render actors to the shadow map
|
||||
renderContext.Task->OnCollectDrawCalls(_shadowContext);
|
||||
_shadowCache.SortDrawCalls(_shadowContext, false, DrawCallsListType::Depth);
|
||||
_shadowCache.ExecuteDrawCalls(_shadowContext, DrawCallsListType::Depth);
|
||||
}
|
||||
|
||||
// Restore GPU context
|
||||
context->ResetSR();
|
||||
context->ResetRenderTarget();
|
||||
const Viewport viewport = renderContext.Task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
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(4, renderContext.Buffers->GBuffer3);
|
||||
|
||||
// Setup shader data
|
||||
GBufferPass::SetInputs(view, sperLight.GBuffer);
|
||||
light.SetupLightData(&sperLight.Light, view, true);
|
||||
sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCube;
|
||||
sperLight.LightShadow.Sharpness = light.ShadowsSharpness;
|
||||
sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength * fade);
|
||||
sperLight.LightShadow.NormalOffsetScale = light.ShadowsNormalOffsetScale * NormalOffsetScaleTweak * (1.0f / shadowMapsSizeCube);
|
||||
sperLight.LightShadow.Bias = light.ShadowsDepthBias;
|
||||
sperLight.LightShadow.FadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
|
||||
sperLight.LightShadow.NumCascades = 1;
|
||||
sperLight.LightShadow.CascadeSplits = Vector4::Zero;
|
||||
Matrix::Transpose(view.ViewProjection(), sperLight.ViewProjectionMatrix);
|
||||
sperLight.ContactShadowsDistance = light.ShadowsDistance;
|
||||
sperLight.ContactShadowsLength = view.Flags & ViewFlags::ContactShadows ? light.ContactShadowsLength : 0.0f;
|
||||
|
||||
// Calculate world view projection matrix for the light sphere
|
||||
Matrix world, wvp, matrix;
|
||||
Matrix::Scaling(lightRadius * sphereModelScale, wvp);
|
||||
Matrix::Translation(lightPosition, matrix);
|
||||
Matrix::Multiply(wvp, matrix, world);
|
||||
Matrix::Multiply(world, view.ViewProjection(), wvp);
|
||||
Matrix::Transpose(wvp, sperLight.WVP);
|
||||
|
||||
// Render shadow in screen space
|
||||
context->UpdateCB(shader->GetCB(0), &sperLight);
|
||||
context->BindCB(0, shader->GetCB(0));
|
||||
context->BindCB(1, shader->GetCB(1));
|
||||
context->BindSR(5, _shadowMapCube->View(cubeFaceIndex));
|
||||
context->SetRenderTarget(shadowMask);
|
||||
context->SetState(_psShadowSpot.Get(shadowQuality + (sperLight.ContactShadowsLength > ZeroTolerance ? 4 : 0)));
|
||||
_sphereModel->Render(context);
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->UnBindSR(5);
|
||||
|
||||
// Render volumetric light with shadow
|
||||
VolumetricFogPass::Instance()->RenderLight(renderContext, context, light, _shadowMapCube->View(cubeFaceIndex), sperLight.LightShadow);
|
||||
}
|
||||
|
||||
void ShadowsPass::RenderShadow(RenderContext& renderContext, RendererDirectionalLightData& light, int32 index, GPUTextureView* shadowMask)
|
||||
{
|
||||
PROFILE_GPU_CPU("Shadow");
|
||||
|
||||
// Cache data
|
||||
auto device = GPUDevice::Instance;
|
||||
auto context = device->GetMainContext();
|
||||
auto& view = renderContext.View;
|
||||
auto mainCache = renderContext.List;
|
||||
auto shader = _shader->GetShader();
|
||||
Data sperLight;
|
||||
Vector3 lightDirection = light.Direction;
|
||||
float shadowsDistance = Math::Min(view.Far, light.ShadowsDistance);
|
||||
int32 csmCount = Math::Clamp(light.CascadeCount, 0, MAX_CSM_CASCADES);
|
||||
bool blendCSM = Graphics::AllowCSMBlending;
|
||||
#if USE_EDITOR
|
||||
if (IsRunningRadiancePass)
|
||||
blendCSM = false;
|
||||
#endif
|
||||
|
||||
// Views with orthographic cameras cannot use cascades, we force it to 1 shadow map here
|
||||
if (view.Projection.M44 == 1.0f)
|
||||
csmCount = 1;
|
||||
|
||||
// Calculate cascade splits
|
||||
auto cameraNear = view.Near;
|
||||
auto cameraFar = view.Far;
|
||||
auto cameraRange = cameraFar - cameraNear;
|
||||
float minDistance;
|
||||
float maxDistance;
|
||||
float cascadeSplits[MAX_CSM_CASCADES];
|
||||
{
|
||||
// TODO: use HiZ and get view min/max range to fit cascades better
|
||||
minDistance = cameraNear;
|
||||
maxDistance = cameraNear + shadowsDistance;
|
||||
|
||||
// TODO: expose partition mode?
|
||||
enum class PartitionMode
|
||||
{
|
||||
Manual = 0,
|
||||
Logarithmic = 1,
|
||||
PSSM = 2,
|
||||
};
|
||||
PartitionMode partitionMode = PartitionMode::Manual;
|
||||
float pssmFactor = 0.5f;
|
||||
float splitDistance0 = 0.05f;
|
||||
float splitDistance1 = 0.15f;
|
||||
float splitDistance2 = 0.50f;
|
||||
float splitDistance3 = 1.00f;
|
||||
|
||||
// Compute the split distances based on the partitioning mode
|
||||
if (partitionMode == PartitionMode::Manual)
|
||||
{
|
||||
if (csmCount == 1)
|
||||
{
|
||||
cascadeSplits[0] = minDistance + splitDistance3 * maxDistance;
|
||||
}
|
||||
else if (csmCount == 2)
|
||||
{
|
||||
cascadeSplits[0] = minDistance + splitDistance1 * maxDistance;
|
||||
cascadeSplits[1] = minDistance + splitDistance3 * maxDistance;
|
||||
}
|
||||
else if (csmCount == 3)
|
||||
{
|
||||
cascadeSplits[0] = minDistance + splitDistance0 * maxDistance;
|
||||
cascadeSplits[1] = minDistance + splitDistance2 * maxDistance;
|
||||
cascadeSplits[2] = minDistance + splitDistance3 * maxDistance;
|
||||
}
|
||||
else if (csmCount == 4)
|
||||
{
|
||||
cascadeSplits[0] = minDistance + splitDistance0 * maxDistance;
|
||||
cascadeSplits[1] = minDistance + splitDistance1 * maxDistance;
|
||||
cascadeSplits[2] = minDistance + splitDistance2 * maxDistance;
|
||||
cascadeSplits[3] = minDistance + splitDistance3 * maxDistance;
|
||||
}
|
||||
}
|
||||
else if (partitionMode == PartitionMode::Logarithmic || partitionMode == PartitionMode::PSSM)
|
||||
{
|
||||
const float lambda = partitionMode == PartitionMode::PSSM ? pssmFactor : 1.0f;
|
||||
|
||||
const auto range = maxDistance - minDistance;
|
||||
const auto ratio = maxDistance / minDistance;
|
||||
const auto logRatio = Math::Clamp(1.0f - lambda, 0.0f, 1.0f);
|
||||
for (int32 cascadeLevel = 0; cascadeLevel < csmCount; cascadeLevel++)
|
||||
{
|
||||
// Compute cascade split (between znear and zfar)
|
||||
const float distribute = static_cast<float>(cascadeLevel + 1) / csmCount;
|
||||
float logZ = static_cast<float>(minDistance * powf(ratio, distribute));
|
||||
float uniformZ = minDistance + range * distribute;
|
||||
cascadeSplits[cascadeLevel] = Math::Lerp(uniformZ, logZ, logRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert distance splits to ratios cascade in the range [0, 1]
|
||||
for (int32 i = 0; i < MAX_CSM_CASCADES; i++)
|
||||
{
|
||||
cascadeSplits[i] = (cascadeSplits[i] - cameraNear) / cameraRange;
|
||||
}
|
||||
}
|
||||
|
||||
// Select best Up vector
|
||||
Vector3 upDirection = Vector3::UnitX;
|
||||
Vector3 VectorUps[] = { Vector3::UnitY, Vector3::UnitX, Vector3::UnitZ };
|
||||
for (int32 i = 0; i < ARRAY_COUNT(VectorUps); i++)
|
||||
{
|
||||
const Vector3 vectorUp = VectorUps[i];
|
||||
if (Math::Abs(Vector3::Dot(lightDirection, vectorUp)) < (1.0f - 0.0001f))
|
||||
{
|
||||
const Vector3 side = Vector3::Normalize(Vector3::Cross(vectorUp, lightDirection));
|
||||
upDirection = Vector3::Normalize(Vector3::Cross(lightDirection, side));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary data
|
||||
Vector3 frustumCorners[8];
|
||||
Matrix shadowView, shadowProjection, shadowVP;
|
||||
|
||||
// Set up GPU context and render view
|
||||
const auto shadowMapsSizeCSM = (float)_shadowMapsSizeCSM;
|
||||
context->SetViewportAndScissors(shadowMapsSizeCSM, shadowMapsSizeCSM);
|
||||
_shadowContext.View.PrepareCache(_shadowContext, shadowMapsSizeCSM, shadowMapsSizeCSM, Vector2::Zero);
|
||||
|
||||
// Create the different view and projection matrices for each split
|
||||
float splitMinRatio = 0;
|
||||
float splitMaxRatio = (minDistance - cameraNear) / cameraRange;
|
||||
for (int32 cascadeIndex = 0; cascadeIndex < csmCount; cascadeIndex++)
|
||||
{
|
||||
// Cascade splits
|
||||
const auto oldSplitMinRatio = splitMinRatio;
|
||||
splitMinRatio = splitMaxRatio;
|
||||
splitMaxRatio = cascadeSplits[cascadeIndex];
|
||||
|
||||
// Calculate cascade split frustum corners in view space
|
||||
for (int32 j = 0; j < 4; j++)
|
||||
{
|
||||
// Calculate frustum in WS and VS
|
||||
float overlap = 0;
|
||||
if (blendCSM)
|
||||
overlap = 0.2f * (splitMinRatio - oldSplitMinRatio);
|
||||
|
||||
const auto frustumRangeVS = mainCache->FrustumCornersVs[j + 4] - mainCache->FrustumCornersVs[j];
|
||||
frustumCorners[j] = mainCache->FrustumCornersVs[j] + frustumRangeVS * (splitMinRatio - overlap);
|
||||
frustumCorners[j + 4] = mainCache->FrustumCornersVs[j] + frustumRangeVS * splitMaxRatio;
|
||||
}
|
||||
|
||||
// Perform stabilization (using Projection Snapping)
|
||||
Vector3 cascadeMinBoundLS;
|
||||
Vector3 cascadeMaxBoundLS;
|
||||
Vector3 target;
|
||||
{
|
||||
// Make sure we are using the same direction when stabilizing
|
||||
BoundingSphere boundingVS;
|
||||
BoundingSphere::FromPoints(frustumCorners, ARRAY_COUNT(frustumCorners), boundingVS);
|
||||
|
||||
// Compute bounding box center
|
||||
Vector3::TransformCoordinate(boundingVS.Center, view.IV, target);
|
||||
|
||||
cascadeMaxBoundLS = Vector3(boundingVS.Radius);
|
||||
cascadeMinBoundLS = -cascadeMaxBoundLS;
|
||||
}
|
||||
|
||||
const auto nearClip = -2000.0f;
|
||||
const auto farClip = cascadeMaxBoundLS.Z - cascadeMinBoundLS.Z;
|
||||
|
||||
// Create shadow view matrix
|
||||
Matrix::LookAt(target - lightDirection * cascadeMaxBoundLS.Z, target, upDirection, shadowView);
|
||||
|
||||
// Create viewport for culling with extended near/far planes due to culling issues
|
||||
Matrix cullingVP;
|
||||
{
|
||||
Matrix::OrthoOffCenter(cascadeMinBoundLS.X, cascadeMaxBoundLS.X, cascadeMinBoundLS.Y, cascadeMaxBoundLS.Y, -100000.0f, farClip + 100000.0f, shadowProjection);
|
||||
Matrix::Multiply(shadowView, shadowProjection, cullingVP);
|
||||
}
|
||||
|
||||
// Create shadow projection matrix
|
||||
Matrix::OrthoOffCenter(cascadeMinBoundLS.X, cascadeMaxBoundLS.X, cascadeMinBoundLS.Y, cascadeMaxBoundLS.Y, nearClip, farClip, shadowProjection);
|
||||
|
||||
// Construct shadow matrix (View * Projection)
|
||||
Matrix::Multiply(shadowView, shadowProjection, shadowVP);
|
||||
|
||||
// Stabilize the shadow matrix on the projection
|
||||
{
|
||||
auto shadowPixelPosition = shadowVP.GetTranslation() * (shadowMapsSizeCSM * 0.5f);
|
||||
shadowPixelPosition.Z = 0;
|
||||
const auto shadowPixelPositionRounded = Vector3(Math::Round(shadowPixelPosition.X), Math::Round(shadowPixelPosition.Y), 0.0f);
|
||||
auto shadowPixelOffset = Vector4(shadowPixelPositionRounded - shadowPixelPosition, 0.0f);
|
||||
shadowPixelOffset *= 2.0f / shadowMapsSizeCSM;
|
||||
shadowProjection.SetRow4(shadowProjection.GetRow4() + shadowPixelOffset);
|
||||
Matrix::Multiply(shadowView, shadowProjection, shadowVP);
|
||||
}
|
||||
|
||||
// Transform NDC space [-1,+1]^2 to texture space [0,1]^2
|
||||
{
|
||||
const Matrix T(
|
||||
0.5f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, -0.5f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.5f, 0.5f, 0.0f, 1.0f);
|
||||
Matrix m;
|
||||
Matrix::Multiply(shadowVP, T, m);
|
||||
Matrix::Transpose(m, sperLight.LightShadow.ShadowVP[cascadeIndex]);
|
||||
}
|
||||
|
||||
// Set up view and cache
|
||||
_shadowCache.Clear();
|
||||
_shadowContext.View.Position = -lightDirection * shadowsDistance + view.Position;
|
||||
_shadowContext.View.Direction = lightDirection;
|
||||
_shadowContext.View.SetUp(shadowView, shadowProjection);
|
||||
_shadowContext.View.CullingFrustum.SetMatrix(cullingVP);
|
||||
|
||||
// Set render target
|
||||
const auto rt = _shadowMapCSM->View(cascadeIndex);
|
||||
context->ResetSR();
|
||||
context->SetRenderTarget(rt, static_cast<GPUTextureView*>(nullptr));
|
||||
context->ClearDepth(rt);
|
||||
|
||||
// Render actors to the shadow map
|
||||
renderContext.Task->OnCollectDrawCalls(_shadowContext);
|
||||
_shadowCache.SortDrawCalls(_shadowContext, false, DrawCallsListType::Depth);
|
||||
_shadowCache.ExecuteDrawCalls(_shadowContext, DrawCallsListType::Depth);
|
||||
}
|
||||
|
||||
// Restore GPU context
|
||||
context->ResetSR();
|
||||
context->ResetRenderTarget();
|
||||
const Viewport viewport = renderContext.Task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
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(4, renderContext.Buffers->GBuffer3);
|
||||
|
||||
// Setup shader data
|
||||
GBufferPass::SetInputs(view, sperLight.GBuffer);
|
||||
light.SetupLightData(&sperLight.Light, view, true);
|
||||
sperLight.LightShadow.ShadowMapSize = shadowMapsSizeCSM;
|
||||
sperLight.LightShadow.Sharpness = light.ShadowsSharpness;
|
||||
sperLight.LightShadow.Fade = Math::Saturate(light.ShadowsStrength);
|
||||
sperLight.LightShadow.NormalOffsetScale = light.ShadowsNormalOffsetScale * NormalOffsetScaleTweak * (1.0f / shadowMapsSizeCSM);
|
||||
sperLight.LightShadow.Bias = light.ShadowsDepthBias;
|
||||
sperLight.LightShadow.FadeDistance = Math::Max(light.ShadowsFadeDistance, 0.1f);
|
||||
sperLight.LightShadow.NumCascades = csmCount;
|
||||
sperLight.LightShadow.CascadeSplits = view.Near + Vector4(cascadeSplits) * cameraRange;
|
||||
Matrix::Transpose(view.ViewProjection(), sperLight.ViewProjectionMatrix);
|
||||
sperLight.ContactShadowsDistance = light.ShadowsDistance;
|
||||
sperLight.ContactShadowsLength = view.Flags & ViewFlags::ContactShadows ? light.ContactShadowsLength : 0.0f;
|
||||
|
||||
// Render shadow in screen space
|
||||
context->UpdateCB(shader->GetCB(0), &sperLight);
|
||||
context->BindCB(0, shader->GetCB(0));
|
||||
context->BindCB(1, shader->GetCB(1));
|
||||
context->BindSR(5, _shadowMapCSM->ViewArray());
|
||||
context->SetRenderTarget(shadowMask);
|
||||
context->SetState(_psShadowDir.Get(maxShadowsQuality + static_cast<int32>(Quality::MAX) * blendCSM + (sperLight.ContactShadowsLength > ZeroTolerance ? 8 : 0)));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->UnBindSR(5);
|
||||
|
||||
// Cache params for the volumetric fog or other effects that use dir light shadow sampling
|
||||
LastDirLightIndex = index;
|
||||
LastDirLightShadowMap = _shadowMapCSM->ViewArray();
|
||||
LastDirLight = sperLight.LightShadow;
|
||||
}
|
||||
149
Source/Engine/Renderer/ShadowsPass.h
Normal file
149
Source/Engine/Renderer/ShadowsPass.h
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
#include "RenderList.h"
|
||||
#include "RendererPass.h"
|
||||
#include "Engine/Content/Assets/Shader.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
|
||||
/// <summary>
|
||||
/// Pixel format for fullscreen render target used for shadows calculations
|
||||
/// </summary>
|
||||
#define SHADOWS_PASS_SS_RR_FORMAT PixelFormat::R11G11B10_Float
|
||||
|
||||
/// <summary>
|
||||
/// Shadows rendering service.
|
||||
/// </summary>
|
||||
class ShadowsPass : public RendererPass<ShadowsPass>
|
||||
{
|
||||
private:
|
||||
|
||||
// Shader stuff
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2 * 2> _psShadowDir;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowPoint;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowSpot;
|
||||
|
||||
// Shadow maps stuff
|
||||
int32 _shadowMapsSizeCSM;
|
||||
int32 _shadowMapsSizeCube;
|
||||
GPUTexture* _shadowMapCSM;
|
||||
GPUTexture* _shadowMapCube;
|
||||
Quality _currentShadowMapsQuality;
|
||||
|
||||
// Shadow map rendering stuff
|
||||
RenderContext _shadowContext;
|
||||
RenderList _shadowCache;
|
||||
AssetReference<Model> _sphereModel;
|
||||
|
||||
// Cached state for the current frame rendering (setup via Prepare)
|
||||
int32 maxShadowsQuality;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
ShadowsPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets current GPU memory usage by the shadow maps
|
||||
/// </summary>
|
||||
/// <returns>GPU memory used in bytes</returns>
|
||||
uint64 GetShadowMapsMemoryUsage() const;
|
||||
|
||||
public:
|
||||
|
||||
// TODO: use full scene shadow map atlas with dynamic slots allocation
|
||||
int32 LastDirLightIndex = -1;
|
||||
GPUTextureView* LastDirLightShadowMap = nullptr;
|
||||
LightShadowData LastDirLight;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether can render shadow for the specified light.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="light">The light.</param>
|
||||
/// <returns><c>true</c> if can render shadow for the specified light; otherwise, <c>false</c>.</returns>
|
||||
bool CanRenderShadow(RenderContext& renderContext, const RendererPointLightData& light);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether can render shadow for the specified light.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="light">The light.</param>
|
||||
/// <returns><c>true</c> if can render shadow for the specified light; otherwise, <c>false</c>.</returns>
|
||||
bool CanRenderShadow(RenderContext& renderContext, const RendererSpotLightData& light);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether can render shadow for the specified light.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="light">The light.</param>
|
||||
/// <returns><c>true</c> if can render shadow for the specified light; otherwise, <c>false</c>.</returns>
|
||||
bool CanRenderShadow(RenderContext& renderContext, const RendererDirectionalLightData& light);
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the shadows rendering. Called by the light pass once per frame.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="context">The GPU command context.</param>
|
||||
void Prepare(RenderContext& renderContext, GPUContext* context);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the shadow mask for the given light.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="light">The light.</param>
|
||||
/// <param name="shadowMask">The shadow mask (output).</param>
|
||||
void RenderShadow(RenderContext& renderContext, RendererPointLightData& light, GPUTextureView* shadowMask);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the shadow mask for the given light.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="light">The light.</param>
|
||||
/// <param name="shadowMask">The shadow mask (output).</param>
|
||||
void RenderShadow(RenderContext& renderContext, RendererSpotLightData& light, GPUTextureView* shadowMask);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the shadow mask for the given light.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="light">The light.</param>
|
||||
/// <param name="index">The light index.</param>
|
||||
/// <param name="shadowMask">The shadow mask (output).</param>
|
||||
void RenderShadow(RenderContext& renderContext, RendererDirectionalLightData& light, int32 index, GPUTextureView* shadowMask);
|
||||
|
||||
private:
|
||||
|
||||
void updateShadowMapSize();
|
||||
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psShadowDir.Release();
|
||||
_psShadowPoint.Release();
|
||||
_psShadowSpot.Release();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
157
Source/Engine/Renderer/Utils/BitonicSort.cpp
Normal file
157
Source/Engine/Renderer/Utils/BitonicSort.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "BitonicSort.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
|
||||
#define INDIRECT_ARGS_STRIDE 12
|
||||
|
||||
BitonicSort::BitonicSort()
|
||||
: _dispatchArgsBuffer(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String BitonicSort::ToString() const
|
||||
{
|
||||
return TEXT("BitonicSort");
|
||||
}
|
||||
|
||||
bool BitonicSort::Init()
|
||||
{
|
||||
// Draw indirect and compute shaders support is required for this implementation
|
||||
const auto& limits = GPUDevice::Instance->Limits;
|
||||
if (!limits.HasDrawIndirect || !limits.HasCompute)
|
||||
return false;
|
||||
|
||||
// Create indirect dispatch arguments buffer
|
||||
_dispatchArgsBuffer = GPUDevice::Instance->CreateBuffer(TEXT("BitonicSortDispatchArgs"));
|
||||
if (_dispatchArgsBuffer->Init(GPUBufferDescription::Raw(22 * 23 / 2 * INDIRECT_ARGS_STRIDE, GPUBufferFlags::Argument | GPUBufferFlags::UnorderedAccess)))
|
||||
return true;
|
||||
|
||||
// Load asset
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/BitonicSort"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<BitonicSort, &BitonicSort::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BitonicSort::setupResources()
|
||||
{
|
||||
// Check if shader has not been loaded
|
||||
if (!_shader->IsLoaded())
|
||||
return true;
|
||||
const auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffer size
|
||||
_cb = shader->GetCB(0);
|
||||
if (_cb->GetSize() != sizeof(Data))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cache compute shaders
|
||||
_indirectArgsCS = shader->GetCS("CS_IndirectArgs");
|
||||
_preSortCS = shader->GetCS("CS_PreSort");
|
||||
_innerSortCS = shader->GetCS("CS_InnerSort");
|
||||
_outerSortCS = shader->GetCS("CS_OuterSort");
|
||||
_copyIndicesCS = shader->GetCS("CS_CopyIndices");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BitonicSort::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_dispatchArgsBuffer);
|
||||
_cb = nullptr;
|
||||
_indirectArgsCS = nullptr;
|
||||
_preSortCS = nullptr;
|
||||
_innerSortCS = nullptr;
|
||||
_outerSortCS = nullptr;
|
||||
_copyIndicesCS = nullptr;
|
||||
|
||||
// Release asset
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
void BitonicSort::Sort(GPUContext* context, GPUBuffer* sortingKeysBuffer, GPUBuffer* countBuffer, uint32 counterOffset, bool sortAscending, GPUBuffer* sortedIndicesBuffer)
|
||||
{
|
||||
ASSERT(context && sortingKeysBuffer && countBuffer);
|
||||
|
||||
PROFILE_GPU_CPU("Bitonic Sort");
|
||||
|
||||
// Check if has missing resources
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare
|
||||
const uint32 elementSizeBytes = sizeof(uint64);
|
||||
const uint32 maxNumElements = sortingKeysBuffer->GetSize() / elementSizeBytes;
|
||||
const uint32 alignedMaxNumElements = Math::RoundUpToPowerOf2(maxNumElements);
|
||||
const uint32 maxIterations = (uint32)Math::Log2((float)Math::Max(2048u, alignedMaxNumElements)) - 10;
|
||||
|
||||
// Setup constants buffer
|
||||
Data data;
|
||||
data.CounterOffset = counterOffset;
|
||||
data.NullItem.Key = sortAscending ? MAX_float : -MAX_float;
|
||||
data.NullItem.Value = 0;
|
||||
data.KeySign = sortAscending ? -1.0f : 1.0f;
|
||||
data.MaxIterations = maxIterations;
|
||||
data.LoopK = 0;
|
||||
data.LoopJ = 0;
|
||||
context->UpdateCB(_cb, &data);
|
||||
context->BindCB(0, _cb);
|
||||
|
||||
// Generate execute indirect arguments
|
||||
context->BindSR(0, countBuffer->View());
|
||||
context->BindUA(0, _dispatchArgsBuffer->View());
|
||||
context->Dispatch(_indirectArgsCS, 1, 1, 1);
|
||||
|
||||
// Pre-Sort the buffer up to k = 2048 (this also pads the list with invalid indices that will drift to the end of the sorted list)
|
||||
context->BindUA(0, sortingKeysBuffer->View());
|
||||
context->DispatchIndirect(_preSortCS, _dispatchArgsBuffer, 0);
|
||||
|
||||
// We have already pre-sorted up through k = 2048 when first writing our list, so we continue sorting with k = 4096
|
||||
// For really large values of k, these indirect dispatches will be skipped over with thread counts of 0
|
||||
uint32 indirectArgsOffset = INDIRECT_ARGS_STRIDE;
|
||||
for (uint32 k = 4096; k <= alignedMaxNumElements; k *= 2)
|
||||
{
|
||||
for (uint32 j = k / 2; j >= 2048; j /= 2)
|
||||
{
|
||||
data.LoopK = k;
|
||||
data.LoopJ = j;
|
||||
context->UpdateCB(_cb, &data);
|
||||
context->BindCB(0, _cb);
|
||||
|
||||
context->DispatchIndirect(_outerSortCS, _dispatchArgsBuffer, indirectArgsOffset);
|
||||
indirectArgsOffset += INDIRECT_ARGS_STRIDE;
|
||||
}
|
||||
|
||||
context->DispatchIndirect(_innerSortCS, _dispatchArgsBuffer, indirectArgsOffset);
|
||||
indirectArgsOffset += INDIRECT_ARGS_STRIDE;
|
||||
}
|
||||
|
||||
context->ResetUA();
|
||||
|
||||
if (sortedIndicesBuffer)
|
||||
{
|
||||
// Copy indices to another buffer
|
||||
context->BindSR(1, sortingKeysBuffer->View());
|
||||
context->BindUA(0, sortedIndicesBuffer->View());
|
||||
// TODO: use indirect dispatch to match the items count for copy
|
||||
context->Dispatch(_copyIndicesCS, (alignedMaxNumElements + 1023) / 1024, 1, 1);
|
||||
}
|
||||
|
||||
context->ResetUA();
|
||||
context->ResetSR();
|
||||
}
|
||||
87
Source/Engine/Renderer/Utils/BitonicSort.h
Normal file
87
Source/Engine/Renderer/Utils/BitonicSort.h
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
|
||||
/// <summary>
|
||||
/// Bitonic Sort implementation using GPU compute shaders.
|
||||
/// It has a complexity of O(n*(log n)^2), which is inferior to most
|
||||
/// traditional sorting algorithms, but because GPUs have so many threads,
|
||||
/// and because each thread can be utilized, the algorithm can fully load
|
||||
/// the GPU, taking advantage of its high ALU and bandwidth capabilities.
|
||||
/// </summary>
|
||||
class BitonicSort : public RendererPass<BitonicSort>
|
||||
{
|
||||
public:
|
||||
|
||||
// The sorting keys buffer item structure template. Matches the shader type.
|
||||
struct Item
|
||||
{
|
||||
float Key;
|
||||
uint32 Value;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct Data {
|
||||
Item NullItem;
|
||||
uint32 CounterOffset;
|
||||
uint32 MaxIterations;
|
||||
uint32 LoopK;
|
||||
float KeySign;
|
||||
uint32 LoopJ;
|
||||
float Dummy0;
|
||||
});
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUBuffer* _dispatchArgsBuffer;
|
||||
GPUConstantBuffer* _cb;
|
||||
GPUShaderProgramCS* _indirectArgsCS;
|
||||
GPUShaderProgramCS* _preSortCS;
|
||||
GPUShaderProgramCS* _innerSortCS;
|
||||
GPUShaderProgramCS* _outerSortCS;
|
||||
GPUShaderProgramCS* _copyIndicesCS;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BitonicSort"/> class.
|
||||
/// </summary>
|
||||
BitonicSort();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the specified buffer of index-key pairs.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context.</param>
|
||||
/// <param name="sortingKeysBuffer">The sorting keys buffer. Used as a structured buffer of type Item (see above).</param>
|
||||
/// <param name="countBuffer">The buffer that contains a items counter value.</param>
|
||||
/// <param name="counterOffset">The offset into counter buffer to find count for this list. Must be a multiple of 4 bytes.</param>
|
||||
/// <param name="sortAscending">True to sort in ascending order (smallest to largest), otherwise false to sort in descending order.</param>
|
||||
/// <param name="sortedIndicesBuffer">The output buffer for sorted values extracted from the sorted sortingKeysBuffer after algorithm run. Valid for uint value types - used as RWBuffer.</param>
|
||||
void Sort(GPUContext* context, GPUBuffer* sortingKeysBuffer, GPUBuffer* countBuffer, uint32 counterOffset, bool sortAscending, GPUBuffer* sortedIndicesBuffer);
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_preSortCS = nullptr;
|
||||
_innerSortCS = nullptr;
|
||||
_outerSortCS = nullptr;
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
239
Source/Engine/Renderer/Utils/MultiScaler.cpp
Normal file
239
Source/Engine/Renderer/Utils/MultiScaler.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "MultiScaler.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
|
||||
MultiScaler::MultiScaler()
|
||||
: _psHalfDepth(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String MultiScaler::ToString() const
|
||||
{
|
||||
return TEXT("MultiScaler");
|
||||
}
|
||||
|
||||
bool MultiScaler::Init()
|
||||
{
|
||||
// Create pipeline states
|
||||
_psHalfDepth = GPUDevice::Instance->CreatePipelineState();
|
||||
_psBlur5.CreatePipelineStates();
|
||||
_psBlur9.CreatePipelineStates();
|
||||
_psBlur13.CreatePipelineStates();
|
||||
|
||||
// Load asset
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/MultiScaler"));
|
||||
if (_shader == nullptr)
|
||||
return true;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<MultiScaler, &MultiScaler::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MultiScaler::setupResources()
|
||||
{
|
||||
// Check if shader has not been loaded
|
||||
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 states
|
||||
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
if (!_psBlur5.IsValid())
|
||||
{
|
||||
if (_psBlur5.Create(psDesc, shader, "PS_Blur5"))
|
||||
return true;
|
||||
}
|
||||
if (!_psBlur9.IsValid())
|
||||
{
|
||||
if (_psBlur9.Create(psDesc, shader, "PS_Blur9"))
|
||||
return true;
|
||||
}
|
||||
if (!_psBlur13.IsValid())
|
||||
{
|
||||
if (_psBlur13.Create(psDesc, shader, "PS_Blur13"))
|
||||
return true;
|
||||
}
|
||||
if (!_psHalfDepth->IsValid())
|
||||
{
|
||||
psDesc.PS = shader->GetPS("PS_HalfDepth");
|
||||
psDesc.DepthWriteEnable = true;
|
||||
psDesc.DepthTestEnable = true;
|
||||
psDesc.DepthFunc = ComparisonFunc::Always;
|
||||
if (_psHalfDepth->Init(psDesc))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiScaler::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
SAFE_DELETE_GPU_RESOURCE(_psHalfDepth);
|
||||
_psBlur5.Delete();
|
||||
_psBlur9.Delete();
|
||||
_psBlur13.Delete();
|
||||
|
||||
// Release asset
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* src, GPUTextureView* dst, GPUTextureView* tmp)
|
||||
{
|
||||
PROFILE_GPU_CPU("MultiScaler Filter");
|
||||
|
||||
context->SetViewportAndScissors((float)width, (float)height);
|
||||
|
||||
// Check if has missing resources
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Simple copy
|
||||
context->SetRenderTarget(dst);
|
||||
context->Draw(src);
|
||||
context->ResetRenderTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
// Select filter
|
||||
GPUPipelineStatePermutationsPs<2>* ps;
|
||||
switch (mode)
|
||||
{
|
||||
case FilterMode::GaussianBlur5:
|
||||
ps = &_psBlur5;
|
||||
break;
|
||||
case FilterMode::GaussianBlur9:
|
||||
ps = &_psBlur9;
|
||||
break;
|
||||
case FilterMode::GaussianBlur13:
|
||||
ps = &_psBlur13;
|
||||
break;
|
||||
default:
|
||||
CRASH;
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare
|
||||
Data data;
|
||||
data.TexelSize.X = 1.0f / width;
|
||||
data.TexelSize.Y = 1.0f / height;
|
||||
auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
// Convolve horizontal
|
||||
context->BindSR(0, src);
|
||||
context->SetRenderTarget(tmp);
|
||||
context->SetState(ps->Get(0));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Convolve vertical
|
||||
context->BindSR(0, tmp);
|
||||
context->SetRenderTarget(dst);
|
||||
context->SetState(ps->Get(1));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
void MultiScaler::Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* srcDst, GPUTextureView* tmp)
|
||||
{
|
||||
PROFILE_GPU_CPU("MultiScaler Filter");
|
||||
|
||||
context->SetViewportAndScissors((float)width, (float)height);
|
||||
|
||||
// Check if has missing resources
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Skip
|
||||
return;
|
||||
}
|
||||
|
||||
// Select filter
|
||||
GPUPipelineStatePermutationsPs<2>* ps;
|
||||
switch (mode)
|
||||
{
|
||||
case FilterMode::GaussianBlur5:
|
||||
ps = &_psBlur5;
|
||||
break;
|
||||
case FilterMode::GaussianBlur9:
|
||||
ps = &_psBlur9;
|
||||
break;
|
||||
case FilterMode::GaussianBlur13:
|
||||
ps = &_psBlur13;
|
||||
break;
|
||||
default:
|
||||
CRASH;
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare
|
||||
Data data;
|
||||
data.TexelSize.X = 1.0f / width;
|
||||
data.TexelSize.Y = 1.0f / height;
|
||||
auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
// Convolve horizontal
|
||||
context->BindSR(0, srcDst);
|
||||
context->SetRenderTarget(tmp);
|
||||
context->SetState(ps->Get(0));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
context->ResetRenderTarget();
|
||||
|
||||
// Convolve vertical
|
||||
context->BindSR(0, tmp);
|
||||
context->SetRenderTarget(srcDst);
|
||||
context->SetState(ps->Get(1));
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
context->ResetRenderTarget();
|
||||
}
|
||||
|
||||
void MultiScaler::DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstHeight, GPUTextureView* src, GPUTextureView* dst)
|
||||
{
|
||||
PROFILE_GPU_CPU("Downscale Depth");
|
||||
|
||||
// Check if has missing resources
|
||||
if (checkIfSkipPass())
|
||||
{
|
||||
// Clear the output
|
||||
context->ClearDepth(dst);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare
|
||||
Data data;
|
||||
data.TexelSize.X = 2.0f / dstWidth;
|
||||
data.TexelSize.Y = 2.0f / dstHeight;
|
||||
auto cb = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb, &data);
|
||||
context->BindCB(0, cb);
|
||||
|
||||
// Draw
|
||||
context->SetViewportAndScissors((float)dstWidth, (float)dstHeight);
|
||||
context->SetRenderTarget(dst, (GPUTextureView*)nullptr);
|
||||
context->BindSR(0, src);
|
||||
context->SetState(_psHalfDepth);
|
||||
context->DrawFullscreenTriangle();
|
||||
|
||||
// Cleanup
|
||||
context->ResetRenderTarget();
|
||||
context->UnBindCB(0);
|
||||
}
|
||||
110
Source/Engine/Renderer/Utils/MultiScaler.h
Normal file
110
Source/Engine/Renderer/Utils/MultiScaler.h
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../RendererPass.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
|
||||
/// <summary>
|
||||
/// Scales an input texture to an output texture (down or up, depending on the relative size between input and output). Can perform image blurring.
|
||||
/// </summary>
|
||||
class MultiScaler : public RendererPass<MultiScaler>
|
||||
{
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct Data {
|
||||
Vector2 TexelSize;
|
||||
Vector2 Padding;
|
||||
});
|
||||
|
||||
AssetReference<Shader> _shader;
|
||||
GPUPipelineState* _psHalfDepth;
|
||||
GPUPipelineStatePermutationsPs<2> _psBlur5;
|
||||
GPUPipelineStatePermutationsPs<2> _psBlur9;
|
||||
GPUPipelineStatePermutationsPs<2> _psBlur13;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
MultiScaler();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Filter mode
|
||||
/// </summary>
|
||||
enum class FilterMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Optimized 5-tap gaussian blur with linear sampling (3 texture fetches).
|
||||
/// </summary>
|
||||
GaussianBlur5 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Optimized 9-tap gaussian blur with linear sampling (5 texture fetches).
|
||||
/// </summary>
|
||||
GaussianBlur9 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Optimized 13-tap gaussian blur with linear sampling (7 texture fetches).
|
||||
/// </summary>
|
||||
GaussianBlur13 = 3,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Performs texture filtering.
|
||||
/// </summary>
|
||||
/// <param name="mode">The filter mode.</param>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <param name="width">The output width.</param>
|
||||
/// <param name="height">The output height.</param>
|
||||
/// <param name="src">The source texture.</param>
|
||||
/// <param name="dst">The 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);
|
||||
|
||||
/// <summary>
|
||||
/// Performs texture filtering.
|
||||
/// </summary>
|
||||
/// <param name="mode">The filter mode.</param>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <param name="width">The output width.</param>
|
||||
/// <param name="height">The output height.</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>
|
||||
void Filter(const FilterMode mode, GPUContext* context, const int32 width, const int32 height, GPUTextureView* srcDst, GPUTextureView* tmp);
|
||||
|
||||
/// <summary>
|
||||
/// Downscales the depth buffer (to half resolution).
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</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="src">The source texture.</param>
|
||||
/// <param name="dst">The destination texture.</param>
|
||||
void DownscaleDepth(GPUContext* context, int32 dstWidth, int32 dstHeight, GPUTextureView* src, GPUTextureView* dst);
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psHalfDepth->ReleaseGPU();
|
||||
_psBlur5.Release();
|
||||
_psBlur9.Release();
|
||||
_psBlur13.Release();
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
683
Source/Engine/Renderer/VolumetricFogPass.cpp
Normal file
683
Source/Engine/Renderer/VolumetricFogPass.cpp
Normal file
@@ -0,0 +1,683 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "VolumetricFogPass.h"
|
||||
#include "ShadowsPass.h"
|
||||
#include "GBufferPass.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
#include "Engine/Graphics/RenderBuffers.h"
|
||||
#include "Engine/Graphics/GPULimits.h"
|
||||
#include "Engine/Graphics/RenderTargetPool.h"
|
||||
#include "Engine/Content/Assets/CubeTexture.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
|
||||
// Must match shader source
|
||||
int32 VolumetricFogGridInjectionGroupSize = 4;
|
||||
int32 VolumetricFogIntegrationGroupSize = 8;
|
||||
|
||||
VolumetricFogPass::VolumetricFogPass()
|
||||
: _shader(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
String VolumetricFogPass::ToString() const
|
||||
{
|
||||
return TEXT("VolumetricFogPass");
|
||||
}
|
||||
|
||||
bool VolumetricFogPass::Init()
|
||||
{
|
||||
const auto& limits = GPUDevice::Instance->Limits;
|
||||
_isSupported = limits.HasGeometryShaders && limits.HasVolumeTextureRendering && limits.HasCompute;
|
||||
|
||||
// Create pipeline states
|
||||
_psInjectLight.CreatePipelineStates();
|
||||
|
||||
// Load assets
|
||||
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/VolumetricFog"));
|
||||
if (_shader == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
_shader.Get()->OnReloading.Bind<VolumetricFogPass, &VolumetricFogPass::OnShaderReloading>(this);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VolumetricFogPass::setupResources()
|
||||
{
|
||||
if (!_shader->IsLoaded())
|
||||
return true;
|
||||
auto shader = _shader->GetShader();
|
||||
|
||||
// Validate shader constant buffers sizes
|
||||
if (shader->GetCB(0)->GetSize() != sizeof(Data))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data);
|
||||
return true;
|
||||
}
|
||||
if (shader->GetCB(1)->GetSize() != sizeof(PerLight))
|
||||
{
|
||||
REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 1, PerLight);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cache compute shaders
|
||||
_csInitialize = shader->GetCS("CS_Initialize");
|
||||
_csLightScattering.Get(shader, "CS_LightScattering");
|
||||
_csFinalIntegration = shader->GetCS("CS_FinalIntegration");
|
||||
|
||||
// Create pipeline stages
|
||||
GPUPipelineState::Description psDesc;
|
||||
if (!_psInjectLight.IsValid())
|
||||
{
|
||||
psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
|
||||
psDesc.BlendMode = BlendingMode::Add;
|
||||
psDesc.VS = shader->GetVS("VS_WriteToSlice");
|
||||
psDesc.GS = shader->GetGS("GS_WriteToSlice");
|
||||
if (_psInjectLight.Create(psDesc, shader, "PS_InjectLight"))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void VolumetricFogPass::Dispose()
|
||||
{
|
||||
// Base
|
||||
RendererPass::Dispose();
|
||||
|
||||
// Delete pipeline states
|
||||
_psInjectLight.Delete();
|
||||
|
||||
_csInitialize = nullptr;
|
||||
_csLightScattering.Clear();
|
||||
_csFinalIntegration = nullptr;
|
||||
|
||||
SAFE_DELETE_GPU_RESOURCE(_vbCircleRasterize);
|
||||
SAFE_DELETE_GPU_RESOURCE(_ibCircleRasterize);
|
||||
|
||||
// Release assets
|
||||
_shader.Unlink();
|
||||
}
|
||||
|
||||
float ComputeZSliceFromDepth(float SceneDepth, const VolumetricFogOptions& options, int32 GridSizeZ)
|
||||
{
|
||||
// This must match frustum voxels depth distribution
|
||||
// See ComputeNormalizedZSliceFromDepth() in VolumetricFog.shader
|
||||
|
||||
return SceneDepth / options.Distance * GridSizeZ;
|
||||
}
|
||||
|
||||
bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options)
|
||||
{
|
||||
auto& view = renderContext.View;
|
||||
const auto fog = renderContext.List->Fog;
|
||||
|
||||
// Check if already prepared for this frame
|
||||
if (renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount)
|
||||
{
|
||||
fog->GetVolumetricFogOptions(options);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if skip rendering
|
||||
if (fog == nullptr || (view.Flags & ViewFlags::Fog) == 0 || !_isSupported || checkIfSkipPass())
|
||||
{
|
||||
RenderTargetPool::Release(renderContext.Buffers->VolumetricFog);
|
||||
renderContext.Buffers->VolumetricFog = nullptr;
|
||||
renderContext.Buffers->LastFrameVolumetricFog = 0;
|
||||
return true;
|
||||
}
|
||||
fog->GetVolumetricFogOptions(options);
|
||||
if (!options.UseVolumetricFog())
|
||||
{
|
||||
RenderTargetPool::Release(renderContext.Buffers->VolumetricFog);
|
||||
renderContext.Buffers->VolumetricFog = nullptr;
|
||||
renderContext.Buffers->LastFrameVolumetricFog = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setup configuration
|
||||
_cache.HistoryWeight = 0.9f;
|
||||
_cache.InverseSquaredLightDistanceBiasScale = 1.0f;
|
||||
const auto quality = Graphics::VolumetricFogQuality;
|
||||
switch (quality)
|
||||
{
|
||||
case Quality::Low:
|
||||
{
|
||||
_cache.GridPixelSize = 16;
|
||||
_cache.GridSizeZ = 64;
|
||||
_cache.FogJitter = false;
|
||||
_cache.TemporalReprojection = false;
|
||||
_cache.HistoryMissSupersampleCount = 1;
|
||||
break;
|
||||
}
|
||||
case Quality::Medium:
|
||||
{
|
||||
_cache.GridPixelSize = 16;
|
||||
_cache.GridSizeZ = 64;
|
||||
_cache.FogJitter = true;
|
||||
_cache.TemporalReprojection = true;
|
||||
_cache.HistoryMissSupersampleCount = 4;
|
||||
break;
|
||||
}
|
||||
case Quality::High:
|
||||
{
|
||||
_cache.GridPixelSize = 16;
|
||||
_cache.GridSizeZ = 128;
|
||||
_cache.FogJitter = true;
|
||||
_cache.TemporalReprojection = true;
|
||||
_cache.HistoryMissSupersampleCount = 4;
|
||||
break;
|
||||
}
|
||||
case Quality::Ultra:
|
||||
{
|
||||
_cache.GridPixelSize = 8;
|
||||
_cache.GridSizeZ = 256;
|
||||
_cache.FogJitter = true;
|
||||
_cache.TemporalReprojection = true;
|
||||
_cache.HistoryMissSupersampleCount = 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare
|
||||
const int32 width = renderContext.Buffers->GetWidth();
|
||||
const int32 height = renderContext.Buffers->GetHeight();
|
||||
_cache.GridSize = Vector3(
|
||||
(float)Math::DivideAndRoundUp(width, _cache.GridPixelSize),
|
||||
(float)Math::DivideAndRoundUp(height, _cache.GridPixelSize),
|
||||
(float)_cache.GridSizeZ);
|
||||
auto& fogData = renderContext.Buffers->VolumetricFogData;
|
||||
fogData.MaxDistance = options.Distance;
|
||||
|
||||
// Init data (partial, without directional light or sky light data);
|
||||
GBufferPass::SetInputs(renderContext.View, _cache.Data.GBuffer);
|
||||
_cache.Data.GlobalAlbedo = options.Albedo.ToVector3() * options.Albedo.A;
|
||||
_cache.Data.GlobalExtinctionScale = options.ExtinctionScale;
|
||||
_cache.Data.GlobalEmissive = options.Emissive.ToVector3() * options.Emissive.A;
|
||||
_cache.Data.GridSize = _cache.GridSize;
|
||||
_cache.Data.GridSizeIntX = (int32)_cache.GridSize.X;
|
||||
_cache.Data.GridSizeIntY = (int32)_cache.GridSize.Y;
|
||||
_cache.Data.GridSizeIntZ = (int32)_cache.GridSize.Z;
|
||||
_cache.Data.HistoryWeight = _cache.HistoryWeight;
|
||||
_cache.Data.FogParameters = options.FogParameters;
|
||||
_cache.Data.InverseSquaredLightDistanceBiasScale = _cache.InverseSquaredLightDistanceBiasScale;
|
||||
_cache.Data.PhaseG = options.ScatteringDistribution;
|
||||
_cache.Data.VolumetricFogMaxDistance = options.Distance;
|
||||
_cache.Data.HistoryMissSuperSampleCount = Math::Clamp(_cache.HistoryMissSupersampleCount, 1, (int32)ARRAY_COUNT(_cache.Data.FrameJitterOffsets));
|
||||
Matrix::Transpose(view.PrevViewProjection, _cache.Data.PrevWorldToClip);
|
||||
_cache.Data.DirectionalLightShadow.NumCascades = 0;
|
||||
_cache.Data.SkyLight.VolumetricScatteringIntensity = 0;
|
||||
|
||||
// Fill frame jitter history
|
||||
const Vector4 defaultOffset = Vector4(0.5f, 0.5f, 0.5f, 0.0f);
|
||||
for (int32 i = 0; i < ARRAY_COUNT(_cache.Data.FrameJitterOffsets); i++)
|
||||
{
|
||||
_cache.Data.FrameJitterOffsets[i] = defaultOffset;
|
||||
}
|
||||
if (_cache.FogJitter && _cache.TemporalReprojection)
|
||||
{
|
||||
for (int32 i = 0; i < _cache.HistoryMissSupersampleCount; i++)
|
||||
{
|
||||
const uint64 frameNumber = renderContext.Task->LastUsedFrame - i;
|
||||
_cache.Data.FrameJitterOffsets[i] = Vector4(
|
||||
RendererUtils::TemporalHalton(frameNumber & 1023, 2),
|
||||
RendererUtils::TemporalHalton(frameNumber & 1023, 3),
|
||||
RendererUtils::TemporalHalton(frameNumber & 1023, 5),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set constant buffer data
|
||||
auto cb0 = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb0, &_cache.Data);
|
||||
|
||||
// Clear local lights scattering table if was used and will be probably reused later
|
||||
if (renderContext.Buffers->LocalShadowedLightScattering)
|
||||
{
|
||||
if (Vector3::NearEqual(renderContext.Buffers->LocalShadowedLightScattering->Size3(), _cache.GridSize))
|
||||
{
|
||||
context->Clear(renderContext.Buffers->LocalShadowedLightScattering->ViewVolume(), Color::Transparent);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderTargetPool::Release(renderContext.Buffers->LocalShadowedLightScattering);
|
||||
renderContext.Buffers->LocalShadowedLightScattering = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Render fog this frame
|
||||
renderContext.Buffers->LastFrameVolumetricFog = Engine::FrameCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
GPUTextureView* VolumetricFogPass::GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) const
|
||||
{
|
||||
if (renderContext.Buffers->LocalShadowedLightScattering == nullptr)
|
||||
{
|
||||
ASSERT(renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount);
|
||||
|
||||
const GPUTextureDescription volumeDescRGB = GPUTextureDescription::New3D(_cache.GridSize, PixelFormat::R11G11B10_Float, GPUTextureFlags::RenderTarget | GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess);
|
||||
|
||||
const auto texture = RenderTargetPool::Get(volumeDescRGB);
|
||||
|
||||
renderContext.Buffers->LocalShadowedLightScattering = texture;
|
||||
|
||||
context->Clear(texture->ViewVolume(), Color::Transparent);
|
||||
}
|
||||
|
||||
return renderContext.Buffers->LocalShadowedLightScattering->ViewVolume();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, T& light, LightShadowData& shadow)
|
||||
{
|
||||
// Prepare
|
||||
VolumetricFogOptions options;
|
||||
if (Init(renderContext, context, options))
|
||||
return;
|
||||
auto& view = renderContext.View;
|
||||
|
||||
// Calculate light volume bounds in camera frustum depth range (min and max)
|
||||
BoundingSphere bounds(light.Position, light.Radius);
|
||||
Vector3 viewSpaceLightBoundsOrigin = Vector3::Transform(bounds.Center, view.View);
|
||||
float furthestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z + bounds.Radius, options, _cache.GridSizeZ);
|
||||
float closestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z - bounds.Radius, options, _cache.GridSizeZ);
|
||||
int32 volumeZBoundsMin = (int32)Math::Clamp(closestSliceIndexUnclamped, 0.0f, _cache.GridSize.Z - 1.0f);
|
||||
int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, _cache.GridSize.Z - 1.0f);
|
||||
|
||||
// Cull light
|
||||
if ((view.Position - bounds.Center).LengthSquared() >= (options.Distance + bounds.Radius) * (options.Distance + bounds.Radius) || volumeZBoundsMin >= volumeZBoundsMax)
|
||||
return;
|
||||
|
||||
PROFILE_GPU_CPU("Volumetric Fog Light");
|
||||
|
||||
// Allocate temporary buffer for light scattering injection
|
||||
auto localShadowedLightScattering = GetLocalShadowedLightScattering(renderContext, context, options);
|
||||
|
||||
// Prepare
|
||||
PerLight perLight;
|
||||
auto cb0 = _shader->GetShader()->GetCB(0);
|
||||
auto cb1 = _shader->GetShader()->GetCB(1);
|
||||
|
||||
// Bind the output
|
||||
context->SetRenderTarget(localShadowedLightScattering);
|
||||
context->SetViewportAndScissors((float)_cache.Data.GridSizeIntX, (float)_cache.Data.GridSizeIntY);
|
||||
|
||||
// Setup data
|
||||
perLight.MinZ = volumeZBoundsMin;
|
||||
perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity;
|
||||
perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius);
|
||||
Matrix::Transpose(view.Projection, perLight.ViewToVolumeClip);
|
||||
light.SetupLightData(&perLight.LocalLight, view, true);
|
||||
perLight.LocalLightShadow = shadow;
|
||||
|
||||
// Upload data
|
||||
context->UpdateCB(cb1, &perLight);
|
||||
context->BindCB(0, cb0);
|
||||
context->BindCB(1, cb1);
|
||||
|
||||
// Ensure to have valid buffers created
|
||||
if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr)
|
||||
InitCircleRasterizeBuffers();
|
||||
|
||||
// Call rendering to the volume
|
||||
const int32 psIndex = (_cache.TemporalReprojection ? 1 : 0) + 2;
|
||||
context->SetState(_psInjectLight.Get(psIndex));
|
||||
const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin;
|
||||
const int32 indexCount = _ibCircleRasterize->GetElementsCount();
|
||||
ASSERT(instanceCount > 0);
|
||||
context->BindVB(ToSpan(&_vbCircleRasterize, 1));
|
||||
context->BindIB(_ibCircleRasterize);
|
||||
context->DrawIndexedInstanced(indexCount, instanceCount, 0);
|
||||
|
||||
// Cleanup
|
||||
context->UnBindCB(0);
|
||||
context->UnBindCB(1);
|
||||
auto viewport = renderContext.Task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
context->ResetRenderTarget();
|
||||
context->FlushState();
|
||||
|
||||
// Mark as rendered
|
||||
light.RenderedVolumetricFog = 1;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void VolumetricFogPass::RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb1)
|
||||
{
|
||||
const BoundingSphere bounds(light.Position, light.Radius);
|
||||
auto& cache = _cache;
|
||||
|
||||
// Calculate light volume bounds in camera frustum depth range (min and max)
|
||||
const Vector3 viewSpaceLightBoundsOrigin = Vector3::Transform(bounds.Center, view.View);
|
||||
const float furthestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z + bounds.Radius, options, cache.GridSizeZ);
|
||||
const float closestSliceIndexUnclamped = ComputeZSliceFromDepth(viewSpaceLightBoundsOrigin.Z - bounds.Radius, options, cache.GridSizeZ);
|
||||
const int32 volumeZBoundsMin = (int32)Math::Clamp(closestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f);
|
||||
const int32 volumeZBoundsMax = (int32)Math::Clamp(furthestSliceIndexUnclamped, 0.0f, cache.GridSize.Z - 1.0f);
|
||||
|
||||
if (volumeZBoundsMin < volumeZBoundsMax)
|
||||
{
|
||||
// TODO: use full scene shadows atlas and render point/spot lights with shadow into a fog volume
|
||||
bool withShadow = false;
|
||||
|
||||
// Setup data
|
||||
perLight.MinZ = volumeZBoundsMin;
|
||||
perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity;
|
||||
perLight.ViewSpaceBoundingSphere = Vector4(viewSpaceLightBoundsOrigin, bounds.Radius);
|
||||
Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip);
|
||||
light.SetupLightData(&perLight.LocalLight, renderContext.View, withShadow);
|
||||
|
||||
// Upload data
|
||||
context->UpdateCB(cb1, &perLight);
|
||||
context->BindCB(1, cb1);
|
||||
|
||||
// Ensure to have valid buffers created
|
||||
if (_vbCircleRasterize == nullptr || _ibCircleRasterize == nullptr)
|
||||
InitCircleRasterizeBuffers();
|
||||
|
||||
// Call rendering to the volume
|
||||
const int32 psIndex = (cache.TemporalReprojection ? 1 : 0) + (withShadow ? 2 : 0);
|
||||
context->SetState(_psInjectLight.Get(psIndex));
|
||||
const int32 instanceCount = volumeZBoundsMax - volumeZBoundsMin;
|
||||
const int32 indexCount = _ibCircleRasterize->GetElementsCount();
|
||||
context->BindVB(ToSpan(&_vbCircleRasterize, 1));
|
||||
context->BindIB(_ibCircleRasterize);
|
||||
context->DrawIndexedInstanced(indexCount, instanceCount, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void VolumetricFogPass::RenderLight(RenderContext& renderContext, GPUContext* context, RendererPointLightData& light, GPUTextureView* shadowMap, LightShadowData& shadow)
|
||||
{
|
||||
// Skip lights with no volumetric light influence or not casting volumetric shadow
|
||||
if (light.VolumetricScatteringIntensity <= ZeroTolerance || !light.CastVolumetricShadow)
|
||||
return;
|
||||
ASSERT(shadowMap);
|
||||
|
||||
context->BindSR(5, shadowMap);
|
||||
|
||||
RenderRadialLight(renderContext, context, light, shadow);
|
||||
|
||||
context->UnBindSR(5);
|
||||
}
|
||||
|
||||
void VolumetricFogPass::RenderLight(RenderContext& renderContext, GPUContext* context, RendererSpotLightData& light, GPUTextureView* shadowMap, LightShadowData& shadow)
|
||||
{
|
||||
// Skip lights with no volumetric light influence or not casting volumetric shadow
|
||||
if (light.VolumetricScatteringIntensity <= ZeroTolerance || !light.CastVolumetricShadow)
|
||||
return;
|
||||
ASSERT(shadowMap);
|
||||
|
||||
context->BindSR(6, shadowMap);
|
||||
|
||||
RenderRadialLight(renderContext, context, light, shadow);
|
||||
|
||||
context->UnBindSR(6);
|
||||
}
|
||||
|
||||
void VolumetricFogPass::Render(RenderContext& renderContext)
|
||||
{
|
||||
// Prepare
|
||||
VolumetricFogOptions options;
|
||||
auto context = GPUDevice::Instance->GetMainContext();
|
||||
if (Init(renderContext, context, options))
|
||||
return;
|
||||
auto& view = renderContext.View;
|
||||
auto& cache = _cache;
|
||||
|
||||
PROFILE_GPU_CPU("Volumetric Fog");
|
||||
|
||||
// TODO: test exponential depth distribution (should give better quality near the camera)
|
||||
// TODO: use tiled light culling and render unshadowed lights in single pass
|
||||
|
||||
// Init directional light data
|
||||
GPUTextureView* dirLightShadowMap = nullptr;
|
||||
if (renderContext.List->DirectionalLights.HasItems())
|
||||
{
|
||||
const int32 dirLightIndex = (int32)renderContext.List->DirectionalLights.Count() - 1;
|
||||
const auto& dirLight = renderContext.List->DirectionalLights[dirLightIndex];
|
||||
const float brightness = dirLight.VolumetricScatteringIntensity;
|
||||
|
||||
if (brightness > ZeroTolerance)
|
||||
{
|
||||
const auto shadowPass = ShadowsPass::Instance();
|
||||
const bool useShadow = dirLight.CastVolumetricShadow && shadowPass->LastDirLightIndex == dirLightIndex;
|
||||
dirLight.SetupLightData(&_cache.Data.DirectionalLight, view, useShadow);
|
||||
_cache.Data.DirectionalLight.Color *= brightness;
|
||||
if (useShadow)
|
||||
{
|
||||
_cache.Data.DirectionalLightShadow = shadowPass->LastDirLight;
|
||||
dirLightShadowMap = shadowPass->LastDirLightShadowMap;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.Data.DirectionalLightShadow.NumCascades = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init sky light data
|
||||
GPUTexture* skyLightImage = nullptr;
|
||||
if (renderContext.List->SkyLights.HasItems())
|
||||
{
|
||||
const auto& skyLight = renderContext.List->SkyLights.Last();
|
||||
|
||||
if (skyLight.VolumetricScatteringIntensity > ZeroTolerance)
|
||||
{
|
||||
_cache.Data.SkyLight.MultiplyColor = skyLight.Color;
|
||||
_cache.Data.SkyLight.AdditiveColor = skyLight.AdditiveColor;
|
||||
_cache.Data.SkyLight.VolumetricScatteringIntensity = skyLight.VolumetricScatteringIntensity;
|
||||
const auto source = skyLight.Image;
|
||||
skyLightImage = source ? source->GetTexture() : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Set constant buffer data
|
||||
auto cb0 = _shader->GetShader()->GetCB(0);
|
||||
context->UpdateCB(cb0, &_cache.Data);
|
||||
context->BindCB(0, cb0);
|
||||
|
||||
// Peek flags
|
||||
const bool temporalHistoryIsValid = cache.TemporalReprojection
|
||||
&& renderContext.Buffers->VolumetricFogHistory
|
||||
&& !renderContext.Task->IsCameraCut
|
||||
&& Vector3::NearEqual(renderContext.Buffers->VolumetricFogHistory->Size3(), cache.GridSize);
|
||||
|
||||
// Allocate buffers
|
||||
const GPUTextureDescription volumeDesc = GPUTextureDescription::New3D(cache.GridSize, PixelFormat::R16G16B16A16_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess);
|
||||
const GPUTextureDescription volumeDescRGB = GPUTextureDescription::New3D(cache.GridSize, PixelFormat::R11G11B10_Float, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess);
|
||||
auto vBufferA = RenderTargetPool::Get(volumeDesc);
|
||||
auto vBufferB = RenderTargetPool::Get(volumeDescRGB);
|
||||
const auto lightScattering = RenderTargetPool::Get(volumeDesc);
|
||||
|
||||
int32 groupCountX = Math::DivideAndRoundUp((int32)cache.GridSize.X, VolumetricFogGridInjectionGroupSize);
|
||||
int32 groupCountY = Math::DivideAndRoundUp((int32)cache.GridSize.Y, VolumetricFogGridInjectionGroupSize);
|
||||
int32 groupCountZ = Math::DivideAndRoundUp((int32)cache.GridSize.Z, VolumetricFogGridInjectionGroupSize);
|
||||
|
||||
// Initialize fog volume properties
|
||||
{
|
||||
PROFILE_GPU("Initialize");
|
||||
|
||||
context->ResetRenderTarget();
|
||||
context->BindUA(0, vBufferA->ViewVolume());
|
||||
context->BindUA(1, vBufferB->ViewVolume());
|
||||
|
||||
context->Dispatch(_csInitialize, groupCountX, groupCountY, groupCountZ);
|
||||
}
|
||||
|
||||
// Voxelize local fog particles
|
||||
// TODO: support rendering volume particles with custom Albedo/Extinction/Emissive
|
||||
|
||||
// Unbind fog attributes tables
|
||||
context->UnBindUA(0);
|
||||
context->UnBindUA(1);
|
||||
context->FlushState();
|
||||
|
||||
// Render Lights
|
||||
GPUTextureView* localShadowedLightScattering = nullptr;
|
||||
{
|
||||
// Get lights to render
|
||||
Array<const RendererPointLightData*> pointLights;
|
||||
Array<const RendererSpotLightData*> spotLights;
|
||||
for (int32 i = 0; i < renderContext.List->PointLights.Count(); i++)
|
||||
{
|
||||
const auto& light = renderContext.List->PointLights[i];
|
||||
if (light.VolumetricScatteringIntensity > ZeroTolerance && !light.RenderedVolumetricFog)
|
||||
{
|
||||
if ((view.Position - light.Position).LengthSquared() < (options.Distance + light.Radius) * (options.Distance + light.Radius))
|
||||
{
|
||||
pointLights.Add(&light);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < renderContext.List->SpotLights.Count(); i++)
|
||||
{
|
||||
const auto& light = renderContext.List->SpotLights[i];
|
||||
if (light.VolumetricScatteringIntensity > ZeroTolerance && !light.RenderedVolumetricFog)
|
||||
{
|
||||
if ((view.Position - light.Position).LengthSquared() < (options.Distance + light.Radius) * (options.Distance + light.Radius))
|
||||
{
|
||||
spotLights.Add(&light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if no lights to render
|
||||
if (pointLights.Count() + spotLights.Count())
|
||||
{
|
||||
PROFILE_GPU("Render Lights");
|
||||
|
||||
// Allocate temporary buffer for light scattering injection
|
||||
localShadowedLightScattering = GetLocalShadowedLightScattering(renderContext, context, options);
|
||||
|
||||
// Prepare
|
||||
PerLight perLight;
|
||||
auto cb1 = _shader->GetShader()->GetCB(1);
|
||||
|
||||
// Bind the output
|
||||
context->SetRenderTarget(localShadowedLightScattering);
|
||||
context->SetViewportAndScissors((float)volumeDesc.Width, (float)volumeDesc.Height);
|
||||
|
||||
// Render them to the volume
|
||||
for (int32 i = 0; i < pointLights.Count(); i++)
|
||||
RenderRadialLight(renderContext, context, view, options, *pointLights[i], perLight, cb1);
|
||||
for (int32 i = 0; i < spotLights.Count(); i++)
|
||||
RenderRadialLight(renderContext, context, view, options, *spotLights[i], perLight, cb1);
|
||||
|
||||
// Cleanup
|
||||
context->UnBindCB(1);
|
||||
context->ResetRenderTarget();
|
||||
context->FlushState();
|
||||
}
|
||||
else if (renderContext.Buffers->LocalShadowedLightScattering)
|
||||
{
|
||||
localShadowedLightScattering = renderContext.Buffers->LocalShadowedLightScattering->ViewVolume();
|
||||
}
|
||||
}
|
||||
|
||||
// Light Scattering
|
||||
{
|
||||
PROFILE_GPU("Light Scattering");
|
||||
|
||||
const auto lightScatteringHistory = temporalHistoryIsValid ? renderContext.Buffers->VolumetricFogHistory : nullptr;
|
||||
|
||||
context->BindUA(0, lightScattering->ViewVolume());
|
||||
context->BindSR(0, vBufferA->ViewVolume());
|
||||
context->BindSR(1, vBufferB->ViewVolume());
|
||||
context->BindSR(2, lightScatteringHistory ? lightScatteringHistory->ViewVolume() : nullptr);
|
||||
context->BindSR(3, localShadowedLightScattering);
|
||||
context->BindSR(4, dirLightShadowMap);
|
||||
context->BindSR(5, skyLightImage);
|
||||
|
||||
const int32 csIndex = cache.TemporalReprojection ? 1 : 0;
|
||||
context->Dispatch(_csLightScattering.Get(csIndex), groupCountX, groupCountY, groupCountZ);
|
||||
}
|
||||
|
||||
// Release resources
|
||||
RenderTargetPool::Release(vBufferA);
|
||||
RenderTargetPool::Release(vBufferB);
|
||||
|
||||
// Update the temporal history buffer
|
||||
if (renderContext.Buffers->VolumetricFogHistory)
|
||||
{
|
||||
RenderTargetPool::Release(renderContext.Buffers->VolumetricFogHistory);
|
||||
}
|
||||
renderContext.Buffers->VolumetricFogHistory = lightScattering;
|
||||
|
||||
// Get buffer for the integrated light scattering (try to reuse the previous frame if it's valid)
|
||||
GPUTexture* integratedLightScattering = renderContext.Buffers->VolumetricFog;
|
||||
if (integratedLightScattering == nullptr || !Vector3::NearEqual(integratedLightScattering->Size3(), cache.GridSize))
|
||||
{
|
||||
if (integratedLightScattering)
|
||||
{
|
||||
RenderTargetPool::Release(integratedLightScattering);
|
||||
}
|
||||
integratedLightScattering = RenderTargetPool::Get(volumeDesc);
|
||||
renderContext.Buffers->VolumetricFog = integratedLightScattering;
|
||||
}
|
||||
renderContext.Buffers->LastFrameVolumetricFog = Engine::FrameCount;
|
||||
|
||||
groupCountX = Math::DivideAndRoundUp((int32)cache.GridSize.X, VolumetricFogIntegrationGroupSize);
|
||||
groupCountY = Math::DivideAndRoundUp((int32)cache.GridSize.Y, VolumetricFogIntegrationGroupSize);
|
||||
|
||||
// Final Integration
|
||||
{
|
||||
PROFILE_GPU("Final Integration");
|
||||
|
||||
context->ResetSR();
|
||||
context->BindUA(0, integratedLightScattering->ViewVolume());
|
||||
context->FlushState();
|
||||
context->BindSR(0, lightScattering->ViewVolume());
|
||||
|
||||
context->Dispatch(_csFinalIntegration, groupCountX, groupCountY, 1);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
context->UnBindUA(0);
|
||||
context->ResetRenderTarget();
|
||||
auto viewport = renderContext.Task->GetViewport();
|
||||
context->SetViewportAndScissors(viewport);
|
||||
context->FlushState();
|
||||
}
|
||||
|
||||
void VolumetricFogPass::InitCircleRasterizeBuffers()
|
||||
{
|
||||
const int32 vertices = 8;
|
||||
const int32 triangles = vertices - 2;
|
||||
const int32 rings = vertices;
|
||||
const float radiansPerRingSegment = PI / (float)rings;
|
||||
|
||||
Vector2 vbData[vertices];
|
||||
uint16 ibData[triangles * 3];
|
||||
|
||||
// Boost the effective radius so that the edges of the circle approximation lie on the circle, instead of the vertices
|
||||
const float radiusScale = 1.0f / Math::Cos(radiansPerRingSegment);
|
||||
|
||||
for (int32 vertexIndex = 0; vertexIndex < vertices; vertexIndex++)
|
||||
{
|
||||
const float angle = vertexIndex / static_cast<float>(vertices - 1) * 2 * PI;
|
||||
vbData[vertexIndex] = Vector2(radiusScale * Math::Cos(angle) * 0.5f + 0.5f, radiusScale * Math::Sin(angle) * 0.5f + 0.5f);
|
||||
}
|
||||
int32 ibIndex = 0;
|
||||
for (int32 triangleIndex = 0; triangleIndex < triangles; triangleIndex++)
|
||||
{
|
||||
const int32 firstVertexIndex = triangleIndex + 2;
|
||||
ibData[ibIndex++] = 0;
|
||||
ibData[ibIndex++] = firstVertexIndex - 1;
|
||||
ibData[ibIndex++] = firstVertexIndex;
|
||||
}
|
||||
|
||||
// Create buffers
|
||||
ASSERT(_vbCircleRasterize == nullptr && _ibCircleRasterize == nullptr);
|
||||
_vbCircleRasterize = GPUDevice::Instance->CreateBuffer(TEXT("VolumetricFog.CircleRasterize.VB"));
|
||||
_ibCircleRasterize = GPUDevice::Instance->CreateBuffer(TEXT("VolumetricFog.CircleRasterize.IB"));
|
||||
if (_vbCircleRasterize->Init(GPUBufferDescription::Vertex(sizeof(Vector2), vertices, vbData))
|
||||
|| _ibCircleRasterize->Init(GPUBufferDescription::Index(sizeof(uint16), triangles * 3, ibData)))
|
||||
{
|
||||
LOG(Fatal, "Failed to setup volumetric fog buffers.");
|
||||
}
|
||||
}
|
||||
197
Source/Engine/Renderer/VolumetricFogPass.h
Normal file
197
Source/Engine/Renderer/VolumetricFogPass.h
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Graphics/RenderView.h"
|
||||
#include "Engine/Graphics/GPUPipelineStatePermutations.h"
|
||||
#include "RendererPass.h"
|
||||
|
||||
/// <summary>
|
||||
/// Volumetric fog rendering service.
|
||||
/// </summary>
|
||||
class VolumetricFogPass : public RendererPass<VolumetricFogPass>
|
||||
{
|
||||
private:
|
||||
|
||||
PACK_STRUCT(struct SkyLightData {
|
||||
Vector3 MultiplyColor;
|
||||
float VolumetricScatteringIntensity;
|
||||
Vector3 AdditiveColor;
|
||||
float Dummt0;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct Data {
|
||||
GBufferData GBuffer;
|
||||
|
||||
Vector3 GlobalAlbedo;
|
||||
float GlobalExtinctionScale;
|
||||
|
||||
Vector3 GlobalEmissive;
|
||||
float HistoryWeight;
|
||||
|
||||
Vector3 GridSize;
|
||||
uint32 HistoryMissSuperSampleCount;
|
||||
|
||||
int32 GridSizeIntX;
|
||||
int32 GridSizeIntY;
|
||||
int32 GridSizeIntZ;
|
||||
float PhaseG;
|
||||
|
||||
Vector2 Dummy0;
|
||||
float VolumetricFogMaxDistance;
|
||||
float InverseSquaredLightDistanceBiasScale;
|
||||
|
||||
Vector4 FogParameters;
|
||||
|
||||
Matrix PrevWorldToClip;
|
||||
|
||||
Vector4 FrameJitterOffsets[8];
|
||||
|
||||
LightData DirectionalLight;
|
||||
LightShadowData DirectionalLightShadow;
|
||||
SkyLightData SkyLight;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct PerLight {
|
||||
Vector2 Dummy1;
|
||||
int32 MinZ;
|
||||
float LocalLightScatteringIntensity;
|
||||
|
||||
Vector4 ViewSpaceBoundingSphere;
|
||||
Matrix ViewToVolumeClip;
|
||||
|
||||
LightData LocalLight;
|
||||
LightShadowData LocalLightShadow;
|
||||
});
|
||||
|
||||
// Shader stuff
|
||||
AssetReference<Shader> _shader;
|
||||
GPUShaderProgramCS* _csInitialize = nullptr;
|
||||
ComputeShaderPermutation<2> _csLightScattering;
|
||||
GPUShaderProgramCS* _csFinalIntegration = nullptr;
|
||||
GPUPipelineStatePermutationsPs<4> _psInjectLight;
|
||||
|
||||
GPUBuffer* _vbCircleRasterize = nullptr;
|
||||
GPUBuffer* _ibCircleRasterize = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// The current frame cache (initialized during Init)
|
||||
/// </summary>
|
||||
struct FrameCache
|
||||
{
|
||||
/// <summary>
|
||||
/// The XY size of a cell in the voxel grid, in pixels.
|
||||
/// </summary>
|
||||
int32 GridPixelSize;
|
||||
|
||||
/// <summary>
|
||||
/// How many Volumetric Fog cells to use in z (depth from camera).
|
||||
/// </summary>
|
||||
int32 GridSizeZ;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to apply jitter to each frame's volumetric fog. Should be used with temporal reprojection to improve the quality.
|
||||
/// </summary>
|
||||
bool FogJitter;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use temporal reprojection on volumetric fog.
|
||||
/// </summary>
|
||||
bool TemporalReprojection;
|
||||
|
||||
/// <summary>
|
||||
/// How much the history value should be weighted each frame. This is a tradeoff between visible jittering and responsiveness.
|
||||
/// </summary>
|
||||
float HistoryWeight;
|
||||
|
||||
/// <summary>
|
||||
/// Number of lighting samples to compute for voxels whose history value is not available. This reduces noise when panning or on camera cuts, but introduces a variable cost to volumetric fog computation. Valid range [1, 8].
|
||||
/// </summary>
|
||||
int32 HistoryMissSupersampleCount;
|
||||
|
||||
/// <summary>
|
||||
/// Scales the amount added to the inverse squared falloff denominator. This effectively removes the spike from inverse squared falloff that causes extreme aliasing.
|
||||
/// </summary>
|
||||
float InverseSquaredLightDistanceBiasScale;
|
||||
|
||||
/// <summary>
|
||||
/// The calculated size of the volume texture.
|
||||
/// </summary>
|
||||
Vector3 GridSize;
|
||||
|
||||
/// <summary>
|
||||
/// The cached per-frame data for the constant buffer.
|
||||
/// </summary>
|
||||
Data Data;
|
||||
};
|
||||
|
||||
FrameCache _cache;
|
||||
bool _isSupported;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Init
|
||||
/// </summary>
|
||||
VolumetricFogPass();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Renders the light to the volumetric fog light scattering volume texture. Called by the light pass after shadow map rendering. Used by the shadows casting lights.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="context">The GPU commands context.</param>
|
||||
/// <param name="light">The light.</param>
|
||||
/// <param name="shadowMap">The shadow map.</param>
|
||||
/// <param name="shadow">The light shadow data.</param>
|
||||
void RenderLight(RenderContext& renderContext, GPUContext* context, RendererPointLightData& light, GPUTextureView* shadowMap, LightShadowData& shadow);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the light to the volumetric fog light scattering volume texture. Called by the light pass after shadow map rendering. Used by the shadows casting lights.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
/// <param name="context">The GPU commands context.</param>
|
||||
/// <param name="light">The light.</param>
|
||||
/// <param name="shadowMap">The shadow map.</param>
|
||||
/// <param name="shadow">The light shadow data.</param>
|
||||
void RenderLight(RenderContext& renderContext, GPUContext* context, RendererSpotLightData& light, GPUTextureView* shadowMap, LightShadowData& shadow);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the volumetric fog (generates integrated light scattering 3D texture). Does nothing if feature is disabled or not supported.
|
||||
/// </summary>
|
||||
/// <param name="renderContext">The rendering context.</param>
|
||||
void Render(RenderContext& renderContext);
|
||||
|
||||
private:
|
||||
|
||||
bool Init(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options);
|
||||
GPUTextureView* GetLocalShadowedLightScattering(RenderContext& renderContext, GPUContext* context, VolumetricFogOptions& options) const;
|
||||
void InitCircleRasterizeBuffers();
|
||||
template<typename T>
|
||||
void RenderRadialLight(RenderContext& renderContext, GPUContext* context, T& light, LightShadowData& shadow);
|
||||
template<typename T>
|
||||
void RenderRadialLight(RenderContext& renderContext, GPUContext* context, RenderView& view, VolumetricFogOptions& options, T& light, PerLight& perLight, GPUConstantBuffer* cb1);
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
void OnShaderReloading(Asset* obj)
|
||||
{
|
||||
_psInjectLight.Release();
|
||||
_csInitialize = nullptr;
|
||||
_csLightScattering.Clear();
|
||||
_csFinalIntegration = nullptr;
|
||||
invalidateResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
// [RendererPass]
|
||||
String ToString() const override;
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
|
||||
protected:
|
||||
|
||||
// [RendererPass]
|
||||
bool setupResources() override;
|
||||
};
|
||||
Reference in New Issue
Block a user