Files
FlaxEngine/Source/Engine/Renderer/ReflectionsPass.cpp

473 lines
14 KiB
C++

// Copyright (c) 2012-2024 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"
#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/
#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/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();
// 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::TwoSided;
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();
// Cleanup
SAFE_DELETE_GPU_RESOURCE(_psProbeNormal);
SAFE_DELETE_GPU_RESOURCE(_psProbeInverted);
SAFE_DELETE_GPU_RESOURCE(_psCombinePass);
_shader = nullptr;
_sphereModel = nullptr;
_preIntegratedGF = nullptr;
}
bool SortProbes(RenderEnvironmentProbeData const& p1, RenderEnvironmentProbeData const& p2)
{
// Compare by radius
int32 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;
// 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 ? _psProbeInverted : _psProbeNormal);
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);
}