You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View 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);
}
}
}
}

View 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;
};

View 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();
}

View 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;
};

View 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);
}

View 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;
};

View 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;
}

View 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;
};

View 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;
}

View 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);
};

View 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;
}

View 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;
};

View 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

View 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;
}

View 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;
};

View 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; \
}

View 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

View 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

View 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

View 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

View 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;
}

View 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;
};

View 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);
}
}

View 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;
};

View 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();
}

View 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;
};

View 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;
}

View 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;
};

View 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();
}

View 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;
};

View 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;
};

View 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);
}

View 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;
};

View 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);
}

View 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;
};

View 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

View 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

View 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);
}

View 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;
};

View 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);
}
}
}
}
}

View 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);
};

View 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");
}
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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);
};

View 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));

View 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);
}

View 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;
};

View 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;
}

View 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;
};

View 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();
}

View 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;
};

View 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);
}

View 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;
};

View 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.");
}
}

View 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;
};