Files
FlaxEngine/Source/Engine/Renderer/ReflectionsPass.cpp
Wojtek Figat 41e851298d Add new PreIntegratedGF with 80% smaller but more accurate data
Update old generation code, use R16G16_UNorm format instead of R11G11B10, skip mips and reduce Y axis to 32 pixels.

#1492
2025-09-25 17:35:10 +02:00

376 lines
12 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#include "ReflectionsPass.h"
#include "GBufferPass.h"
#include "RenderList.h"
#include "ScreenSpaceReflectionsPass.h"
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Content/Content.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderTargetPool.h"
#include "Engine/Level/Actors/EnvironmentProbe.h"
GPU_CB_STRUCT(Data {
ShaderEnvProbeData PData;
Matrix WVP;
ShaderGBufferData GBuffer;
});
#if GENERATE_GF_CACHE
// This code below (PreIntegratedGF namespace) is based on many Siggraph presentations about BRDF shading:
// https://blog.selfshadow.com/publications/s2015-shading-course/
// https://blog.selfshadow.com/publications/s2012-shading-course/
#define COMPILE_WITH_ASSETS_IMPORTER 1
#define COMPILE_WITH_TEXTURE_TOOL 1
#include "Engine/Engine/Globals.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/ContentImporters/ImportTexture.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
namespace PreIntegratedGF
{
static const int ResolutionX = 128;
static const int ResolutionY = 32;
static const int NumSamples = 256;
Float2 Hammersley(int32 i, int32 sampleCount)
{
float E1 = (float)i / (float)sampleCount;
float E2 = (float)((double)ReverseBits((uint32)i) * 2.3283064365386963e-10);
return Float2(E1, E2);
}
Float3 ImportanceSampleGGX(Float2 E, float roughness)
{
float m = roughness * roughness;
float m2 = m * m;
float phi = 2 * PI * E.X;
float cosTheta = sqrtf((1 - E.Y) / (1 + (m2 - 1) * E.Y));
float sinTheta = sqrtf(1 - cosTheta * cosTheta);
return Float3(sinTheta * cosf(phi), sinTheta * sinf(phi), cosTheta);
}
float Vis_SmithJointApprox(float roughness, float NoV, float NoL)
{
float a = roughness * roughness;
float Vis_SmithV = NoL * (NoV * (1 - a) + a);
float Vis_SmithL = NoV * (NoL * (1 - a) + a);
return 0.5f / (Vis_SmithV + Vis_SmithL);
}
Float2 IntegrateBRDF(float roughness, float NoV)
{
if (roughness < 0.04f) roughness = 0.04f;
Float3 V(sqrtf(1 - NoV * NoV), 0, NoV);
float a = 0, b = 0;
for (int32 i = 0; i < NumSamples; i++)
{
Float2 E = Hammersley(i, NumSamples);
Float3 H = ImportanceSampleGGX(E, roughness);
Float3 L = 2 * Float3::Dot(V, H) * H - V;
float NoL = Math::Saturate(L.Z);
float NoH = Math::Saturate(H.Z);
float VoH = Math::Saturate(Float3::Dot(V, H));
if (NoL > 0)
{
float Vis = Vis_SmithJointApprox(roughness, NoV, NoL);
float NoL_Vis_PDF = NoL * Vis * (4 * VoH / NoH);
float Fc = powf(1 - VoH, 5);
a += NoL_Vis_PDF * (1 - Fc);
b += NoL_Vis_PDF * Fc;
}
}
return Float2(a, b) / (float)NumSamples;
}
bool OnGenerate(TextureData& image)
{
// Setup image
image.Width = ResolutionX;
image.Height = ResolutionY;
image.Depth = 1;
image.Format = PixelFormat::R16G16_UNorm;
image.Items.Resize(1);
image.Items[0].Mips.Resize(1);
auto& mip = image.Items[0].Mips[0];
mip.RowPitch = 4 * image.Width;
mip.DepthPitch = mip.RowPitch * image.Height;
mip.Lines = image.Height;
mip.Data.Allocate(mip.DepthPitch);
// Generate GF pairs to be sampled in [NoV, roughness] space
auto pos = (uint16*)mip.Data.Get();
for (int32 y = 0; y < image.Height; y++)
{
float roughness = ((float)y + 0.5f) / (float)image.Height;
for (int32 x = 0; x < image.Width; x++)
{
float NoV = ((float)x + 0.5f) / (float)image.Width;
Float2 brdf = IntegrateBRDF(roughness, NoV);
*pos++ = (uint16)(Math::Saturate(brdf.X) * MAX_uint16 + 0.5f);
*pos++ = (uint16)(Math::Saturate(brdf.Y) * MAX_uint16 + 0.5f);
}
}
return false;
}
void Generate()
{
Guid id = Guid::Empty;
ImportTexture::Options options;
options.Type = TextureFormatType::HdrRGB;
options.InternalFormat = PixelFormat::R16G16_UNorm;
options.IndependentChannels = true;
options.IsAtlas = false;
options.sRGB = false;
options.NeverStream = true;
options.GenerateMipMaps = false;
options.Compress = false;
options.InternalLoad.Bind(&OnGenerate);
const String path = Globals::EngineContentFolder / PRE_INTEGRATED_GF_ASSET_NAME + ASSET_FILES_EXTENSION_WITH_DOT;
AssetsImportingManager::Create(AssetsImportingManager::CreateTextureTag, path, id, &options);
}
};
#endif
class Model;
String ReflectionsPass::ToString() const
{
return TEXT("ReflectionsPass");
}
bool ReflectionsPass::Init()
{
#if GENERATE_GF_CACHE
// Generate cache
PreIntegratedGF::Generate();
#endif
// Create pipeline states
_psProbe = GPUDevice::Instance->CreatePipelineState();
_psProbeInside = GPUDevice::Instance->CreatePipelineState();
_psCombinePass = GPUDevice::Instance->CreatePipelineState();
// Load assets
_shader = Content::LoadAsyncInternal<Shader>(TEXT("Shaders/Reflections"));
_sphereModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Sphere"));
_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();
CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data);
// Create pipeline stages
GPUPipelineState::Description psDesc;
if (!_psProbe->IsValid())
{
psDesc = GPUPipelineState::Description::DefaultNoDepth;
psDesc.BlendMode = BlendingMode::AlphaBlend;
psDesc.VS = shader->GetVS("VS_Model");
psDesc.PS = shader->GetPS("PS_EnvProbe");
psDesc.CullMode = CullMode::Normal;
psDesc.DepthEnable = true;
if (_psProbe->Init(psDesc))
return true;
psDesc.DepthFunc = ComparisonFunc::Greater;
psDesc.CullMode = CullMode::Inverted;
if (_psProbeInside->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();
// Cleanup
SAFE_DELETE_GPU_RESOURCE(_psProbe);
SAFE_DELETE_GPU_RESOURCE(_psProbeInside);
SAFE_DELETE_GPU_RESOURCE(_psCombinePass);
_shader = nullptr;
_sphereModel = nullptr;
_preIntegratedGF = nullptr;
}
bool SortProbes(RenderEnvironmentProbeData const& p1, RenderEnvironmentProbeData const& p2)
{
// Compare by Sort Order
int32 res = p1.SortOrder - p2.SortOrder;
if (res == 0)
{
// Compare by radius
res = static_cast<int32>(p2.Radius - p1.Radius);
if (res == 0)
{
// Compare by ID to prevent flickering
res = p2.HashID - p1.HashID;
}
}
return res < 0;
}
void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* lightBuffer)
{
auto device = GPUDevice::Instance;
auto context = device->GetMainContext();
if (checkIfSkipPass())
{
// Skip pass (just clear buffer when doing debug preview)
if (renderContext.View.Mode == ViewMode::Reflections)
context->Clear(lightBuffer, Color::Black);
return;
}
// Cache data
auto& view = renderContext.View;
bool useReflections = EnumHasAnyFlags(view.Flags, ViewFlags::Reflections);
bool useSSR = EnumHasAnyFlags(view.Flags, ViewFlags::SSR) && renderContext.List->Settings.ScreenSpaceReflections.Intensity > ZeroTolerance;
int32 probesCount = 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;
PROFILE_GPU_CPU("Reflections");
// 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(), PixelFormat::R11G11B10_Float);
auto reflectionsBuffer = RenderTargetPool::Get(tempDesc);
RENDER_TARGET_POOL_SET_NAME(reflectionsBuffer, "Reflections");
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(), renderContext.List->EnvironmentProbes.Count(), &SortProbes);
// Render all env probes
auto& sphereMesh = _sphereModel->LODs.Get()[0].Meshes.Get()[0];
for (int32 i = 0; i < probesCount; i++)
{
const RenderEnvironmentProbeData& probe = renderContext.List->EnvironmentProbes.Get()[i];
// Calculate world view projection matrix for the light sphere
Matrix world, wvp;
bool isViewInside;
RenderTools::ComputeSphereModelDrawMatrix(renderContext.View, probe.Position, probe.Radius, world, isViewInside);
Matrix::Multiply(world, view.ViewProjection(), wvp);
// Pack probe properties buffer
probe.SetShaderData(data.PData);
Matrix::Transpose(wvp, data.WVP);
// Render reflections
context->UpdateCB(cb, &data);
context->BindCB(0, cb);
context->BindSR(4, probe.Texture);
context->SetState(isViewInside ? _psProbeInside : _psProbe);
sphereMesh.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);
}