From 375222a089e56dab74b296c4c7c5e06df374040a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 23 May 2022 10:15:02 +0200 Subject: [PATCH 01/27] Initial DDGI implementation --- Content/Editor/Camera/O_Camera.flax | 4 +- .../DebugMaterials/DDGIDebugProbes.flax | 3 + Content/Engine/Models/SphereLowPoly.flax | 4 +- Content/Shaders/GI/DDGI.flax | 3 + Content/Shaders/GI/GlobalSurfaceAtlas.flax | 4 +- Source/Editor/Cooker/Steps/DeployDataStep.cpp | 1 + .../GI/DynamicDiffuseGlobalIllumination.cpp | 528 ++++++++++++++++++ .../GI/DynamicDiffuseGlobalIllumination.h | 93 +++ Source/Engine/Renderer/Renderer.cpp | 9 +- Source/Shaders/GI/DDGI.hlsl | 214 +++++++ Source/Shaders/GI/DDGI.shader | 439 +++++++++++++++ Source/Shaders/GI/GlobalSurfaceAtlas.hlsl | 2 +- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 2 +- Source/Shaders/GlobalSignDistanceField.hlsl | 9 + 14 files changed, 1306 insertions(+), 9 deletions(-) create mode 100644 Content/Editor/DebugMaterials/DDGIDebugProbes.flax create mode 100644 Content/Shaders/GI/DDGI.flax create mode 100644 Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp create mode 100644 Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h create mode 100644 Source/Shaders/GI/DDGI.hlsl create mode 100644 Source/Shaders/GI/DDGI.shader diff --git a/Content/Editor/Camera/O_Camera.flax b/Content/Editor/Camera/O_Camera.flax index 443f7502f..5e0940624 100644 --- a/Content/Editor/Camera/O_Camera.flax +++ b/Content/Editor/Camera/O_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c356aa9250b8d42c6ba44fa23bad29a4b2c216f31061df03f20efd3c871414a -size 88395 +oid sha256:a2ec3410338bc342f7de1c4af6ae0f6310c739140e83de45632f3a3bc7c47f12 +size 88720 diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax new file mode 100644 index 000000000..f38eb04bd --- /dev/null +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd7232c07e5969b4db266528006dc0bad9b737f77da7bca56730fcf652509e9a +size 37759 diff --git a/Content/Engine/Models/SphereLowPoly.flax b/Content/Engine/Models/SphereLowPoly.flax index 57dc378b2..57dfc621c 100644 --- a/Content/Engine/Models/SphereLowPoly.flax +++ b/Content/Engine/Models/SphereLowPoly.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cd1093c7666a9a8df46b7b1d5cb1fe302bdf64eabc8d85c1cf1b2f91d35526d -size 3448 +oid sha256:572d0b6d951c1fe3a39aa2bfc6007b951a89abb010b72d138dd9d51ec12ec617 +size 3757 diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax new file mode 100644 index 000000000..a8463dbec --- /dev/null +++ b/Content/Shaders/GI/DDGI.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97b8d640c0f47b0eedba8bb90190043081ae26a5e371eba1c5e5c1271e411ddf +size 18125 diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index 2376e451e..460f42dc1 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ec7fc26caf2d2c9c216cc47684ac2adc6b872a5a61cdd7028a59e9230eae0f4 -size 10668 +oid sha256:d10f34a19ddbeea914082a23362317a2fc6ca31ca18d9307447023c9f2789414 +size 10610 diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index b41b28e43..4a027096e 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -70,6 +70,7 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting")); data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField")); data.AddRootEngineAsset(TEXT("Shaders/GI/GlobalSurfaceAtlas")); + data.AddRootEngineAsset(TEXT("Shaders/GI/DDGI")); data.AddRootEngineAsset(TEXT("Shaders/Quad")); data.AddRootEngineAsset(TEXT("Shaders/Reflections")); data.AddRootEngineAsset(TEXT("Shaders/Shadows")); diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp new file mode 100644 index 000000000..45fb8ba58 --- /dev/null +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -0,0 +1,528 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "DynamicDiffuseGlobalIllumination.h" +#include "GlobalSurfaceAtlasPass.h" +#include "../GlobalSignDistanceFieldPass.h" +#include "../RenderList.h" +#include "Engine/Core/Random.h" +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Math/Matrix3x3.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Content/Content.h" +#include "Engine/Debug/DebugDraw.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Level/Actors/BrushMode.h" +#include "Engine/Renderer/GBufferPass.h" + +// Implementation based on: +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Probes", Journal of Computer Graphics Tools, April 2019 +// Zander Majercik, Jean-Philippe Guertin, Derek Nowrouzezahrai, and Morgan McGuire +// https://morgan3d.github.io/articles/2019-04-01-ddgi/index.html and https://gdcvault.com/play/1026182/ +// +// Additional references: +// "Scaling Probe-Based Real-Time Dynamic Global Illumination for Production", https://jcgt.org/published/0010/02/01/ +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields", https://jcgt.org/published/0008/02/01/ + +// This must match HLSL +#define DDGI_TRACE_RAYS_GROUP_SIZE_X 32 +#define DDGI_TRACE_RAYS_LIMIT 512 // Limit of rays per-probe (runtime value can be smaller) +#define DDGI_PROBE_RESOLUTION_IRRADIANCE 6 // Resolution (in texels) for probe irradiance data (excluding 1px padding on each side) +#define DDGI_PROBE_RESOLUTION_DISTANCE 14 // Resolution (in texels) for probe distance data (excluding 1px padding on each side) +#define DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE 8 +#define DDGI_PROBE_CLASSIFY_GROUP_SIZE 32 + +PACK_STRUCT(struct Data0 + { + DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI; + GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; + GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; + GBufferData GBuffer; + Vector3 Padding0; + float IndirectLightingIntensity; + }); + +class DDGICustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + int32 ProbeRaysCount = 0; + float ProbesSpacing = 0.0f; + Int3 ProbeCounts = Int3::Zero; + Vector3 ProbesOrigin; + Int3 ProbeScrollOffsets; + Int3 ProbeScrollDirections; + bool ProbeScrollClear[3]; + GPUTexture* ProbesTrace = nullptr; // Probes ray tracing: (RGB: hit radiance, A: hit distance) + GPUTexture* ProbesState = nullptr; // Probes state: (RGB: world-space offset, A: state) + GPUTexture* ProbesIrradiance = nullptr; // Probes irradiance (RGB: sRGB color) + GPUTexture* ProbesDistance = nullptr; // Probes distance (R: mean distance, G: mean distance^2) + DynamicDiffuseGlobalIlluminationPass::BindingData Result; + + FORCE_INLINE void Clear() + { + ProbesOrigin = Vector3::Zero; + ProbeScrollOffsets = Int3::Zero; + ProbeScrollDirections = Int3::Zero; + ProbeScrollClear[0] = false; + ProbeScrollClear[1] = false; + ProbeScrollClear[2] = false; + RenderTargetPool::Release(ProbesTrace); + RenderTargetPool::Release(ProbesState); + RenderTargetPool::Release(ProbesIrradiance); + RenderTargetPool::Release(ProbesDistance); + } + + ~DDGICustomBuffer() + { + Clear(); + } +}; + +void CalculateVolumeRandomRotation(Matrix3x3& matrix) +{ + // Reference: James Arvo's algorithm Graphics Gems 3 (pages 117-120) + // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.53.1357&rep=rep1&type=pdf + + float u1 = TWO_PI * Random::Rand(); + float cos1 = Math::Cos(u1); + float sin1 = Math::Sin(u1); + float u2 = TWO_PI * Random::Rand(); + float cos2 = Math::Cos(u2); + float sin2 = Math::Sin(u2); + + float u3 = Random::Rand(); + float sq3 = 2.0f * sqrtf(u3 * (1.0f - u3)); + + float s2 = 2.0f * u3 * sin2 * sin2 - 1.0f; + float c2 = 2.0f * u3 * cos2 * cos2 - 1.0f; + float sc = 2.0f * u3 * sin2 * cos2; + + matrix.M11 = cos1 * c2 - sin1 * sc; + matrix.M12 = sin1 * c2 + cos1 * sc; + matrix.M13 = sq3 * cos2; + + matrix.M21 = cos1 * sc - sin1 * s2; + matrix.M22 = sin1 * sc + cos1 * s2; + matrix.M23 = sq3 * sin2; + + matrix.M31 = cos1 * (sq3 * cos2) - sin1 * (sq3 * sin2); + matrix.M32 = sin1 * (sq3 * cos2) + cos1 * (sq3 * sin2); + matrix.M33 = 1.0f - 2.0f * u3; +} + +int32 AbsFloor(const float value) +{ + return value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value); +} + +int32 GetSignNotZero(const float value) +{ + return value >= 0.0f ? 1 : -1; +} + +Vector3 GetVolumeOrigin(DDGICustomBuffer& ddgiData) +{ + return ddgiData.ProbesOrigin + Vector3(ddgiData.ProbeScrollOffsets) * ddgiData.ProbesSpacing; +} + +void CalculateVolumeScrolling(DDGICustomBuffer& ddgiData, const Vector3& viewOrigin) +{ + // Reset the volume origin and scroll offsets for each axis + for (int32 axis = 0; axis < 3; axis++) + { + if (ddgiData.ProbeScrollOffsets.Raw[axis] != 0 && (ddgiData.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0)) + { + ddgiData.ProbesOrigin.Raw[axis] += (float)ddgiData.ProbeCounts.Raw[axis] * ddgiData.ProbesSpacing * (float)ddgiData.ProbeScrollDirections.Raw[axis]; + ddgiData.ProbeScrollOffsets.Raw[axis] = 0; + } + } + + // Calculate the count of grid cells between the view origin and the scroll anchor + const Vector3 translation = viewOrigin - GetVolumeOrigin(ddgiData); + for (int32 axis = 0; axis < 3; axis++) + { + const int32 scroll = AbsFloor(translation.Raw[axis] / ddgiData.ProbesSpacing); + ddgiData.ProbeScrollOffsets.Raw[axis] += scroll; + ddgiData.ProbeScrollClear[axis] = scroll != 0; + ddgiData.ProbeScrollDirections.Raw[axis] = GetSignNotZero(translation.Raw[axis]); + } +} + +String DynamicDiffuseGlobalIlluminationPass::ToString() const +{ + return TEXT("DynamicDiffuseGlobalIlluminationPass"); +} + +bool DynamicDiffuseGlobalIlluminationPass::Init() +{ + // Check platform support + const auto device = GPUDevice::Instance; + _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad; + return false; +} + +bool DynamicDiffuseGlobalIlluminationPass::setupResources() +{ + if (!_supported) + return true; + + // Load shader + if (!_shader) + { + _shader = Content::LoadAsyncInternal(TEXT("Shaders/GI/DDGI")); + if (_shader == nullptr) + return true; +#if COMPILE_WITH_DEV_ENV + _shader.Get()->OnReloading.Bind(this); +#endif + } + if (!_shader->IsLoaded()) + return true; + + // Initialize resources + const auto shader = _shader->GetShader(); + _cb0 = shader->GetCB(0); + if (!_cb0) + return true; + _csClassify = shader->GetCS("CS_Classify"); + _csTraceRays = shader->GetCS("CS_TraceRays"); + _csUpdateProbesIrradiance = shader->GetCS("CS_UpdateProbes", 0); + _csUpdateProbesDistance = shader->GetCS("CS_UpdateProbes", 1); + _csUpdateBordersIrradianceRow = shader->GetCS("CS_UpdateBorders", 0); + _csUpdateBordersIrradianceCollumn = shader->GetCS("CS_UpdateBorders", 1); + _csUpdateBordersDistanceRow = shader->GetCS("CS_UpdateBorders", 2); + _csUpdateBordersDistanceCollumn = shader->GetCS("CS_UpdateBorders", 3); + auto device = GPUDevice::Instance; + auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + if (!_psIndirectLighting) + { + _psIndirectLighting = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_IndirectLighting"); + psDesc.BlendMode = BlendingMode::Additive; + if (_psIndirectLighting->Init(psDesc)) + return true; + } + + return false; +} + +#if COMPILE_WITH_DEV_ENV + +void DynamicDiffuseGlobalIlluminationPass::OnShaderReloading(Asset* obj) +{ + LastFrameShaderReload = Engine::FrameCount; + _csClassify = nullptr; + _csTraceRays = nullptr; + _csUpdateProbesIrradiance = nullptr; + _csUpdateProbesDistance = nullptr; + _csUpdateBordersIrradianceRow = nullptr; + _csUpdateBordersIrradianceCollumn = nullptr; + _csUpdateBordersDistanceRow = nullptr; + _csUpdateBordersDistanceCollumn = nullptr; + SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); + invalidateResources(); +} + +#endif + +void DynamicDiffuseGlobalIlluminationPass::Dispose() +{ + RendererPass::Dispose(); + + // Cleanup + _cb0 = nullptr; + _csTraceRays = nullptr; + _shader = nullptr; + SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); +#if USE_EDITOR + _debugModel = nullptr; + _debugMaterial = nullptr; +#endif +} + +bool DynamicDiffuseGlobalIlluminationPass::Get(const RenderBuffers* buffers, BindingData& result) +{ + auto* ddgiData = buffers ? buffers->FindCustomBuffer(TEXT("DDGI")) : nullptr; + if (ddgiData && ddgiData->LastFrameUsed + 1 >= Engine::FrameCount) // Allow to use data from the previous frame (eg. particles in Editor using the Editor viewport in Game viewport - Game render task runs first) + { + result = ddgiData->Result; + return false; + } + return true; +} + +bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer) +{ + // Skip if not supported + if (checkIfSkipPass()) + return true; + if (renderContext.List->Scenes.Count() == 0) + return true; + auto& ddgiData = *renderContext.Buffers->GetCustomBuffer(TEXT("DDGI")); + + // Render Global SDF and Global Surface Atlas for software raytracing + GlobalSignDistanceFieldPass::BindingData bindingDataSDF; + if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF)) + return true; + GlobalSurfaceAtlasPass::BindingData bindingDataSurfaceAtlas; + if (GlobalSurfaceAtlasPass::Instance()->Render(renderContext, context, bindingDataSurfaceAtlas)) + return true; + + // Skip if already done in the current frame + const auto currentFrame = Engine::FrameCount; + if (ddgiData.LastFrameUsed == currentFrame) + return false; + ddgiData.LastFrameUsed = currentFrame; + PROFILE_GPU_CPU("Dynamic Diffuse Global Illumination"); + + // TODO: configurable via graphics settings + const Quality quality = Quality::Ultra; + bool debugProbes = true; // TODO: add debug option to draw probes locations -> in Graphics window - Editor-only + // TODO: configurable via postFx settings (maybe use Global SDF distance?) + const float indirectLightingIntensity = 1.0f; + const Vector3 giDistance(2000, 2000, 2000); // GI distance around the view (in each direction) + const float giResolution = 100.0f; // GI probes placement spacing + const Int3 probesCounts(Vector3::Ceil(giDistance / giResolution)); + const Vector3 probesDistance = Vector3(probesCounts) * giResolution; + const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality + const float probeHistoryWeight = 0.97f; + + // Init buffers + const int32 probesCount = probesCounts.X * probesCounts.Y * probesCounts.Z; + if (probesCount == 0 || indirectLightingIntensity <= ZeroTolerance) + return true; + int32 probesCountX = probesCounts.X * probesCounts.Y; + int32 probesCountY = probesCounts.Z; + bool clear = false; + if (Math::NotNearEqual(ddgiData.ProbesSpacing, giResolution) || ddgiData.ProbeCounts != probesCounts || ddgiData.ProbeRaysCount != probeRaysCount) + { + PROFILE_CPU_NAMED("Init"); + ddgiData.Clear(); + ddgiData.ProbeRaysCount = probeRaysCount; + ddgiData.ProbesSpacing = giResolution; + ddgiData.ProbeCounts = probesCounts; + + // Allocate probes textures + uint64 memUsage = 0; + auto desc = GPUTextureDescription::New2D(probesCountX, probesCountY, PixelFormat::Unknown); + // TODO rethink probes data placement in memory -> what if we get [50x50x30] resolution? That's 75000 probes! Use sparse storage with active-only probes +#define INIT_TEXTURE(texture, format, width, height) desc.Format = format; desc.Width = width; desc.Height = height; ddgiData.texture = RenderTargetPool::Get(desc); if (!ddgiData.texture) return true; memUsage += ddgiData.texture->GetMemoryUsage() + desc.Flags = GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess; + INIT_TEXTURE(ProbesTrace, PixelFormat::R16G16B16A16_Float, probeRaysCount, probesCount); + INIT_TEXTURE(ProbesState, PixelFormat::R16G16B16A16_Float, probesCountX, probesCountY); // TODO: optimize to a RGBA32 (pos offset can be normalized to [0-0.5] range of ProbesSpacing and packed with state flag) + INIT_TEXTURE(ProbesIrradiance, PixelFormat::R11G11B10_Float, probesCountX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), probesCountY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2)); + INIT_TEXTURE(ProbesDistance, PixelFormat::R16G16_Float, probesCountX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), probesCountY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2)); +#undef INIT_TEXTURE + LOG(Info, "Dynamic Diffuse Global Illumination memory usage: {0} MB, probes: {1}", memUsage / 1024 / 1024, probesCount); + clear = true; + } +#if USE_EDITOR + clear |= ddgiData.LastFrameUsed <= LastFrameShaderReload; +#endif + if (clear) + { + // Clear probes + PROFILE_GPU("Clear"); + context->ClearUA(ddgiData.ProbesState, Vector4::Zero); + context->ClearUA(ddgiData.ProbesIrradiance, Vector4::Zero); + context->ClearUA(ddgiData.ProbesDistance, Vector4::Zero); + } + + // Compute random rotation matrix for probe rays orientation (randomized every frame) + Matrix3x3 raysRotationMatrix; + CalculateVolumeRandomRotation(raysRotationMatrix); + + // Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement) + Vector3 viewOrigin = renderContext.View.Position; + Vector3 viewDirection = renderContext.View.Direction; + const float probesDistanceMax = probesDistance.MaxValue(); + const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (probesDistanceMax * 2.0f), viewOrigin - probesDistance, viewOrigin + probesDistance); + const float viewOriginOffset = viewRayHit.Y * probesDistanceMax * 0.8f; + viewOrigin += viewDirection * viewOriginOffset; + const float viewOriginSnapping = giResolution; + viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping; + CalculateVolumeScrolling(ddgiData, viewOrigin); + + // Upload constants + { + ddgiData.Result.Constants.ProbesOrigin = ddgiData.ProbesOrigin; + ddgiData.Result.Constants.ProbesSpacing = ddgiData.ProbesSpacing; + Quaternion& raysRotation = *(Quaternion*)&ddgiData.Result.Constants.RaysRotation; + Quaternion::RotationMatrix(raysRotationMatrix, raysRotation); + raysRotation.Conjugate(); + ddgiData.Result.Constants.ProbesCounts[0] = probesCounts.X; + ddgiData.Result.Constants.ProbesCounts[1] = probesCounts.Y; + ddgiData.Result.Constants.ProbesCounts[2] = probesCounts.Z; + ddgiData.Result.Constants.ProbesScrollOffsets = ddgiData.ProbeScrollOffsets; + ddgiData.Result.Constants.ProbeScrollDirections = ddgiData.ProbeScrollDirections; + ddgiData.Result.Constants.ProbeScrollClear[0] = ddgiData.ProbeScrollClear[0] != 0; + ddgiData.Result.Constants.ProbeScrollClear[1] = ddgiData.ProbeScrollClear[1] != 0; + ddgiData.Result.Constants.ProbeScrollClear[2] = ddgiData.ProbeScrollClear[2] != 0; + ddgiData.Result.Constants.RayMaxDistance = 10000.0f; // TODO: adjust to match perf/quality ratio (make it based on Global SDF and Global Surface Atlas distance) + ddgiData.Result.Constants.ViewDir = viewDirection; + ddgiData.Result.Constants.RaysCount = probeRaysCount; + ddgiData.Result.Constants.ProbeHistoryWeight = probeHistoryWeight; + ddgiData.Result.Constants.IrradianceGamma = 5.0f; + ddgiData.Result.ProbesState = ddgiData.ProbesState->View(); + ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->View(); + ddgiData.Result.ProbesIrradiance = ddgiData.ProbesIrradiance->View(); + + Data0 data; + data.DDGI = ddgiData.Result.Constants; + data.GlobalSDF = bindingDataSDF.Constants; + data.GlobalSurfaceAtlas = bindingDataSurfaceAtlas.Constants; + data.IndirectLightingIntensity = indirectLightingIntensity; + GBufferPass::SetInputs(renderContext.View, data.GBuffer); + context->UpdateCB(_cb0, &data); + context->BindCB(0, _cb0); + } + + // Classify probes (activation/deactivation and relocation) + { + PROFILE_GPU_CPU("Probes Classification"); + uint32 threadGroups = Math::DivideAndRoundUp(probesCount, DDGI_PROBE_CLASSIFY_GROUP_SIZE); + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); + } + context->BindUA(0, ddgiData.Result.ProbesState); + context->Dispatch(_csClassify, threadGroups, 1, 1); + context->ResetUA(); + } + + // Trace rays from probes + { + PROFILE_GPU_CPU("Trace Rays"); + + // Global SDF with Global Surface Atlas software raytracing (X - per probe ray, Y - per probe) + ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0); + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); + } + context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr); + context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr); + context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); + context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View()); + context->BindSR(12, ddgiData.Result.ProbesState); + context->BindUA(0, ddgiData.ProbesTrace->View()); + context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesCount, 1); + context->ResetUA(); + context->ResetSR(); + +#if 0 + // Probes trace debug preview + context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); + context->SetRenderTarget(lightBuffer); + context->Draw(ddgiData.ProbesTrace); + return false; +#endif + } + + // Update probes + { + PROFILE_GPU_CPU("Update Probes"); + context->BindSR(0, ddgiData.Result.ProbesState); + context->BindSR(1, ddgiData.ProbesTrace->View()); + + // Update irradiance + context->BindUA(0, ddgiData.Result.ProbesIrradiance); + context->Dispatch(_csUpdateProbesIrradiance, probesCountX, probesCountY, 1); + uint32 threadGroupsX = Math::DivideAndRoundUp(probesCountX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + uint32 threadGroupsY = Math::DivideAndRoundUp(probesCountY, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersIrradianceRow, threadGroupsX, threadGroupsY, 1); + threadGroupsX = Math::DivideAndRoundUp(probesCountX, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersIrradianceCollumn, threadGroupsX, threadGroupsY, 1); + + // Update distance + context->BindUA(0, ddgiData.Result.ProbesDistance); + context->Dispatch(_csUpdateProbesDistance, probesCountX, probesCountY, 1); + threadGroupsX = Math::DivideAndRoundUp(probesCountX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountY, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersDistanceRow, threadGroupsX, threadGroupsY, 1); + threadGroupsX = Math::DivideAndRoundUp(probesCountX, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersDistanceCollumn, threadGroupsX, threadGroupsY, 1); + } + + // Render indirect lighting + { + PROFILE_GPU_CPU("Indirect Lighting"); +#if 0 + // DDGI indirect lighting debug preview + context->Clear(lightBuffer, Color::Transparent); +#endif + context->ResetUA(); + context->BindSR(0, renderContext.Buffers->GBuffer0->View()); + context->BindSR(1, renderContext.Buffers->GBuffer1->View()); + context->BindSR(2, renderContext.Buffers->GBuffer2->View()); + context->BindSR(3, renderContext.Buffers->DepthBuffer->View()); + context->BindSR(4, ddgiData.Result.ProbesState); + context->BindSR(5, ddgiData.Result.ProbesDistance); + context->BindSR(6, ddgiData.Result.ProbesIrradiance); + context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); + context->SetRenderTarget(lightBuffer); + context->SetState(_psIndirectLighting); + context->DrawFullscreenTriangle(); + } + +#if USE_EDITOR + // Probes debug drawing + if (debugProbes) + { + PROFILE_GPU_CPU("Debug Probes"); + if (!_debugModel) + _debugModel = Content::LoadAsyncInternal(TEXT("Editor/Primitives/Sphere")); + if (!_debugMaterial) + _debugMaterial = Content::LoadAsyncInternal(TEXT("Editor/DebugMaterials/DDGIDebugProbes")); + if (_debugModel && _debugModel->IsLoaded() && _debugModel->CanBeRendered() && _debugMaterial && _debugMaterial->IsLoaded()) + { + RenderContext debugRenderContext(renderContext); + debugRenderContext.List = RenderList::GetFromPool(); + debugRenderContext.View.Pass = DrawPass::GBuffer; + debugRenderContext.View.Prepare(debugRenderContext); + Matrix world; + Matrix::Scaling(Vector3(0.2f), world); + const Mesh& debugMesh = _debugModel->LODs[0].Meshes[0]; + for (int32 probeIndex = 0; probeIndex < probesCount; probeIndex++) + debugMesh.Draw(debugRenderContext, _debugMaterial, world, StaticFlags::None, true, DrawPass::GBuffer, (float)probeIndex); + debugRenderContext.List->SortDrawCalls(debugRenderContext, false, DrawCallsListType::GBuffer); + context->SetViewportAndScissors(debugRenderContext.View.ScreenSize.X, debugRenderContext.View.ScreenSize.Y); + GPUTextureView* targetBuffers[5] = + { + lightBuffer, + renderContext.Buffers->GBuffer0->View(), + renderContext.Buffers->GBuffer1->View(), + renderContext.Buffers->GBuffer2->View(), + renderContext.Buffers->GBuffer3->View(), + }; + context->SetRenderTarget(*renderContext.Buffers->DepthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers))); + { + // Pass DDGI data to the material + _debugMaterial->SetParameterValue(TEXT("ProbesState"), Variant(ddgiData.ProbesState)); + _debugMaterial->SetParameterValue(TEXT("ProbesIrradiance"), Variant(ddgiData.ProbesIrradiance)); + _debugMaterial->SetParameterValue(TEXT("ProbesDistance"), Variant(ddgiData.ProbesDistance)); + auto cb = _debugMaterial->GetShader()->GetCB(3); + if (cb) + { + context->UpdateCB(cb, &ddgiData.Result.Constants); + context->BindCB(3, cb); + } + } + debugRenderContext.List->ExecuteDrawCalls(debugRenderContext, DrawCallsListType::GBuffer); + RenderList::ReturnToPool(debugRenderContext.List); + context->UnBindCB(3); + context->ResetRenderTarget(); + } + } +#endif + + return false; +} diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h new file mode 100644 index 000000000..5440cf7be --- /dev/null +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "../RendererPass.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Graphics/Textures/GPUTexture.h" + +/// +/// Dynamic Diffuse Global Illumination rendering pass. +/// +class FLAXENGINE_API DynamicDiffuseGlobalIlluminationPass : public RendererPass +{ +public: + // Constant buffer data for DDGI access on a GPU. + PACK_STRUCT(struct ConstantsData + { + Vector3 ProbesOrigin; + float ProbesSpacing; + Vector4 RaysRotation; + uint32 ProbesCounts[3]; + float IrradianceGamma; + Int3 ProbesScrollOffsets; + float ProbeHistoryWeight; + Vector3 ViewDir; + uint32 RaysCount; + Int3 ProbeScrollDirections; + float RayMaxDistance; + uint32 ProbeScrollClear[3]; + uint32 Padding0; + }); + + // Binding data for the GPU. + struct BindingData + { + ConstantsData Constants; + GPUTextureView* ProbesState; + GPUTextureView* ProbesDistance; + GPUTextureView* ProbesIrradiance; + }; + +private: + bool _supported = false; + AssetReference _shader; + GPUConstantBuffer* _cb0 = nullptr; + GPUShaderProgramCS* _csClassify; + GPUShaderProgramCS* _csTraceRays; + GPUShaderProgramCS* _csUpdateProbesIrradiance; + GPUShaderProgramCS* _csUpdateProbesDistance; + GPUShaderProgramCS* _csUpdateBordersIrradianceRow; + GPUShaderProgramCS* _csUpdateBordersIrradianceCollumn; + GPUShaderProgramCS* _csUpdateBordersDistanceRow; + GPUShaderProgramCS* _csUpdateBordersDistanceCollumn; + GPUPipelineState* _psIndirectLighting; +#if USE_EDITOR + AssetReference _debugModel; + AssetReference _debugMaterial; +#endif + +public: + /// + /// Gets the DDGI binding data (only if enabled). + /// + /// The rendering context buffers. + /// The result DDGI data for binding to the shaders. + /// True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false. + bool Get(const RenderBuffers* buffers, BindingData& result); + + /// + /// Renders the DDGI. + /// + /// The rendering context. + /// The GPU context. + /// The light accumulation buffer (input and output). + /// True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false. + bool Render(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer); + +private: +#if COMPILE_WITH_DEV_ENV + uint64 LastFrameShaderReload = 0; + void OnShaderReloading(Asset* obj); +#endif + +public: + // [RendererPass] + String ToString() const override; + bool Init() override; + void Dispose() override; + +protected: + // [RendererPass] + bool setupResources() override; +}; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 056870571..ea3d127f0 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -23,6 +23,7 @@ #include "AtmospherePreCompute.h" #include "GlobalSignDistanceFieldPass.h" #include "GI/GlobalSurfaceAtlasPass.h" +#include "GI/DynamicDiffuseGlobalIllumination.h" #include "Utils/MultiScaler.h" #include "Utils/BitonicSort.h" #include "AntiAliasing/FXAA.h" @@ -85,6 +86,7 @@ bool RendererService::Init() PassList.Add(HistogramPass::Instance()); PassList.Add(GlobalSignDistanceFieldPass::Instance()); PassList.Add(GlobalSurfaceAtlasPass::Instance()); + PassList.Add(DynamicDiffuseGlobalIlluminationPass::Instance()); #if USE_EDITOR PassList.Add(QuadOverdrawPass::Instance()); #endif @@ -397,6 +399,11 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) // Render lighting LightPass::Instance()->RenderLight(renderContext, *lightBuffer); + if (renderContext.View.Flags & ViewFlags::GI) + { + // TODO: add option to PostFx Volume for realtime GI type (None, DDGI) + DynamicDiffuseGlobalIlluminationPass::Instance()->Render(renderContext, context, *lightBuffer); + } if (renderContext.View.Mode == ViewMode::LightBuffer) { auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext); @@ -499,7 +506,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) 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); diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl new file mode 100644 index 000000000..b36cc5d3d --- /dev/null +++ b/Source/Shaders/GI/DDGI.hlsl @@ -0,0 +1,214 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +// Implementation based on: +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Probes", Journal of Computer Graphics Tools, April 2019 +// Zander Majercik, Jean-Philippe Guertin, Derek Nowrouzezahrai, and Morgan McGuire +// https://morgan3d.github.io/articles/2019-04-01-ddgi/index.html and https://gdcvault.com/play/1026182/ +// +// Additional references: +// "Scaling Probe-Based Real-Time Dynamic Global Illumination for Production", https://jcgt.org/published/0010/02/01/ +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields", https://jcgt.org/published/0008/02/01/ + +#include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" +#include "./Flax/Octahedral.hlsl" + +#define DDGI_PROBE_STATE_ACTIVE 0 +#define DDGI_PROBE_STATE_INACTIVE 1 +#define DDGI_PROBE_RESOLUTION_IRRADIANCE 6 // Resolution (in texels) for probe irradiance data (excluding 1px padding on each side) +#define DDGI_PROBE_RESOLUTION_DISTANCE 14 // Resolution (in texels) for probe distance data (excluding 1px padding on each side) +#define DDGI_SRGB_BLENDING 1 // Enables blending in sRGB color space, otherwise irradiance blending is done in linear space + +// DDGI data for a constant buffer +struct DDGIData +{ + float3 ProbesOrigin; + float ProbesSpacing; + float4 RaysRotation; + uint3 ProbesCounts; + float IrradianceGamma; + int3 ProbesScrollOffsets; + float ProbeHistoryWeight; + float3 ViewDir; + uint RaysCount; + int3 ProbeScrollDirections; + float RayMaxDistance; + uint3 ProbeScrollClear; // TODO: pack into bits + uint Padding0; +}; + +uint GetDDGIProbeIndex(DDGIData data, uint3 probeCoords) +{ + uint probesPerPlane = data.ProbesCounts.x * data.ProbesCounts.z; + uint planeIndex = probeCoords.y; + uint probeIndexInPlane = probeCoords.x + (data.ProbesCounts.x * probeCoords.z); + return planeIndex * probesPerPlane + probeIndexInPlane; +} + +uint GetDDGIProbeIndex(DDGIData data, uint2 texCoords, uint texResolution) +{ + uint probesPerPlane = data.ProbesCounts.x * data.ProbesCounts.z; + uint planeIndex = texCoords.x / (data.ProbesCounts.x * texResolution); + uint probeIndexInPlane = (texCoords.x / texResolution) - (planeIndex * data.ProbesCounts.x) + (data.ProbesCounts.x * (texCoords.y / texResolution)); + return planeIndex * probesPerPlane + probeIndexInPlane; +} + +uint3 GetDDGIProbeCoords(DDGIData data, uint probeIndex) +{ + uint3 probeCoords; + probeCoords.x = probeIndex % data.ProbesCounts.x; + probeCoords.y = probeIndex / (data.ProbesCounts.x * data.ProbesCounts.z); + probeCoords.z = (probeIndex / data.ProbesCounts.x) % data.ProbesCounts.z; + return probeCoords; +} + +uint2 GetDDGIProbeTexelCoords(DDGIData data, uint probeIndex) +{ + uint probesPerPlane = data.ProbesCounts.x * data.ProbesCounts.z; + uint planeIndex = probeIndex / probesPerPlane; + uint gridSpaceX = probeIndex % data.ProbesCounts.x; + uint gridSpaceY = probeIndex / data.ProbesCounts.x; + uint x = gridSpaceX + (planeIndex * data.ProbesCounts.x); + uint y = gridSpaceY % data.ProbesCounts.z; + return uint2(x, y); +} + +uint GetDDGIScrollingProbeIndex(DDGIData data, uint3 probeCoords) +{ + // Probes are scrolled on edges to stabilize GI when camera moves + return GetDDGIProbeIndex(data, (probeCoords + data.ProbesScrollOffsets + data.ProbesCounts) % data.ProbesCounts); +} + +float3 GetDDGIProbeWorldPosition(DDGIData data, uint3 probeCoords) +{ + float3 probePosition = probeCoords * data.ProbesSpacing; + float3 probeGridOffset = (data.ProbesSpacing * (data.ProbesCounts - 1)) * 0.5f; + return data.ProbesOrigin + probePosition - probeGridOffset + (data.ProbesScrollOffsets * data.ProbesSpacing); +} + +// Loads probe probe state +float LoadDDGIProbeState(DDGIData data, Texture2D probesState, uint probeIndex) +{ + int2 probeDataCoords = GetDDGIProbeTexelCoords(data, probeIndex); + float4 probeState = probesState.Load(int3(probeDataCoords, 0)); + return probeState.w; +} + +// Loads probe world-space position (XYZ) and probe state (W) +float4 LoadDDGIProbePositionAndState(DDGIData data, Texture2D probesState, uint probeIndex, uint3 probeCoords) +{ + float4 result; + result.xyz = GetDDGIProbeWorldPosition(data, probeCoords); + + // Probe state contains relocation's offset and the classification's state + int2 probeDataCoords = GetDDGIProbeTexelCoords(data, probeIndex); + float4 probeState = probesState.Load(int3(probeDataCoords, 0)); + result.xyz += probeState.xyz; + result.w = probeState.w; + + return result; +} + +// Calculates texture UVs for sampling probes atlas texture (irradiance or distance) +float2 GetDDGIProbeUV(DDGIData data, uint probeIndex, float2 octahedralCoords, uint resolution) +{ + uint2 coords = GetDDGIProbeTexelCoords(data, probeIndex); + float probeTexelSize = resolution + 2.0f; + float textureWidth = probeTexelSize * (data.ProbesCounts.x * data.ProbesCounts.y); + float textureHeight = probeTexelSize * data.ProbesCounts.z; + float2 uv = float2(coords.x * probeTexelSize, coords.y * probeTexelSize) + (probeTexelSize * 0.5f); + uv += octahedralCoords.xy * (resolution * 0.5f); + uv /= float2(textureWidth, textureHeight); + return uv; +} + +// Samples DDGI probes volume at the given world-space position and returns the irradiance. +float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Texture2D probesDistance, Texture2D probesIrradiance, float3 worldPosition, float3 worldNormal, float bias) +{ + float4 irradiance = float4(0, 0, 0, 0); + float3 probesOrigin = data.ProbesScrollOffsets * data.ProbesSpacing + data.ProbesOrigin; + float3 probesExtent = (data.ProbesCounts - 1) * (data.ProbesSpacing * 0.5f); + + // Bias the world-space position to reduce artifacts + float3 surfaceBias = (worldNormal * bias) + (data.ViewDir * (bias * -4.0f)); + float3 biasedWorldPosition = worldPosition + surfaceBias; + + // Get the grid coordinates of the probe nearest the biased world position + uint3 baseProbeCoords = clamp(uint3((worldPosition - probesOrigin + probesExtent) / data.ProbesSpacing), 0, data.ProbesCounts - 1); + float3 baseProbeWorldPosition = GetDDGIProbeWorldPosition(data, baseProbeCoords); + float3 biasAlpha = saturate((biasedWorldPosition - baseProbeWorldPosition) / data.ProbesSpacing); + + // Loop over the closest probes to accumulate their contributions + for (uint i = 0; i < 8; i++) + { + uint3 probeCoordsOffset = uint3(i, i >> 1, i >> 2) & 1; + uint3 probeCoords = clamp(baseProbeCoords + probeCoordsOffset, 0, data.ProbesCounts - 1); + uint probeIndex = GetDDGIScrollingProbeIndex(data, probeCoords); + + // Load probe position and state + float4 probePositionAndState = LoadDDGIProbePositionAndState(data, probesState, probeIndex, probeCoords); + if (probePositionAndState.w == DDGI_PROBE_STATE_INACTIVE) + continue; + + // Calculate the distance and direction from the (biased and non-biased) shading point and the probe + float3 worldPosToProbe = normalize(probePositionAndState.xyz - worldPosition); + float3 biasedPosToProbe = normalize(probePositionAndState.xyz - biasedWorldPosition); + float biasedPosToProbeDist = length(probePositionAndState.xyz - biasedWorldPosition); + + // Smooth backface test + float weight = Square(dot(worldPosToProbe, worldNormal) * 0.5f + 0.5f); + + // Sample distance texture + float2 octahedralCoords = GetOctahedralCoords(-biasedPosToProbe); + float2 uv = GetDDGIProbeUV(data, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_DISTANCE); + float2 probeDistance = probesDistance.SampleLevel(SamplerLinearClamp, uv, 0).rg * 2.0f; + float probeDistanceMean = probeDistance.x; + float probeDistanceMean2 = probeDistance.y; + + // Visibility weight (Chebyshev) + if (biasedPosToProbeDist > probeDistanceMean) + { + float probeDistanceVariance = abs(Square(probeDistanceMean) - probeDistanceMean2); + float chebyshevWeight = probeDistanceVariance / (probeDistanceVariance + Square(biasedPosToProbeDist - probeDistanceMean)); + weight *= max(chebyshevWeight * chebyshevWeight * chebyshevWeight, 0.05f); + } + + // Avoid a weight of zero + weight = max(weight, 0.000001f); + + // Adjust weight curve to inject a small portion of light + const float minWeightThreshold = 0.2f; + if (weight < minWeightThreshold) + weight *= Square(weight) * (1.0f / (minWeightThreshold * minWeightThreshold)); + + // Calculate trilinear weights based on the distance to each probe to smoothly transition between grid of 8 probes + float3 trilinear = lerp(1.0f - biasAlpha, biasAlpha, probeCoordsOffset); + weight *= max(trilinear.x * trilinear.y * trilinear.z, 0.001f); + + // Sample irradiance texture + octahedralCoords = GetOctahedralCoords(worldNormal); + uv = GetDDGIProbeUV(data, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE); + float3 probeIrradiance = probesIrradiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb; +#if DDGI_SRGB_BLENDING + probeIrradiance = pow(probeIrradiance, data.IrradianceGamma * 0.5f); +#endif + + // Accumulate weighted irradiance + irradiance += float4(probeIrradiance * weight, weight); + } + + if (irradiance.a > 0.0f) + { + // Normalize irradiance + irradiance.rgb *= 1.f / irradiance.a; +#if DDGI_SRGB_BLENDING + irradiance.rgb *= irradiance.rgb; +#endif + irradiance.rgb *= 2.0f * PI; + + // Fade-out outside the probes volume + float fadeDistance = data.ProbesSpacing * 0.5f; + irradiance.rgb *= saturate(Min3(probesExtent - abs(worldPosition - probesOrigin)) / fadeDistance); + } + return irradiance.rgb; +} diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader new file mode 100644 index 000000000..59e7b0468 --- /dev/null +++ b/Source/Shaders/GI/DDGI.shader @@ -0,0 +1,439 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +// Implementation based on: +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Probes", Journal of Computer Graphics Tools, April 2019 +// Zander Majercik, Jean-Philippe Guertin, Derek Nowrouzezahrai, and Morgan McGuire +// https://morgan3d.github.io/articles/2019-04-01-ddgi/index.html and https://gdcvault.com/play/1026182/ +// +// Additional references: +// "Scaling Probe-Based Real-Time Dynamic Global Illumination for Production", https://jcgt.org/published/0010/02/01/ +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields", https://jcgt.org/published/0008/02/01/ + +#include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" +#include "./Flax/Quaternion.hlsl" +#include "./Flax/GlobalSignDistanceField.hlsl" +#include "./Flax/GI/GlobalSurfaceAtlas.hlsl" +#include "./Flax/GI/DDGI.hlsl" + +// This must match C++ +#define DDGI_TRACE_RAYS_LIMIT 512 // Limit of rays per-probe (runtime value can be smaller) +#define DDGI_TRACE_RAYS_GROUP_SIZE_X 32 +#define DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE 8 +#define DDGI_PROBE_CLASSIFY_GROUP_SIZE 32 + +META_CB_BEGIN(0, Data) +DDGIData DDGI; +GlobalSDFData GlobalSDF; +GlobalSurfaceAtlasData GlobalSurfaceAtlas; +GBufferData GBuffer; +float3 Padding0; +float IndirectLightingIntensity; +META_CB_END + +// Calculates the evenly distributed direction ray on a sphere (Spherical Fibonacci lattice) +float3 GetSphericalFibonacci(float sampleIndex, float samplesCount) +{ + float b = (sqrt(5.0f) * 0.5f + 0.5f) - 1.0f; + float s = sampleIndex * b; + float phi = (2.0f * PI) * (s - floor(s)); + float cosTheta = 1.0f - (2.0f * sampleIndex + 1.0f) * (1.0f / samplesCount); + float sinTheta = sqrt(saturate(1.0f - (cosTheta * cosTheta))); + return float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); +} + +// Calculates a random normalized ray direction (based on the ray index and the current probes rotation phrase) +float3 GetProbeRayDirection(DDGIData data, uint rayIndex) +{ + float3 direction = GetSphericalFibonacci(rayIndex, data.RaysCount); + return normalize(QuaternionRotate(data.RaysRotation, direction)); +} + +#ifdef _CS_Classify + +RWTexture2D RWProbesState : register(u0); + +Texture3D GlobalSDFTex[4] : register(t0); + +// Compute shader for updating probes state between active and inactive. +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(DDGI_PROBE_CLASSIFY_GROUP_SIZE, 1, 1)] +void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) +{ + uint probeIndex = DispatchThreadId.x; + uint probesCount = DDGI.ProbesCounts.x * DDGI.ProbesCounts.y * DDGI.ProbesCounts.z; + if (probeIndex >= probesCount) + return; + uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + probeIndex = GetDDGIScrollingProbeIndex(DDGI, probeCoords); + int2 probeDataCoords = GetDDGIProbeTexelCoords(DDGI, probeIndex); + + // Load probe state and position + float4 probeState = RWProbesState[probeDataCoords]; + float3 probePosition = GetDDGIProbeWorldPosition(DDGI, probeCoords); + // TODO: reset probe offset for scrolled probes + probePosition.xyz += probeState.xyz; + probeState.w = DDGI_PROBE_STATE_ACTIVE; + + // Use Global SDF to quickly get distance and direction to the scene geometry + float sdf; + float3 sdfNormal = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, probePosition.xyz, sdf)); + float threshold = GlobalSDF.CascadeVoxelSize[0] * 0.5f; + float distanceLimit = length(DDGI.ProbesSpacing) * 1.5f + threshold; + float relocateLimit = length(DDGI.ProbesSpacing) * 0.6f; + if (abs(sdf) > distanceLimit + threshold) // Probe is too far from geometry + { + // Disable it + probeState = float4(0, 0, 0, DDGI_PROBE_STATE_INACTIVE); + } + else if (sdf < threshold) // Probe is inside geometry + { + if (abs(sdf) < relocateLimit) + { + // Relocate it + probeState.xyz = probeState.xyz + sdfNormal * (sdf + threshold); + } + else + { + // Reset relocation + probeState.xyz = float3(0, 0, 0); + } + } + else if (sdf > relocateLimit) // Probe is far enough any geometry + { + // Reset relocation + probeState.xyz = float3(0, 0, 0); + } + + RWProbesState[probeDataCoords] = probeState; +} + +#endif + +#ifdef _CS_TraceRays + +RWTexture2D RWProbesTrace : register(u0); + +Texture3D GlobalSDFTex[4] : register(t0); +Texture3D GlobalSDFMip[4] : register(t4); +ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8); +Buffer GlobalSurfaceAtlasCulledObjects : register(t9); +Texture2D GlobalSurfaceAtlasDepth : register(t10); +Texture2D GlobalSurfaceAtlasTex : register(t11); +Texture2D ProbesState : register(t12); + +// Compute shader for tracing rays for probes using Global SDF and Global Surface Atlas. +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(DDGI_TRACE_RAYS_GROUP_SIZE_X, 1, 1)] +void CS_TraceRays(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +{ + uint rayIndex = DispatchThreadId.x; + uint probeIndex = DispatchThreadId.y; + uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + probeIndex = GetDDGIScrollingProbeIndex(DDGI, probeCoords); + + // Load current probe state and position + float4 probePositionAndState = LoadDDGIProbePositionAndState(DDGI, ProbesState, probeIndex, probeCoords); + if (probePositionAndState.w == DDGI_PROBE_STATE_INACTIVE) + return; // Skip disabled probes + float3 probeRayDirection = GetProbeRayDirection(DDGI, rayIndex); + + // Trace ray with Global SDF + GlobalSDFTrace trace; + trace.Init(probePositionAndState.xyz, probeRayDirection, 0.0f, DDGI.RayMaxDistance); + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); + + // Calculate radiance and distance + float4 radiance; + if (hit.IsHit()) + { + if (hit.HitSDF <= 0.0f && hit.HitTime <= GlobalSDF.CascadeVoxelSize[0]) + { + // Ray starts inside geometry (mark as negative distance and reduce it's influence during irradiance blending) + radiance = float4(0, 0, 0, hit.HitTime * -0.25f); + } + else + { + // Sample Global Surface Atlas to get the lighting at the hit location + float3 hitPosition = hit.GetHitPosition(trace); + float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit); + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hitPosition, -probeRayDirection, surfaceThreshold); + radiance = float4(surfaceColor.rgb, hit.HitTime); + + // Add some bias to prevent self occlusion artifacts in Chebyshev due to Global SDF being very incorrect in small scale + radiance.w = max(radiance.w + GlobalSDF.CascadeVoxelSize[hit.HitCascade] * 0.5f, 0); + } + } + else + { + // Ray hits sky + radiance.rgb = float3(0, 0, 0); // TODO: sample sky/skybox with a fallback radiance + radiance.a = 1e27f; // Sky is the limit + } + + // Write into probes trace results + RWProbesTrace[uint2(rayIndex, probeIndex)] = radiance; +} + +#endif + +#if defined(_CS_UpdateProbes) || defined(_CS_UpdateBorders) + +#if DDGI_PROBE_UPDATE_MODE == 0 +// Update irradiance +#define DDGI_PROBE_RESOLUTION DDGI_PROBE_RESOLUTION_IRRADIANCE +#else +// Update distance +#define DDGI_PROBE_RESOLUTION DDGI_PROBE_RESOLUTION_DISTANCE +#endif + +groupshared float4 CachedProbesTraceRadiance[DDGI_TRACE_RAYS_LIMIT]; +groupshared float3 CachedProbesTraceDirection[DDGI_TRACE_RAYS_LIMIT]; + +RWTexture2D RWOutput : register(u0); +Texture2D ProbesState : register(t0); +Texture2D ProbesTrace : register(t1); + +// Compute shader for updating probes irradiance or distance texture. +META_CS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(DDGI_PROBE_UPDATE_MODE=0) +META_PERMUTATION_1(DDGI_PROBE_UPDATE_MODE=1) +[numthreads(DDGI_PROBE_RESOLUTION, DDGI_PROBE_RESOLUTION, 1)] +void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupIndex : SV_GroupIndex) +{ + // Get probe index and atlas location in the atlas + uint probeIndex = GetDDGIProbeIndex(DDGI, DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); + uint probesCount = DDGI.ProbesCounts.x * DDGI.ProbesCounts.y * DDGI.ProbesCounts.z; + bool skip = probeIndex >= probesCount; + uint2 outputCoords = uint2(1, 1) + DispatchThreadId.xy + (DispatchThreadId.xy / DDGI_PROBE_RESOLUTION) * 2; + + // Clear probes that have been scrolled to a new positions (blending with current irradiance will happen the next frame) + uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + UNROLL + for (uint planeIndex = 0; planeIndex < 3; planeIndex++) + { + if (DDGI.ProbeScrollClear[planeIndex]) + { + int scrollOffset = DDGI.ProbesScrollOffsets[planeIndex]; + int scrollDirection = DDGI.ProbeScrollDirections[planeIndex]; + uint probeCount = DDGI.ProbesCounts[planeIndex]; + uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount; + if (probeCoords[planeIndex] == coord) + { + // Clear probe and return + //RWOutput[outputCoords] = float4(0, 0, 0, 0); + if (!skip) + RWOutput[outputCoords] = float4(0, 0, 0, 0); + skip = true; + } + } + } + + // Skip disabled probes + float probeState = LoadDDGIProbeState(DDGI, ProbesState, probeIndex); + if (probeState == DDGI_PROBE_STATE_INACTIVE) + skip = true; + + // Calculate octahedral projection for probe (unwraps spherical projection into a square) + float2 octahedralCoords = GetOctahedralCoords(DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); + float3 octahedralDirection = GetOctahedralDirection(octahedralCoords); + + // Load trace rays results into shared memory to reuse across whole thread group + uint count = (uint)(ceil((float)(DDGI_TRACE_RAYS_LIMIT) / (float)(DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTION))); + for (uint i = 0; i < count; i++) + { + uint rayIndex = (GroupIndex * count) + i; + if (rayIndex >= DDGI.RaysCount) + break; + CachedProbesTraceRadiance[rayIndex] = ProbesTrace[uint2(rayIndex, probeIndex)]; + CachedProbesTraceDirection[rayIndex] = GetProbeRayDirection(DDGI, rayIndex); + } + GroupMemoryBarrierWithGroupSync(); + + // TODO: optimize probes updating to build indirect dispatch args and probes indices list before tracing rays and blending irradiance/distance + if (skip) + { + // Clear probe + //RWOutput[outputCoords] = float4(0, 0, 0, 0); + return; + } + + // Loop over rays + float4 result = float4(0, 0, 0, 0); +#if DDGI_PROBE_UPDATE_MODE == 0 + uint backfacesCount = 0; + uint backfacesLimit = uint(DDGI.RaysCount * 0.1f); +#else + float distanceLimit = length(DDGI.ProbesSpacing) * 1.5f; +#endif + LOOP + for (uint rayIndex = 0; rayIndex < DDGI.RaysCount; rayIndex++) + { + float3 rayDirection = CachedProbesTraceDirection[rayIndex]; + float rayWeight = max(dot(octahedralDirection, rayDirection), 0.0f); + float4 rayRadiance = CachedProbesTraceRadiance[rayIndex]; + +#if DDGI_PROBE_UPDATE_MODE == 0 + if (rayRadiance.w < 0.0f) + { + // Count backface hits + backfacesCount++; + + // Skip further blending after reaching backfaces limit + if (backfacesCount >= backfacesLimit) + return; + continue; + } + + // Add radiance (RGB) and weight (A) + result += float4(rayRadiance.rgb * rayWeight, rayWeight); +#else + // Increase reaction speed for depth discontinuities + rayWeight = pow(rayWeight, 4.0f); + + // Add distance (R), distance^2 (G) and weight (A) + float rayDistance = min(abs(rayRadiance.w), distanceLimit); + result += float4(rayDistance * rayWeight, (rayDistance * rayDistance) * rayWeight, 0.0f, rayWeight); +#endif + } + + // Normalize results + float epsilon = (float)DDGI.RaysCount * 1e-9f; + result.rgb *= 1.0f / (2.0f * max(result.a, epsilon)); + + // Blend current value with the previous probe data + float3 previous = RWOutput[outputCoords].rgb; + float historyWeight = DDGI.ProbeHistoryWeight; + if (dot(previous, previous) == 0) + { + // Cut any blend from zero + historyWeight = 0.0f; + } +#if DDGI_PROBE_UPDATE_MODE == 0 + result *= IndirectLightingIntensity; +#if DDGI_SRGB_BLENDING + result.rgb = pow(result.rgb, 1.0f / DDGI.IrradianceGamma); +#endif + float3 irradianceDelta = result.rgb - previous.rgb; + float irradianceDeltaMax = Max3(abs(irradianceDelta)); + if (irradianceDeltaMax > 0.25f) + { + // Reduce history weight after significant lighting change + historyWeight = max(historyWeight - 0.2f, 0.0f); + } + if (irradianceDeltaMax > 0.8f) + { + // Reduce flickering during rapid brightness changes + result.rgb = previous.rgb + (irradianceDelta * 0.25f); + } + float3 resultDelta = (1.0f - historyWeight) * irradianceDelta; + if (Max3(result.rgb) < Max3(previous.rgb)) + resultDelta = min(max(abs(resultDelta), 1.0f / 1024.0f), abs(irradianceDelta)) * sign(resultDelta); + result = float4(previous.rgb + resultDelta, 1.0f); +#else + result = float4(lerp(result.rg, previous.rg, historyWeight), 0.0f, 1.0f); +#endif + + RWOutput[outputCoords] = result; +} + +// Compute shader for updating probes irradiance or distance texture borders (fills gaps between probes to support bilinear filtering) +META_CS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=0, BORDER_ROW=1) +META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=0, BORDER_ROW=0) +META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=1, BORDER_ROW=1) +META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=1, BORDER_ROW=0) +[numthreads(DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE, 1)] +void CS_UpdateBorders(uint3 DispatchThreadId : SV_DispatchThreadID) +{ + uint probeSideLength = DDGI_PROBE_RESOLUTION + 2; + uint probeSideLengthMinusOne = probeSideLength - 1; + uint2 copyCoordinates = uint2(0, 0); + uint2 threadCoordinates = DispatchThreadId.xy; +#if BORDER_ROW + threadCoordinates.y *= probeSideLength; + uint corner = DispatchThreadId.x % probeSideLength; +#else + threadCoordinates.x *= probeSideLength; + uint corner = threadCoordinates.y % probeSideLength; +#endif + if (corner == 0 || corner == probeSideLengthMinusOne) + { +#if !BORDER_ROW + // Left corner + copyCoordinates.x = threadCoordinates.x + DDGI_PROBE_RESOLUTION; + copyCoordinates.y = threadCoordinates.y - sign(corner - 1) * DDGI_PROBE_RESOLUTION; + RWOutput[threadCoordinates] = RWOutput[copyCoordinates]; + + // Right corner + threadCoordinates.x += probeSideLengthMinusOne; + copyCoordinates.x = threadCoordinates.x - DDGI_PROBE_RESOLUTION; + RWOutput[threadCoordinates] = RWOutput[copyCoordinates]; +#endif + return; + } + +#if BORDER_ROW + // Top row + uint probeStart = uint(threadCoordinates.x / probeSideLength) * probeSideLength; + uint offset = probeSideLengthMinusOne - (threadCoordinates.x % probeSideLength); + copyCoordinates = uint2(probeStart + offset, threadCoordinates.y + 1); +#else + // Left column + uint probeStart = uint(threadCoordinates.y / probeSideLength) * probeSideLength; + uint offset = probeSideLengthMinusOne - (threadCoordinates.y % probeSideLength); + copyCoordinates = uint2(threadCoordinates.x + 1, probeStart + offset); +#endif + RWOutput[threadCoordinates] = RWOutput[copyCoordinates]; + +#if BORDER_ROW + // Bottom row + threadCoordinates.y += probeSideLengthMinusOne; + copyCoordinates = uint2(probeStart + offset, threadCoordinates.y - 1); +#else + // Right column + threadCoordinates.x += probeSideLengthMinusOne; + copyCoordinates = uint2(threadCoordinates.x - 1, probeStart + offset); +#endif + RWOutput[threadCoordinates] = RWOutput[copyCoordinates]; +} + +#endif + +#ifdef _PS_IndirectLighting + +#include "./Flax/GBuffer.hlsl" +#include "./Flax/LightingCommon.hlsl" + +Texture2D ProbesState : register(t4); +Texture2D ProbesDistance : register(t5); +Texture2D ProbesIrradiance : register(t6); + +// Pixel shader for drawing indirect lighting in fullscreen +META_PS(true, FEATURE_LEVEL_SM5) +void PS_IndirectLighting(Quad_VS2PS input, out float4 output : SV_Target0) +{ + output = 0; + + // Sample GBuffer + GBufferSample gBuffer = SampleGBuffer(GBuffer, input.TexCoord); + + // Check if cannot shadow pixel + BRANCH + if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) + { + discard; + return; + } + + // Sample irradiance + float bias = 1.0f; + float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesState, ProbesDistance, ProbesIrradiance, gBuffer.WorldPos, gBuffer.Normal, bias); + + // Calculate lighting + float3 diffuseColor = GetDiffuseColor(gBuffer); + float3 diffuse = Diffuse_Lambert(diffuseColor); + output = float4(diffuse * irradiance, 1); +} + +#endif diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl index b1ea4d5d5..6d1d0adbb 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl @@ -7,7 +7,7 @@ #define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling #define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4 #define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile -#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.05f // Cut-off value for tiles transitions blending during sampling #define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) struct GlobalSurfaceTile diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index b9e805e80..c36f99b8f 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -267,7 +267,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target //return float4(hit.HitNormal * 0.5f + 0.5f, 1); // Sample Global Surface Atlas at the hit location - float surfaceThreshold = hit.HitCascade * 10.0f + 20.0f; // Scale the threshold based on the hit cascade (less precision) + float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit); float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold); return float4(surfaceColor.rgb, 1); } diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index f851fc192..0d561db69 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -45,6 +45,7 @@ struct GlobalSDFHit float HitTime; uint HitCascade; uint StepsCount; + float HitSDF; bool IsHit() { @@ -180,6 +181,7 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] // Surface hit hit.HitTime = max(stepTime + stepDistance - minSurfaceThickness, 0.0f); hit.HitCascade = cascade; + hit.HitSDF = stepDistance; if (trace.NeedsHitNormal) { // Calculate hit normal from SDF gradient @@ -202,3 +204,10 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] } return hit; } + +// Calculates the surface threshold for Global Surface Atlas sampling which matches the Global SDF trace to reduce artifacts +float GetGlobalSurfaceAtlasThreshold(GlobalSDFHit hit) +{ + // Scale the threshold based on the hit cascade (less precision) + return hit.HitCascade * 10.0f + 20.0f; +} From 72c04743972e274f80604b6acb6f984dbcf9ea24 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 23 May 2022 12:53:39 +0200 Subject: [PATCH 02/27] Add realtime sky/skybox capturing for GI --- Source/Engine/Graphics/RenderBuffers.cpp | 10 ++ Source/Engine/Graphics/RenderBuffers.h | 20 ++-- Source/Engine/Level/Actors/Sky.cpp | 5 + Source/Engine/Level/Actors/Sky.h | 1 + Source/Engine/Level/Actors/Skybox.cpp | 5 + Source/Engine/Level/Actors/Skybox.h | 1 + Source/Engine/Renderer/DrawCall.h | 5 + Source/Engine/Renderer/GBufferPass.cpp | 96 +++++++++++++++---- Source/Engine/Renderer/GBufferPass.h | 9 ++ .../GI/DynamicDiffuseGlobalIllumination.cpp | 2 + .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 12 ++- Source/Shaders/GI/DDGI.shader | 3 +- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 26 +++-- 13 files changed, 156 insertions(+), 39 deletions(-) diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index a98b13d91..51e4a3436 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -111,6 +111,16 @@ GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context) return HalfResDepth; } +const RenderBuffers::CustomBuffer* RenderBuffers::FindCustomBuffer(const StringView& name) const +{ + for (const CustomBuffer* e : CustomBuffers) + { + if (e->Name == name) + return e; + } + return nullptr; +} + uint64 RenderBuffers::GetMemoryUsage() const { uint64 result = 0; diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index 149b4c12b..a6f231c0b 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -156,28 +156,24 @@ public: return _viewport; } + const CustomBuffer* FindCustomBuffer(const StringView& name) const; + template const T* FindCustomBuffer(const StringView& name) const { - for (CustomBuffer* e : CustomBuffers) - { - if (e->Name == name) - return (const T*)e; - } - return nullptr; + return (const T*)FindCustomBuffer(name); } template T* GetCustomBuffer(const StringView& name) { - for (CustomBuffer* e : CustomBuffers) + CustomBuffer* result = (CustomBuffer*)FindCustomBuffer(name); + if (!result) { - if (e->Name == name) - return (T*)e; + result = New(); + result->Name = name; + CustomBuffers.Add(result); } - CustomBuffer* result = New(); - result->Name = name; - CustomBuffers.Add(result); return (T*)result; } diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 59d95d3f8..6c6f73494 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -195,6 +195,11 @@ void Sky::DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureV context->DrawFullscreenTriangle(); } +bool Sky::IsDynamicSky() const +{ + return !IsStatic() || (SunLight && !SunLight->IsStatic()); +} + void Sky::ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) { // Get precomputed cache and bind it to the pipeline diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index 5f2f78a2f..389745e14 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -74,6 +74,7 @@ public: void DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) override; // [ISkyRenderer] + bool IsDynamicSky() const override; void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index f76beff7e..af14c6488 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -87,6 +87,11 @@ bool Skybox::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) return false; } +bool Skybox::IsDynamicSky() const +{ + return !IsStatic(); +} + void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) { // Prepare mock draw call data diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h index 3e44dd015..99c28bdd8 100644 --- a/Source/Engine/Level/Actors/Skybox.h +++ b/Source/Engine/Level/Actors/Skybox.h @@ -68,6 +68,7 @@ public: bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; // [ISkyRenderer] + bool IsDynamicSky() const override; void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 16f2dd590..0c353dd39 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -35,6 +35,11 @@ class ISkyRenderer { public: + /// + /// Returns true if sky is realtime, otherwise it's static. + /// + virtual bool IsDynamicSky() const = 0; + /// /// Apply sky material/shader state to the GPU pipeline with custom parameters set (render to GBuffer). /// diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index e8b753b09..e410bce96 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -14,10 +14,12 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Level/Actors/Decal.h" +#include "Engine/Engine/Engine.h" PACK_STRUCT(struct GBufferPassData{ GBufferData GBuffer; @@ -144,7 +146,6 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer // Cache data auto device = GPUDevice::Instance; auto context = device->GetMainContext(); - auto& view = renderContext.View; GPUTextureView* targetBuffers[5] = { lightBuffer, @@ -153,7 +154,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer renderContext.Buffers->GBuffer2->View(), renderContext.Buffers->GBuffer3->View(), }; - view.Pass = DrawPass::GBuffer; + renderContext.View.Pass = DrawPass::GBuffer; // Clear GBuffer { @@ -222,20 +223,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer 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); + DrawSky(renderContext, context); } context->ResetRenderTarget(); @@ -280,6 +268,65 @@ void GBufferPass::RenderDebug(RenderContext& renderContext) context->ResetSR(); } +// Custom render buffer for realtime skybox capturing (eg. used by GI). +class SkyboxCustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + uint64 LastCaptureFrame = 0; + GPUTexture* Skybox = nullptr; + + ~SkyboxCustomBuffer() + { + RenderTargetPool::Release(Skybox); + } +}; + +GPUTextureView* GBufferPass::RenderSkybox(RenderContext& renderContext, GPUContext* context) +{ + GPUTextureView* result = nullptr; + if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered()) + { + // Initialize skybox texture + auto& skyboxData = *renderContext.Buffers->GetCustomBuffer(TEXT("Skybox")); + bool dirty = false; + const int32 resolution = 16; + if (!skyboxData.Skybox) + { + const auto desc = GPUTextureDescription::NewCube(resolution, PixelFormat::R11G11B10_Float); + skyboxData.Skybox = RenderTargetPool::Get(desc); + if (!skyboxData.Skybox) + return nullptr; + dirty = true; + } + + // Redraw sky from time-to-time (dynamic skies can be animated, static skies can have textures streamed) + const uint32 redrawFramesCount = renderContext.List->Sky->IsDynamicSky() ? 4 : 240; + if (Engine::FrameCount - skyboxData.LastCaptureFrame >= redrawFramesCount) + dirty = true; + + if (dirty) + { + PROFILE_GPU_CPU("Skybox"); + skyboxData.LastCaptureFrame = Engine::FrameCount; + const RenderView originalView = renderContext.View; + renderContext.View.Pass = DrawPass::GBuffer; + renderContext.View.SetUpCube(10.0f, 10000.0f, originalView.Position); + for (int32 faceIndex = 0; faceIndex < 6; faceIndex++) + { + renderContext.View.SetFace(faceIndex); + context->SetRenderTarget(skyboxData.Skybox->View(faceIndex)); + context->SetViewportAndScissors(resolution, resolution); + DrawSky(renderContext, context); + } + renderContext.View = originalView; + context->ResetRenderTarget(); + } + + result = skyboxData.Skybox->ViewArray(); + } + return result; +} + #if USE_EDITOR void GBufferPass::DrawMaterialComplexity(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer) @@ -326,6 +373,23 @@ void GBufferPass::SetInputs(const RenderView& view, GBufferData& gBuffer) Matrix::Transpose(view.IP, gBuffer.InvProjectionMatrix); } +void GBufferPass::DrawSky(RenderContext& renderContext, GPUContext* context) +{ + // Cache data + auto model = _skyModel.Get(); + auto box = model->GetBox(); + + // Calculate sphere model transform to cover far plane + Matrix m1, m2; + Matrix::Scaling(renderContext.View.Far / (box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum + Matrix::CreateWorld(renderContext.View.Position, Vector3::Up, Vector3::Backward, m2); // Rotate sphere model + m1 *= m2; + + // Draw sky + renderContext.List->Sky->ApplySky(context, renderContext, m1); + model->Render(context); +} + void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer) { // Skip if no decals to render diff --git a/Source/Engine/Renderer/GBufferPass.h b/Source/Engine/Renderer/GBufferPass.h index e31b0d873..8ff3f6091 100644 --- a/Source/Engine/Renderer/GBufferPass.h +++ b/Source/Engine/Renderer/GBufferPass.h @@ -37,6 +37,14 @@ public: /// The rendering context. void RenderDebug(RenderContext& renderContext); + /// + /// Renders the sky or skybox into low-resolution cubemap. Can be used to sample realtime sky lighting in GI passes. + /// + /// The rendering context. + /// The GPU context. + /// Rendered cubemap or null if not ready or failed. + GPUTextureView* RenderSkybox(RenderContext& renderContext, GPUContext* context); + #if USE_EDITOR void DrawMaterialComplexity(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer); #endif @@ -54,6 +62,7 @@ public: private: + void DrawSky(RenderContext& renderContext, GPUContext* context); void DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer); #if COMPILE_WITH_DEV_ENV diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 45fb8ba58..952fbce3e 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -272,6 +272,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, GlobalSurfaceAtlasPass::BindingData bindingDataSurfaceAtlas; if (GlobalSurfaceAtlasPass::Instance()->Render(renderContext, context, bindingDataSurfaceAtlas)) return true; + GPUTextureView* skybox = GBufferPass::Instance()->RenderSkybox(renderContext, context); // Skip if already done in the current frame const auto currentFrame = Engine::FrameCount; @@ -411,6 +412,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View()); context->BindSR(12, ddgiData.Result.ProbesState); + context->BindSR(13, skybox); context->BindUA(0, ddgiData.ProbesTrace->View()); context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesCount, 1); context->ResetUA(); diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 8803c7e5b..4aa4382a1 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -2,6 +2,7 @@ #include "GlobalSurfaceAtlasPass.h" #include "../GlobalSignDistanceFieldPass.h" +#include "../GBufferPass.h" #include "../RenderList.h" #include "../ShadowsPass.h" #include "Engine/Core/Math/Matrix3x3.h" @@ -36,7 +37,7 @@ PACK_STRUCT(struct Data0 { Vector3 ViewWorldPos; float ViewNearPlane; - float Padding00; + float SkyboxIntensity; uint32 CulledObjectsCapacity; float LightShadowsStrength; float ViewFarPlane; @@ -815,11 +816,12 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->Draw(output, renderContext.Buffers->GBuffer0); return; } + GPUTextureView* skybox = GBufferPass::Instance()->RenderSkybox(renderContext, context); PROFILE_GPU_CPU("Global Surface Atlas Debug"); const Vector2 outputSize(output->Size()); + Data0 data; { - Data0 data; data.ViewWorldPos = renderContext.View.Position; data.ViewNearPlane = renderContext.View.Near; data.ViewFarPlane = renderContext.View.Far; @@ -827,6 +829,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex data.ViewFrustumWorldRays[i] = Vector4(renderContext.List->FrustumCornersWs[i + 4], 0); data.GlobalSDF = bindingDataSDF.Constants; data.GlobalSurfaceAtlas = bindingData.Constants; + data.SkyboxIntensity = 1.0f; context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } @@ -838,6 +841,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); context->BindSR(10, bindingData.AtlasDepth->View()); + context->BindSR(12, skybox); context->SetState(_psDebug); context->SetRenderTarget(output->View()); { @@ -850,6 +854,10 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y)); context->DrawFullscreenTriangle(); + // Disable skybox + data.SkyboxIntensity = 0.0f; + context->UpdateCB(_cb0, &data); + // Bottom left - diffuse context->BindSR(11, bindingData.AtlasGBuffer0->View()); context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y)); diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 59e7b0468..178707c08 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -121,6 +121,7 @@ Buffer GlobalSurfaceAtlasCulledObjects : register(t9); Texture2D GlobalSurfaceAtlasDepth : register(t10); Texture2D GlobalSurfaceAtlasTex : register(t11); Texture2D ProbesState : register(t12); +TextureCube Skybox : register(t13); // Compute shader for tracing rays for probes using Global SDF and Global Surface Atlas. META_CS(true, FEATURE_LEVEL_SM5) @@ -167,7 +168,7 @@ void CS_TraceRays(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_Dispat else { // Ray hits sky - radiance.rgb = float3(0, 0, 0); // TODO: sample sky/skybox with a fallback radiance + radiance.rgb = Skybox.SampleLevel(SamplerLinearClamp, probeRayDirection, 0); radiance.a = 1e27f; // Sky is the limit } diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index c36f99b8f..9057419a5 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -12,7 +12,7 @@ META_CB_BEGIN(0, Data) float3 ViewWorldPos; float ViewNearPlane; -float Padding00; +float SkyboxIntensity; uint CulledObjectsCapacity; float LightShadowsStrength; float ViewFarPlane; @@ -245,6 +245,7 @@ ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8); Buffer GlobalSurfaceAtlasCulledObjects : register(t9); Texture2D GlobalSurfaceAtlasDepth : register(t10); Texture2D GlobalSurfaceAtlasTex : register(t11); +TextureCube Skybox : register(t12); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) @@ -262,14 +263,23 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane); trace.NeedsHitNormal = true; GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); - if (!hit.IsHit()) - return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); - //return float4(hit.HitNormal * 0.5f + 0.5f, 1); - // Sample Global Surface Atlas at the hit location - float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit); - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold); - return float4(surfaceColor.rgb, 1); + float3 color; + if (hit.IsHit()) + { + // Sample Global Surface Atlas at the hit location + float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit); + color = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold).rgb; + //color = hit.HitNormal * 0.5f + 0.5f; + } + else + { + // Sample skybox + float3 skybox = Skybox.SampleLevel(SamplerLinearClamp, viewRay, 0); + float3 sky = float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f); + color = lerp(sky, skybox, SkyboxIntensity); + } + return float4(color, 1); } #endif From 89ac470733c8c84a08f8f86a08baa74c95acd9b7 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 23 May 2022 14:10:45 +0200 Subject: [PATCH 03/27] Add indirect lighting to Global Surface Atlas to achieve infinite GI bounces --- .../GI/DynamicDiffuseGlobalIllumination.cpp | 3 +- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 63 ++++++++++++++++--- .../Renderer/GI/GlobalSurfaceAtlasPass.h | 1 + Source/Shaders/GI/GlobalSurfaceAtlas.shader | 24 ++++++- 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 952fbce3e..7045cc77d 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -455,6 +455,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, } // Render indirect lighting + if (lightBuffer) { PROFILE_GPU_CPU("Indirect Lighting"); #if 0 @@ -477,7 +478,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, #if USE_EDITOR // Probes debug drawing - if (debugProbes) + if (debugProbes && lightBuffer) { PROFILE_GPU_CPU("Debug Probes"); if (!_debugModel) diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 4aa4382a1..2c52711a1 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "GlobalSurfaceAtlasPass.h" +#include "DynamicDiffuseGlobalIllumination.h" #include "../GlobalSignDistanceFieldPass.h" #include "../GBufferPass.h" #include "../RenderList.h" @@ -44,6 +45,7 @@ PACK_STRUCT(struct Data0 Vector4 ViewFrustumWorldRays[4]; GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; + DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI; LightData Light; }); @@ -123,7 +125,7 @@ public: GPUTexture* AtlasGBuffer0 = nullptr; GPUTexture* AtlasGBuffer1 = nullptr; GPUTexture* AtlasGBuffer2 = nullptr; - GPUTexture* AtlasDirectLight = nullptr; + GPUTexture* AtlasLighting = nullptr; GPUBuffer* ChunksBuffer = nullptr; GPUBuffer* CulledObjectsBuffer = nullptr; int32 CulledObjectsCounterIndex = -1; @@ -155,7 +157,7 @@ public: RenderTargetPool::Release(AtlasGBuffer0); RenderTargetPool::Release(AtlasGBuffer1); RenderTargetPool::Release(AtlasGBuffer2); - RenderTargetPool::Release(AtlasDirectLight); + RenderTargetPool::Release(AtlasLighting); ClearObjects(); } @@ -238,13 +240,17 @@ bool GlobalSurfaceAtlasPass::setupResources() psDesc.DepthFunc = ComparisonFunc::Never; psDesc.BlendMode = BlendingMode::Add; psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; - psDesc.PS = shader->GetPS("PS_DirectLighting", 0); + psDesc.PS = shader->GetPS("PS_Lighting", 0); if (_psDirectLighting0->Init(psDesc)) return true; _psDirectLighting1 = device->CreatePipelineState(); - psDesc.PS = shader->GetPS("PS_DirectLighting", 1); + psDesc.PS = shader->GetPS("PS_Lighting", 1); if (_psDirectLighting1->Init(psDesc)) return true; + _psIndirectLighting = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_Lighting", 2); + if (_psIndirectLighting->Init(psDesc)) + return true; } return false; @@ -257,6 +263,7 @@ void GlobalSurfaceAtlasPass::OnShaderReloading(Asset* obj) SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); + SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); SAFE_DELETE_GPU_RESOURCE(_psDebug); invalidateResources(); } @@ -274,6 +281,7 @@ void GlobalSurfaceAtlasPass::Dispose() SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); + SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; _shader = nullptr; @@ -323,7 +331,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co INIT_ATLAS_TEXTURE(AtlasGBuffer0, GBUFFER0_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer1, GBUFFER1_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer2, GBUFFER2_FORMAT); - INIT_ATLAS_TEXTURE(AtlasDirectLight, LIGHT_BUFFER_FORMAT); + INIT_ATLAS_TEXTURE(AtlasLighting, LIGHT_BUFFER_FORMAT); desc.Flags = GPUTextureFlags::DepthStencil | GPUTextureFlags::ShaderResource; INIT_ATLAS_TEXTURE(AtlasDepth, PixelFormat::D16_UNorm); #undef INIT_ATLAS_TEXTURE @@ -679,7 +687,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.Atlas[1] = surfaceAtlasData.AtlasGBuffer0; result.Atlas[2] = surfaceAtlasData.AtlasGBuffer1; result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; - result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; + result.Atlas[4] = surfaceAtlasData.AtlasLighting; result.Chunks = surfaceAtlasData.ChunksBuffer; result.CulledObjects = surfaceAtlasData.CulledObjectsBuffer; surfaceAtlasData.Result = result; @@ -690,14 +698,14 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co PROFILE_GPU_CPU("Direct Lighting"); // Copy emissive light into the final direct lighting atlas - // TODO: test perf diff when manually copying only dirty object tiles and dirty light tiles + // TODO: test perf diff when manually copying only dirty object tiles and dirty light tiles together with indirect lighting { PROFILE_GPU_CPU("Copy Emissive"); - context->CopyTexture(surfaceAtlasData.AtlasDirectLight, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); + context->CopyTexture(surfaceAtlasData.AtlasLighting, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); } context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); - context->SetRenderTarget(surfaceAtlasData.AtlasDirectLight->View()); + context->SetRenderTarget(surfaceAtlasData.AtlasLighting->View()); context->BindSR(0, surfaceAtlasData.AtlasGBuffer0->View()); context->BindSR(1, surfaceAtlasData.AtlasGBuffer1->View()); context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View()); @@ -795,7 +803,38 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->SetState(_psDirectLighting1); VB_DRAW(); } + if (renderContext.View.Flags & ViewFlags::GI) + { + // TODO: add option to PostFx Volume for realtime GI type (None, DDGI) + DynamicDiffuseGlobalIlluminationPass::BindingData bindingDataDDGI; + if (!DynamicDiffuseGlobalIlluminationPass::Instance()->Get(renderContext.Buffers, bindingDataDDGI)) + { + // Collect tiles to shade + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + VB_WRITE_TILE(tile); + } + } + // Draw draw indirect light + data.DDGI = bindingDataDDGI.Constants; + context->BindSR(5, bindingDataDDGI.ProbesState); + context->BindSR(6, bindingDataDDGI.ProbesDistance); + context->BindSR(7, bindingDataDDGI.ProbesIrradiance); + context->UpdateCB(_cb0, &data); + context->SetState(_psIndirectLighting); + VB_DRAW(); + } + } + + context->ResetSR(); context->ResetRenderTarget(); } @@ -809,6 +848,12 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output) { + // Render all dependant effects + if (renderContext.View.Flags & ViewFlags::GI) + { + // TODO: add option to PostFx Volume for realtime GI type (None, DDGI) + DynamicDiffuseGlobalIlluminationPass::Instance()->Render(renderContext, context, nullptr); + } GlobalSignDistanceFieldPass::BindingData bindingDataSDF; BindingData bindingData; if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF) || Render(renderContext, context, bindingData)) diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h index ca33ddc68..64b3e9705 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h @@ -47,6 +47,7 @@ private: GPUPipelineState* _psClear = nullptr; GPUPipelineState* _psDirectLighting0 = nullptr; GPUPipelineState* _psDirectLighting1 = nullptr; + GPUPipelineState* _psIndirectLighting = nullptr; GPUPipelineState* _psDebug = nullptr; GPUConstantBuffer* _cb0 = nullptr; GPUShaderProgramCS* _csCullObjects; diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 9057419a5..83707ac38 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -8,6 +8,7 @@ #include "./Flax/LightingCommon.hlsl" #include "./Flax/GlobalSignDistanceField.hlsl" #include "./Flax/GI/GlobalSurfaceAtlas.hlsl" +#include "./Flax/GI/DDGI.hlsl" META_CB_BEGIN(0, Data) float3 ViewWorldPos; @@ -19,6 +20,7 @@ float ViewFarPlane; float4 ViewFrustumWorldRays[4]; GlobalSDFData GlobalSDF; GlobalSurfaceAtlasData GlobalSurfaceAtlas; +DDGIData DDGI; LightData Light; META_CB_END @@ -60,7 +62,7 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl RT2 = float4(1, 0, 0, 0); } -#ifdef _PS_DirectLighting +#ifdef _PS_Lighting #include "./Flax/GBuffer.hlsl" #include "./Flax/Matrix.hlsl" @@ -68,14 +70,21 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl // GBuffer+Depth at 0-3 slots Buffer GlobalSurfaceAtlasObjects : register(t4); +#if INDIRECT_LIGHT +Texture2D ProbesState : register(t5); +Texture2D ProbesDistance : register(t6); +Texture2D ProbesIrradiance : register(t7); +#else Texture3D GlobalSDFTex[4] : register(t5); Texture3D GlobalSDFMip[4] : register(t9); +#endif // Pixel shader for Global Surface Atlas shading with direct light contribution META_PS(true, FEATURE_LEVEL_SM5) META_PERMUTATION_1(RADIAL_LIGHT=0) META_PERMUTATION_1(RADIAL_LIGHT=1) -float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target +META_PERMUTATION_1(INDIRECT_LIGHT=1) +float4 PS_Lighting(AtlasVertexOutput input) : SV_Target { // Load current tile info GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasObjects, input.TileAddress); @@ -104,6 +113,16 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target float4x4 tileLocalToWorld = Inverse(tile.WorldToLocal); gBuffer.WorldPos = mul(float4(gBufferTilePos, 1), tileLocalToWorld).xyz; +#if INDIRECT_LIGHT + // Sample irradiance + float bias = 1.0f; + float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesState, ProbesDistance, ProbesIrradiance, gBuffer.WorldPos, gBuffer.Normal, bias); + + // Calculate lighting + float3 diffuseColor = GetDiffuseColor(gBuffer); + float3 diffuse = Diffuse_Lambert(diffuseColor); + float4 light = float4(diffuse * irradiance, 1); +#else // Calculate shadowing float3 L = Light.Direction; #if RADIAL_LIGHT @@ -150,6 +169,7 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target bool isSpotLight = false; #endif float4 light = GetLighting(ViewWorldPos, Light, gBuffer, shadowMask, RADIAL_LIGHT, isSpotLight); +#endif return light; } From e6a06832bf6fb0ab080720890adc2130f1cd14e6 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 25 May 2022 10:39:24 +0200 Subject: [PATCH 04/27] Fix invalid border corner pixel copy in probe image --- Source/Shaders/GI/DDGI.shader | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 178707c08..d6713e67d 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -347,6 +347,9 @@ META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=1, BORDER_ROW=0) [numthreads(DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE, 1)] void CS_UpdateBorders(uint3 DispatchThreadId : SV_DispatchThreadID) { +#define COPY_PIXEL RWOutput[threadCoordinates] = RWOutput[copyCoordinates] +#define COPY_PIXEL_DEBUG RWOutput[threadCoordinates] = float4(5, 0, 0, 1) + uint probeSideLength = DDGI_PROBE_RESOLUTION + 2; uint probeSideLengthMinusOne = probeSideLength - 1; uint2 copyCoordinates = uint2(0, 0); @@ -363,13 +366,13 @@ void CS_UpdateBorders(uint3 DispatchThreadId : SV_DispatchThreadID) #if !BORDER_ROW // Left corner copyCoordinates.x = threadCoordinates.x + DDGI_PROBE_RESOLUTION; - copyCoordinates.y = threadCoordinates.y - sign(corner - 1) * DDGI_PROBE_RESOLUTION; - RWOutput[threadCoordinates] = RWOutput[copyCoordinates]; + copyCoordinates.y = threadCoordinates.y - sign((int)corner - 1) * DDGI_PROBE_RESOLUTION; + COPY_PIXEL; // Right corner threadCoordinates.x += probeSideLengthMinusOne; copyCoordinates.x = threadCoordinates.x - DDGI_PROBE_RESOLUTION; - RWOutput[threadCoordinates] = RWOutput[copyCoordinates]; + COPY_PIXEL; #endif return; } @@ -385,7 +388,7 @@ void CS_UpdateBorders(uint3 DispatchThreadId : SV_DispatchThreadID) uint offset = probeSideLengthMinusOne - (threadCoordinates.y % probeSideLength); copyCoordinates = uint2(threadCoordinates.x + 1, probeStart + offset); #endif - RWOutput[threadCoordinates] = RWOutput[copyCoordinates]; + COPY_PIXEL; #if BORDER_ROW // Bottom row @@ -396,7 +399,9 @@ void CS_UpdateBorders(uint3 DispatchThreadId : SV_DispatchThreadID) threadCoordinates.x += probeSideLengthMinusOne; copyCoordinates = uint2(threadCoordinates.x - 1, probeStart + offset); #endif - RWOutput[threadCoordinates] = RWOutput[copyCoordinates]; + COPY_PIXEL; +#undef COPY_PIXEL +#undef COPY_PIXEL_DEBUG } #endif From c2082925c703c8dafe16bf663e4b222e4902a16f Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 26 May 2022 15:09:37 +0200 Subject: [PATCH 05/27] Improve multi-bounce irradiance lighting by limiting the diffuse color intensity --- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 83707ac38..c917583d7 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -120,6 +120,7 @@ float4 PS_Lighting(AtlasVertexOutput input) : SV_Target // Calculate lighting float3 diffuseColor = GetDiffuseColor(gBuffer); + diffuseColor = min(diffuseColor, 0.9f); // Nothing reflects diffuse like perfectly in the real world (ensure to have energy loss at each light bounce) float3 diffuse = Diffuse_Lambert(diffuseColor); float4 light = float4(diffuse * irradiance, 1); #else From 33772299664ee7813ebb3227ff642205839726bc Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 26 May 2022 15:10:26 +0200 Subject: [PATCH 06/27] Add color grading and postfx for Glboal Surface Atlas lighting debug view to increase readability --- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 2c52711a1..f6c13ac01 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -17,6 +17,9 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Renderer/ColorGradingPass.h" +#include "Engine/Renderer/EyeAdaptationPass.h" +#include "Engine/Renderer/PostProcessingPass.h" #include "Engine/Utilities/RectPack.h" // This must match HLSL @@ -888,17 +891,41 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->BindSR(10, bindingData.AtlasDepth->View()); context->BindSR(12, skybox); context->SetState(_psDebug); - context->SetRenderTarget(output->View()); { Vector2 outputSizeThird = outputSize * 0.333f; Vector2 outputSizeTwoThird = outputSize * 0.666f; + GPUTexture* tempBuffer = renderContext.Buffers->RT2_FloatRGB; + context->SetRenderTarget(tempBuffer->View()); + // Full screen - direct light context->BindSR(11, bindingData.AtlasLighting->View()); context->SetViewport(outputSize.X, outputSize.Y); context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y)); context->DrawFullscreenTriangle(); + // Color Grading and Post-Processing to improve readability in bright/dark scenes + context->ResetRenderTarget(); + auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext); + EyeAdaptationPass::Instance()->Render(renderContext, tempBuffer); + PostProcessingPass::Instance()->Render(renderContext, tempBuffer, output, colorGradingLUT); + RenderTargetPool::Release(colorGradingLUT); + context->ResetRenderTarget(); + + // Rebind resources + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); + } + context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); + context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); + context->BindSR(10, bindingData.AtlasDepth->View()); + context->BindSR(12, skybox); + context->BindCB(0, _cb0); + context->SetState(_psDebug); + context->SetRenderTarget(output->View()); + // Disable skybox data.SkyboxIntensity = 0.0f; context->UpdateCB(_cb0, &data); From 48b3a34182c691af1cd87a13c96b97165aafb323 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 26 May 2022 15:43:55 +0200 Subject: [PATCH 07/27] Fix potential issue with SR/RT slots --- Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 7045cc77d..a81f6c939 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -527,5 +527,8 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, } #endif + context->ResetRenderTarget(); + context->ResetSR(); + return false; } From eb6050cf274347b5c592c9565084f8ef7bd32f3a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 27 May 2022 11:44:39 +0200 Subject: [PATCH 08/27] Improve probes relocation algorithm to reduce visual artifacts due to probes flickering --- .../Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp | 1 + Source/Shaders/GI/DDGI.hlsl | 14 ++++++-------- Source/Shaders/GI/DDGI.shader | 10 ++++++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index f6c13ac01..4e6a6b566 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -896,6 +896,7 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex Vector2 outputSizeTwoThird = outputSize * 0.666f; GPUTexture* tempBuffer = renderContext.Buffers->RT2_FloatRGB; + context->Clear(tempBuffer->View(), Color::Black); context->SetRenderTarget(tempBuffer->View()); // Full screen - direct light diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index b36cc5d3d..69619faa9 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -97,16 +97,10 @@ float LoadDDGIProbeState(DDGIData data, Texture2D probesState, uint prob // Loads probe world-space position (XYZ) and probe state (W) float4 LoadDDGIProbePositionAndState(DDGIData data, Texture2D probesState, uint probeIndex, uint3 probeCoords) { - float4 result; - result.xyz = GetDDGIProbeWorldPosition(data, probeCoords); - - // Probe state contains relocation's offset and the classification's state int2 probeDataCoords = GetDDGIProbeTexelCoords(data, probeIndex); float4 probeState = probesState.Load(int3(probeDataCoords, 0)); - result.xyz += probeState.xyz; - result.w = probeState.w; - - return result; + probeState.xyz += GetDDGIProbeWorldPosition(data, probeCoords); + return probeState; } // Calculates texture UVs for sampling probes atlas texture (irradiance or distance) @@ -193,6 +187,10 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur probeIrradiance = pow(probeIrradiance, data.IrradianceGamma * 0.5f); #endif + // Debug probe offset visualization + //float4 probeState = probesState.Load(int3(GetDDGIProbeTexelCoords(data, probeIndex), 0)); + //probeIrradiance = float3(max(frac(probeState.xyz) * 2, 0.1f)); + // Accumulate weighted irradiance irradiance += float4(probeIrradiance * weight, weight); } diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index d6713e67d..1e4dd1200 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -90,8 +90,13 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) { if (abs(sdf) < relocateLimit) { - // Relocate it - probeState.xyz = probeState.xyz + sdfNormal * (sdf + threshold); + float3 offsetToAdd = sdfNormal * (sdf + threshold); + if (distance(probeState.xyz, offsetToAdd) < relocateLimit) + { + // Relocate it + probeState.xyz = probeState.xyz + offsetToAdd; + } + // TODO: maybe sample SDF at the relocated location and disable probe if it's still in the geometry? } else { @@ -400,6 +405,7 @@ void CS_UpdateBorders(uint3 DispatchThreadId : SV_DispatchThreadID) copyCoordinates = uint2(threadCoordinates.x - 1, probeStart + offset); #endif COPY_PIXEL; + #undef COPY_PIXEL #undef COPY_PIXEL_DEBUG } From d0a6f82cc282ce5ebde4869284e727e6ef3f1a6e Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 27 May 2022 12:07:02 +0200 Subject: [PATCH 09/27] Fix updating Global Surface Atlas for the static object that rendering state was modified --- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 4e6a6b566..2b8675ff0 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -117,7 +117,7 @@ struct GlobalSurfaceAtlasObject } }; -class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer +class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener { public: int32 Resolution = 0; @@ -170,6 +170,33 @@ public: SAFE_DELETE_GPU_RESOURCE(CulledObjectsBuffer); Clear(); } + + // [ISceneRenderingListener] + void OnSceneRenderingAddActor(Actor* a) override + { + } + + void OnSceneRenderingUpdateActor(Actor* a, const BoundingSphere& prevBounds) override + { + // Dirty static objects to redraw when changed (eg. material modification) + if (a->HasStaticFlag(StaticFlags::Lightmap)) + { + GlobalSurfaceAtlasObject* object = Objects.TryGet(a); + if (object) + { + // Dirty object to redraw + object->LastFrameDirty = 0; + } + } + } + + void OnSceneRenderingRemoveActor(Actor* a) override + { + } + + void OnSceneRenderingClear(SceneRendering* scene) override + { + } }; void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, void* actorObject, int32 tileIndex) @@ -358,6 +385,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.ClearObjects(); } } + for (SceneRendering* scene : renderContext.List->Scenes) + surfaceAtlasData.ListenSceneRendering(scene); if (!surfaceAtlasData.AtlasTiles) surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); if (!_vertexBuffer) From bdc7b3b754fe83ee067388613cb9a64f06e073a1 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 27 May 2022 12:07:27 +0200 Subject: [PATCH 10/27] Add updating ModelInstanceActor render state on material change --- Source/Engine/Graphics/Models/ModelInstanceEntry.h | 4 ++++ Source/Engine/Level/Actors/ModelInstanceActor.cpp | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/Source/Engine/Graphics/Models/ModelInstanceEntry.h b/Source/Engine/Graphics/Models/ModelInstanceEntry.h index 65554ef14..57bdce8cd 100644 --- a/Source/Engine/Graphics/Models/ModelInstanceEntry.h +++ b/Source/Engine/Graphics/Models/ModelInstanceEntry.h @@ -38,6 +38,10 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(ModelInstanceEntry); public: bool operator==(const ModelInstanceEntry& other) const; + FORCE_INLINE bool operator!=(const ModelInstanceEntry& other) const + { + return !operator==(other); + } }; /// diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index 8cf3a3620..e0164c82a 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -11,15 +11,25 @@ ModelInstanceActor::ModelInstanceActor(const SpawnParams& params) void ModelInstanceActor::SetEntries(const Array& value) { + bool anyChanged = false; Entries.Resize(value.Count()); for (int32 i = 0; i < value.Count(); i++) + { + anyChanged |= Entries[i] != value[i]; Entries[i] = value[i]; + } + if (anyChanged && _sceneRenderingKey != -1) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material) { CHECK(entryIndex >= 0 && entryIndex < Entries.Count()); + if (Entries[entryIndex].Material == material) + return; Entries[entryIndex].Material = material; + if (_sceneRenderingKey != -1) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 entryIndex) @@ -28,6 +38,8 @@ MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 CHECK_RETURN(material && !material->WaitForLoaded(), nullptr); const auto result = material->CreateVirtualInstance(); Entries[entryIndex].Material = result; + if (_sceneRenderingKey != -1) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); return result; } From 641d04a50ee4f6a9547733fc850e64e9fda0e93b Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Tue, 31 May 2022 15:52:19 +0200 Subject: [PATCH 11/27] Improve GI quality --- .../DebugMaterials/DDGIDebugProbes.flax | 4 ++-- Content/Shaders/GI/DDGI.flax | 4 ++-- Content/Shaders/GI/GlobalSurfaceAtlas.flax | 4 ++-- .../GI/DynamicDiffuseGlobalIllumination.cpp | 9 +++++--- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 14 +++++++---- Source/Shaders/GI/DDGI.shader | 23 +++++++++---------- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 1 + 7 files changed, 34 insertions(+), 25 deletions(-) diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax index f38eb04bd..49c91d5b4 100644 --- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd7232c07e5969b4db266528006dc0bad9b737f77da7bca56730fcf652509e9a -size 37759 +oid sha256:78c41dd01f6f2740fbdd08c2e0f05875398f38ba8e09bd91495636281a6ec6cc +size 37799 diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index a8463dbec..3285a98d5 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97b8d640c0f47b0eedba8bb90190043081ae26a5e371eba1c5e5c1271e411ddf -size 18125 +oid sha256:a321b884ea74537d1b4f2fd0769cd81ff91280d35ad193c0811b6fc995b9f38d +size 18502 diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index 460f42dc1..6184bb640 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d10f34a19ddbeea914082a23362317a2fc6ca31ca18d9307447023c9f2789414 -size 10610 +oid sha256:22313af733ee40c4789fd184d172b6ad8ea3d46ee3da9b33dd34403a642f76f6 +size 11710 diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index a81f6c939..9d86a9585 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -43,7 +43,8 @@ PACK_STRUCT(struct Data0 GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; GBufferData GBuffer; - Vector3 Padding0; + Vector2 Padding0; + float ResetBlend; float IndirectLightingIntensity; }); @@ -283,7 +284,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, // TODO: configurable via graphics settings const Quality quality = Quality::Ultra; - bool debugProbes = true; // TODO: add debug option to draw probes locations -> in Graphics window - Editor-only + bool debugProbes = false; // TODO: add debug option to draw probes locations -> in Graphics window - Editor-only // TODO: configurable via postFx settings (maybe use Global SDF distance?) const float indirectLightingIntensity = 1.0f; const Vector3 giDistance(2000, 2000, 2000); // GI distance around the view (in each direction) @@ -291,7 +292,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, const Int3 probesCounts(Vector3::Ceil(giDistance / giResolution)); const Vector3 probesDistance = Vector3(probesCounts) * giResolution; const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality - const float probeHistoryWeight = 0.97f; + const float probeHistoryWeight = 0.8f; // Init buffers const int32 probesCount = probesCounts.X * probesCounts.Y * probesCounts.Z; @@ -347,6 +348,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, viewOrigin += viewDirection * viewOriginOffset; const float viewOriginSnapping = giResolution; viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping; + //viewOrigin = Vector3::Zero; CalculateVolumeScrolling(ddgiData, viewOrigin); // Upload constants @@ -377,6 +379,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, data.DDGI = ddgiData.Result.Constants; data.GlobalSDF = bindingDataSDF.Constants; data.GlobalSurfaceAtlas = bindingDataSurfaceAtlas.Constants; + data.ResetBlend = clear ? 1.0f : 0.0f; data.IndirectLightingIntensity = indirectLightingIntensity; GBufferPass::SetInputs(renderContext.View, data.GBuffer); context->UpdateCB(_cb0, &data); diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 2b8675ff0..e278a121a 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -437,7 +437,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.TileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution surfaceAtlasData.DistanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down surfaceAtlasData.DistanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down - surfaceAtlasData.DistanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away + surfaceAtlasData.DistanceScaling = 0.2f; // The scale for tiles at distanceScalingEnd and further away // TODO: add DetailsScale param to adjust quality of scene details in Global Surface Atlas const uint32 viewMask = renderContext.View.RenderLayersMask; const Vector3 viewPosition = renderContext.View.Position; @@ -595,6 +595,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.Constants.ChunkSize = distance / (float)GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; result.Constants.ObjectsCount = surfaceAtlasData.Objects.Count(); + // If we don't know the culled objects buffer capacity then we shouldn't use atlas results as many objects are still missing (see CulledObjectsCounterIndex usage) + bool notReady = false; + // Cull objects into chunks (for faster Atlas sampling) if (surfaceAtlasData.Objects.Count() != 0) { @@ -620,18 +623,23 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co if (surfaceAtlasData.CulledObjectsCounterIndex != -1) { // Get the last counter value (accept staging readback delay) + notReady = true; auto data = (uint32*)_culledObjectsSizeBuffer->Map(GPUResourceMapMode::Read); if (data) { uint32 counter = data[surfaceAtlasData.CulledObjectsCounterIndex]; _culledObjectsSizeBuffer->Unmap(); if (counter > 0) + { objectsBufferCapacity = counter * sizeof(Vector4); + notReady = false; + } } } if (surfaceAtlasData.CulledObjectsCounterIndex == -1) { // Find a free timer slot + notReady = true; for (int32 i = 0; i < ARRAY_COUNT(_culledObjectsSizeFrames); i++) { if (currentFrame - _culledObjectsSizeFrames[i] > GPU_ASYNC_LATENCY) @@ -870,12 +878,10 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->ResetRenderTarget(); } - // TODO: indirect lighting apply to get infinite bounces for GI - // TODO: explore atlas tiles optimization with feedback from renderer (eg. when tile is sampled by GI/Reflections mark it as used, then sort tiles by importance and prioritize updates for ones frequently used) #undef WRITE_TILE - return false; + return notReady; } void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output) diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 1e4dd1200..d5daae4ba 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -27,7 +27,8 @@ DDGIData DDGI; GlobalSDFData GlobalSDF; GlobalSurfaceAtlasData GlobalSurfaceAtlas; GBufferData GBuffer; -float3 Padding0; +float2 Padding0; +float ResetBlend; float IndirectLightingIntensity; META_CB_END @@ -228,8 +229,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd { // Clear probe and return //RWOutput[outputCoords] = float4(0, 0, 0, 0); - if (!skip) - RWOutput[outputCoords] = float4(0, 0, 0, 0); + //if (!skip) RWOutput[outputCoords] = float4(0, 0, 0, 0); skip = true; } } @@ -260,7 +260,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd if (skip) { // Clear probe - //RWOutput[outputCoords] = float4(0, 0, 0, 0); + RWOutput[outputCoords] = float4(0, 0, 0, 0); return; } @@ -310,17 +310,15 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd // Blend current value with the previous probe data float3 previous = RWOutput[outputCoords].rgb; float historyWeight = DDGI.ProbeHistoryWeight; - if (dot(previous, previous) == 0) - { - // Cut any blend from zero + //historyWeight = 0.0f; + if (ResetBlend || dot(previous, previous) == 0) historyWeight = 0.0f; - } #if DDGI_PROBE_UPDATE_MODE == 0 result *= IndirectLightingIntensity; #if DDGI_SRGB_BLENDING result.rgb = pow(result.rgb, 1.0f / DDGI.IrradianceGamma); #endif - float3 irradianceDelta = result.rgb - previous.rgb; + float3 irradianceDelta = result.rgb - previous; float irradianceDeltaMax = Max3(abs(irradianceDelta)); if (irradianceDeltaMax > 0.25f) { @@ -330,12 +328,13 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd if (irradianceDeltaMax > 0.8f) { // Reduce flickering during rapid brightness changes - result.rgb = previous.rgb + (irradianceDelta * 0.25f); + result.rgb = previous + (irradianceDelta * 0.25f); } float3 resultDelta = (1.0f - historyWeight) * irradianceDelta; - if (Max3(result.rgb) < Max3(previous.rgb)) + if (Max3(result.rgb) < Max3(previous)) resultDelta = min(max(abs(resultDelta), 1.0f / 1024.0f), abs(irradianceDelta)) * sign(resultDelta); - result = float4(previous.rgb + resultDelta, 1.0f); + result = float4(previous + resultDelta, 1.0f); + //result = float4(lerp(result.rgb, previous.rgb, historyWeight), 1.0f); #else result = float4(lerp(result.rg, previous.rg, historyWeight), 0.0f, 1.0f); #endif diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index c917583d7..4f91680d3 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -117,6 +117,7 @@ float4 PS_Lighting(AtlasVertexOutput input) : SV_Target // Sample irradiance float bias = 1.0f; float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesState, ProbesDistance, ProbesIrradiance, gBuffer.WorldPos, gBuffer.Normal, bias); + //irradiance = 0; // Calculate lighting float3 diffuseColor = GetDiffuseColor(gBuffer); From d05c09a33d03894221cd93b69a8e6e418c31e711 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 2 Jun 2022 18:13:15 +0200 Subject: [PATCH 12/27] Improve GI quality --- Content/Shaders/GI/DDGI.flax | 4 ++-- Content/Shaders/GI/GlobalSurfaceAtlas.flax | 4 ++-- Source/Engine/Foliage/FoliageType.cpp | 7 ------- Source/Engine/Foliage/FoliageType.h | 2 +- Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp | 6 ++++-- Source/Shaders/GI/DDGI.shader | 7 ++++--- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 3 +++ 7 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 3285a98d5..3f0e3b153 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a321b884ea74537d1b4f2fd0769cd81ff91280d35ad193c0811b6fc995b9f38d -size 18502 +oid sha256:a5a7f8297f6ce999229521399607d277c63a350dadff2d48fd009b892f48d286 +size 18558 diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index 6184bb640..5852ce04c 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22313af733ee40c4789fd184d172b6ad8ea3d46ee3da9b33dd34403a642f76f6 -size 11710 +oid sha256:60f2a063a165e62fa94892eb9869db8e990c15866cb3339ed682b4cd0da9778b +size 11785 diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index 1aa14bacb..75bd33191 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -208,11 +208,4 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE(PlacementRandomRollAngle); DESERIALIZE_BIT(PlacementAlignToNormal); DESERIALIZE_BIT(PlacementRandomYaw); - - // [Deprecated on 07.02.2022, expires on 07.02.2024] - if (modifier->EngineBuild <= 6330) - DrawModes |= DrawPass::GlobalSDF; - // [Deprecated on 27.04.2022, expires on 27.04.2024] - if (modifier->EngineBuild <= 6331) - DrawModes |= DrawPass::GlobalSurfaceAtlas; } diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h index 2cef6729c..a70491bfe 100644 --- a/Source/Engine/Foliage/FoliageType.h +++ b/Source/Engine/Foliage/FoliageType.h @@ -132,7 +132,7 @@ public: /// /// The draw passes to use for rendering this foliage type. /// - API_FIELD() DrawPass DrawModes = DrawPass::Default; + API_FIELD() DrawPass DrawModes = DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward; /// /// The shadows casting mode. diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index e278a121a..b1c9095f0 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -28,6 +28,8 @@ #define GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE 6 // Amount of float4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles +#define GLOBAL_SURFACE_ATLAS_TILE_SIZE_MIN 8 // The minimum size of the tile +#define GLOBAL_SURFACE_ATLAS_TILE_SIZE_MAX 192 // The maximum size of the tile #define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) #define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES 0 // Forces to redraw all object tiles every frame #define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) @@ -1013,8 +1015,8 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, con } // Clamp tile resolution (in pixels) - static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < 8, "Invalid tile size configuration. Minimum tile size must be larger than padding."); - tileResolution = Math::Clamp(tileResolution, 8, 128); + static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < GLOBAL_SURFACE_ATLAS_TILE_SIZE_MIN, "Invalid tile size configuration. Minimum tile size must be larger than padding."); + tileResolution = Math::Clamp(tileResolution, GLOBAL_SURFACE_ATLAS_TILE_SIZE_MIN, GLOBAL_SURFACE_ATLAS_TILE_SIZE_MAX); // Snap tiles resolution (down) which allows to reuse atlas slots once object gets resizes/replaced by other object tileResolution = Math::AlignDown(tileResolution, 8); diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index d5daae4ba..cd1ed41d8 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -320,12 +320,13 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd #endif float3 irradianceDelta = result.rgb - previous; float irradianceDeltaMax = Max3(abs(irradianceDelta)); - if (irradianceDeltaMax > 0.25f) + float irradianceDeltaLen = length(irradianceDelta); + if (irradianceDeltaMax > 0.2f) { // Reduce history weight after significant lighting change - historyWeight = max(historyWeight - 0.2f, 0.0f); + historyWeight = max(historyWeight - 0.7f, 0.0f); } - if (irradianceDeltaMax > 0.8f) + if (irradianceDeltaLen > 2.0f) { // Reduce flickering during rapid brightness changes result.rgb = previous + (irradianceDelta * 0.25f); diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 4f91680d3..e557b4b89 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -113,6 +113,9 @@ float4 PS_Lighting(AtlasVertexOutput input) : SV_Target float4x4 tileLocalToWorld = Inverse(tile.WorldToLocal); gBuffer.WorldPos = mul(float4(gBufferTilePos, 1), tileLocalToWorld).xyz; + // Boost material diffuse color to improve GI + gBuffer.Color *= 1.1f; + #if INDIRECT_LIGHT // Sample irradiance float bias = 1.0f; From 54c322da7c4ebcc39df325d211b5a057760ec39d Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 3 Jun 2022 12:14:03 +0200 Subject: [PATCH 13/27] Fix missing specular regression --- Content/Shaders/GI/GlobalSurfaceAtlas.flax | 4 ++-- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 2 +- Source/Shaders/Lighting.hlsl | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index 5852ce04c..6c82a055f 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60f2a063a165e62fa94892eb9869db8e990c15866cb3339ed682b4cd0da9778b -size 11785 +oid sha256:f207d29b09af5c4e3564ee73f87b4d0eb0075d5982bbbd7c6275a723ab84ef1d +size 11787 diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index e557b4b89..4491efabd 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -1,7 +1,7 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. // Diffuse-only lighting -#define NO_SPECULAR +#define NO_SPECULAR 1 #include "./Flax/Common.hlsl" #include "./Flax/Math.hlsl" diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 4a1cabac6..9984d638a 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -5,6 +5,10 @@ #include "./Flax/LightingCommon.hlsl" +#ifndef NO_SPECULAR +#define NO_SPECULAR 0 +#endif + ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMask) { ShadowData shadow; @@ -24,7 +28,7 @@ LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, floa LightingData lighting; lighting.Diffuse = Diffuse_Lambert(diffuseColor); -#if defined(NO_SPECULAR) +#if NO_SPECULAR lighting.Specular = 0; #else float3 specularColor = GetSpecularColor(gBuffer); From befaf65ab1c0ce3c37ed8be0e9935515551844d5 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 3 Jun 2022 16:11:46 +0200 Subject: [PATCH 14/27] Optimize DDGI probes position calculation when sampling irradiance --- Source/Shaders/GI/DDGI.hlsl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index 69619faa9..2aaf8f74d 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -140,14 +140,15 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur uint probeIndex = GetDDGIScrollingProbeIndex(data, probeCoords); // Load probe position and state - float4 probePositionAndState = LoadDDGIProbePositionAndState(data, probesState, probeIndex, probeCoords); - if (probePositionAndState.w == DDGI_PROBE_STATE_INACTIVE) + float4 probeState = probesState.Load(int3(GetDDGIProbeTexelCoords(data, probeIndex), 0)); + if (probeState.w == DDGI_PROBE_STATE_INACTIVE) continue; + float3 probePosition = baseProbeWorldPosition + ((probeCoords - baseProbeCoords) * data.ProbesSpacing) + probeState.xyz; // Calculate the distance and direction from the (biased and non-biased) shading point and the probe - float3 worldPosToProbe = normalize(probePositionAndState.xyz - worldPosition); - float3 biasedPosToProbe = normalize(probePositionAndState.xyz - biasedWorldPosition); - float biasedPosToProbeDist = length(probePositionAndState.xyz - biasedWorldPosition); + float3 worldPosToProbe = normalize(probePosition.xyz - worldPosition); + float3 biasedPosToProbe = normalize(probePosition.xyz - biasedWorldPosition); + float biasedPosToProbeDist = length(probePosition.xyz - biasedWorldPosition); // Smooth backface test float weight = Square(dot(worldPosToProbe, worldNormal) * 0.5f + 0.5f); @@ -188,7 +189,6 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur #endif // Debug probe offset visualization - //float4 probeState = probesState.Load(int3(GetDDGIProbeTexelCoords(data, probeIndex), 0)); //probeIrradiance = float3(max(frac(probeState.xyz) * 2, 0.1f)); // Accumulate weighted irradiance From 79220556df00b3572013e8cb528041d802b106cf Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 3 Jun 2022 17:43:12 +0200 Subject: [PATCH 15/27] Improve DDGI probes relocation algorithm --- Content/Shaders/GI/DDGI.flax | 4 ++-- Source/Shaders/GI/DDGI.hlsl | 11 +++++---- Source/Shaders/GI/DDGI.shader | 45 +++++++++++++++++++++-------------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 3f0e3b153..29c746d3b 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5a7f8297f6ce999229521399607d277c63a350dadff2d48fd009b892f48d286 -size 18558 +oid sha256:cd93974da1fcb3fad04406e385cb2988f171ef2378017adb9971ac36e5d7b5de +size 18757 diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index 2aaf8f74d..3017a6c8a 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -143,16 +143,17 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur float4 probeState = probesState.Load(int3(GetDDGIProbeTexelCoords(data, probeIndex), 0)); if (probeState.w == DDGI_PROBE_STATE_INACTIVE) continue; - float3 probePosition = baseProbeWorldPosition + ((probeCoords - baseProbeCoords) * data.ProbesSpacing) + probeState.xyz; + float3 probeBasePosition = baseProbeWorldPosition + ((probeCoords - baseProbeCoords) * data.ProbesSpacing); + float3 probePosition = probeBasePosition + probeState.xyz; // Calculate the distance and direction from the (biased and non-biased) shading point and the probe - float3 worldPosToProbe = normalize(probePosition.xyz - worldPosition); - float3 biasedPosToProbe = normalize(probePosition.xyz - biasedWorldPosition); - float biasedPosToProbeDist = length(probePosition.xyz - biasedWorldPosition); + float3 worldPosToProbe = normalize(probePosition - worldPosition); + float3 biasedPosToProbe = normalize(probePosition - biasedWorldPosition); + float biasedPosToProbeDist = length(probePosition - biasedWorldPosition); // Smooth backface test float weight = Square(dot(worldPosToProbe, worldNormal) * 0.5f + 0.5f); - + // Sample distance texture float2 octahedralCoords = GetOctahedralCoords(-biasedPosToProbe); float2 uv = GetDDGIProbeUV(data, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_DISTANCE); diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index cd1ed41d8..6cfb7fa0d 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -71,44 +71,53 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) // Load probe state and position float4 probeState = RWProbesState[probeDataCoords]; - float3 probePosition = GetDDGIProbeWorldPosition(DDGI, probeCoords); - // TODO: reset probe offset for scrolled probes - probePosition.xyz += probeState.xyz; + float3 probeBasePosition = GetDDGIProbeWorldPosition(DDGI, probeCoords); + float3 probePosition = probeBasePosition + probeState.xyz; probeState.w = DDGI_PROBE_STATE_ACTIVE; // Use Global SDF to quickly get distance and direction to the scene geometry float sdf; float3 sdfNormal = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, probePosition.xyz, sdf)); float threshold = GlobalSDF.CascadeVoxelSize[0] * 0.5f; - float distanceLimit = length(DDGI.ProbesSpacing) * 1.5f + threshold; + float distanceLimit = length(DDGI.ProbesSpacing) * 2.0f; float relocateLimit = length(DDGI.ProbesSpacing) * 0.6f; - if (abs(sdf) > distanceLimit + threshold) // Probe is too far from geometry + if (abs(sdf) > distanceLimit) // Probe is too far from geometry { // Disable it probeState = float4(0, 0, 0, DDGI_PROBE_STATE_INACTIVE); } - else if (sdf < threshold) // Probe is inside geometry + else { - if (abs(sdf) < relocateLimit) + if (sdf < threshold) // Probe is inside geometry { - float3 offsetToAdd = sdfNormal * (sdf + threshold); - if (distance(probeState.xyz, offsetToAdd) < relocateLimit) + if (abs(sdf) < relocateLimit) { - // Relocate it - probeState.xyz = probeState.xyz + offsetToAdd; + float3 offsetToAdd = sdfNormal * sdf; + if (distance(probeState.xyz, offsetToAdd) < relocateLimit) + { + // Relocate it + probeState.xyz += offsetToAdd; + } + } + else + { + // Reset relocation + probeState.xyz = float3(0, 0, 0); } - // TODO: maybe sample SDF at the relocated location and disable probe if it's still in the geometry? } - else + else if (sdf > threshold * 2.0f) // Probe is far enough any geometry + { + // Reset relocation + probeState.xyz = float3(0, 0, 0); + } + + // Check if probe is relocated but the base location is fine + sdf = SampleGlobalSDF(GlobalSDF, GlobalSDFTex, probeBasePosition.xyz); + if (sdf > threshold) { // Reset relocation probeState.xyz = float3(0, 0, 0); } - } - else if (sdf > relocateLimit) // Probe is far enough any geometry - { - // Reset relocation - probeState.xyz = float3(0, 0, 0); } RWProbesState[probeDataCoords] = probeState; From 31d9802cf4a37bc888f2bf94e56112c0fb6c54e2 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 6 Jun 2022 10:28:39 +0200 Subject: [PATCH 16/27] Add `IndirectLightingIntensity` support for lights in Global Surface Atlas --- Source/Engine/Level/Actors/DirectionalLight.cpp | 1 + Source/Engine/Level/Actors/PointLight.cpp | 1 + Source/Engine/Level/Actors/SkyLight.cpp | 1 + Source/Engine/Level/Actors/SpotLight.cpp | 1 + Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp | 3 +++ Source/Engine/Renderer/RenderList.h | 4 ++++ 6 files changed, 11 insertions(+) diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index 38ac0407d..b34eaecbc 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -35,6 +35,7 @@ void DirectionalLight::Draw(RenderContext& renderContext) data.ShadowsDepthBias = ShadowsDepthBias; data.ShadowsSharpness = ShadowsSharpness; data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; + data.IndirectLightingIntensity = IndirectLightingIntensity; data.CastVolumetricShadow = CastVolumetricShadow; data.RenderedVolumetricFog = 0; data.ShadowsMode = ShadowsMode; diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 04024e480..6bceaba93 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -131,6 +131,7 @@ void PointLight::Draw(RenderContext& renderContext) data.SourceRadius = SourceRadius; data.SourceLength = SourceLength; data.ContactShadowsLength = ContactShadowsLength; + data.IndirectLightingIntensity = IndirectLightingIntensity; data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr; renderContext.List->PointLights.Add(data); } diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index 6bab5440b..bc2035a23 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -112,6 +112,7 @@ void SkyLight::Draw(RenderContext& renderContext) data.CastVolumetricShadow = CastVolumetricShadow; data.RenderedVolumetricFog = 0; data.AdditiveColor = AdditiveColor.ToVector3() * (AdditiveColor.A * brightness); + data.IndirectLightingIntensity = IndirectLightingIntensity; data.Radius = GetScaledRadius(); data.Image = GetSource(); renderContext.List->SkyLights.Add(data); diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index 234786e3c..229dfe1e4 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -182,6 +182,7 @@ void SpotLight::Draw(RenderContext& renderContext) data.CosOuterCone = _cosOuterCone; data.InvCosConeDifference = _invCosConeDifference; data.ContactShadowsLength = ContactShadowsLength; + data.IndirectLightingIntensity = IndirectLightingIntensity; data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr; Vector3::Transform(Vector3::Up, GetOrientation(), data.UpVector); data.OuterConeAngle = outerConeAngle; diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index b1c9095f0..c67709a6f 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -786,6 +786,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const bool useShadow = CanRenderShadow(renderContext.View, light); // TODO: test perf/quality when using Shadow Map for directional light (ShadowsPass::Instance()->LastDirLightShadowMap) instead of Global SDF trace light.SetupLightData(&data.Light, useShadow); + data.Light.Color *= light.IndirectLightingIntensity; data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting0); @@ -813,6 +814,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Draw draw light const bool useShadow = CanRenderShadow(renderContext.View, light); light.SetupLightData(&data.Light, useShadow); + data.Light.Color *= light.IndirectLightingIntensity; data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting1); @@ -840,6 +842,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Draw draw light const bool useShadow = CanRenderShadow(renderContext.View, light); light.SetupLightData(&data.Light, useShadow); + data.Light.Color *= light.IndirectLightingIntensity; data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting1); diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 589a1162c..43b6daab2 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -34,6 +34,7 @@ struct RendererDirectionalLightData float ShadowsSharpness; float VolumetricScatteringIntensity; + float IndirectLightingIntensity; int8 CastVolumetricShadow : 1; int8 RenderedVolumetricFog : 1; @@ -72,6 +73,7 @@ struct RendererSpotLightData float CosOuterCone; float InvCosConeDifference; float ContactShadowsLength; + float IndirectLightingIntensity; ShadowsCastingMode ShadowsMode; int8 CastVolumetricShadow : 1; @@ -106,6 +108,7 @@ struct RendererPointLightData float SourceLength; float ContactShadowsLength; + float IndirectLightingIntensity; ShadowsCastingMode ShadowsMode; int8 CastVolumetricShadow : 1; @@ -126,6 +129,7 @@ struct RendererSkyLightData float Radius; Vector3 AdditiveColor; + float IndirectLightingIntensity; int8 CastVolumetricShadow : 1; int8 RenderedVolumetricFog : 1; From 1f1ed2bf60a228ff296b30a20b4f03eb2220e44a Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 6 Jun 2022 10:28:58 +0200 Subject: [PATCH 17/27] Fix terrain draw modes to collect only current draw pass --- Source/Engine/Terrain/TerrainChunk.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index e2874ef98..562e4b6b3 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -121,7 +121,8 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const //drawCall.TerrainData.HeightmapUVScaleBias.W += halfTexelOffset; // Submit draw call - renderContext.List->AddDrawCall(_patch->_terrain->DrawModes, flags, drawCall, true); + auto drawModes = (DrawPass)(_patch->_terrain->DrawModes & renderContext.View.Pass); + renderContext.List->AddDrawCall(drawModes, flags, drawCall, true); } void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* material, int32 lodIndex) const @@ -175,7 +176,8 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi //drawCall.TerrainData.HeightmapUVScaleBias.W += halfTexelOffset; // Submit draw call - renderContext.List->AddDrawCall(_patch->_terrain->DrawModes, flags, drawCall, true); + auto drawModes = (DrawPass)(_patch->_terrain->DrawModes & renderContext.View.Pass); + renderContext.List->AddDrawCall(drawModes, flags, drawCall, true); } bool TerrainChunk::Intersects(const Ray& ray, float& distance) From c147e3bff447c67f7b4728fc7e7683b570ef4c86 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 6 Jun 2022 13:13:36 +0200 Subject: [PATCH 18/27] Minor DDGI optimization --- Content/Shaders/GI/DDGI.flax | 4 +-- .../GI/DynamicDiffuseGlobalIllumination.cpp | 2 +- Source/Shaders/GI/DDGI.shader | 33 +++++++++---------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 29c746d3b..d68e89a7b 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd93974da1fcb3fad04406e385cb2988f171ef2378017adb9971ac36e5d7b5de -size 18757 +oid sha256:589d77f14b69b0af963b848ee5a4342c0986304f64296a29d21f45d7e0e6ea88 +size 18536 diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 9d86a9585..8521cd1c0 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -287,12 +287,12 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, bool debugProbes = false; // TODO: add debug option to draw probes locations -> in Graphics window - Editor-only // TODO: configurable via postFx settings (maybe use Global SDF distance?) const float indirectLightingIntensity = 1.0f; + const float probeHistoryWeight = 0.8f; const Vector3 giDistance(2000, 2000, 2000); // GI distance around the view (in each direction) const float giResolution = 100.0f; // GI probes placement spacing const Int3 probesCounts(Vector3::Ceil(giDistance / giResolution)); const Vector3 probesDistance = Vector3(probesCounts) * giResolution; const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality - const float probeHistoryWeight = 0.8f; // Init buffers const int32 probesCount = probesCounts.X * probesCounts.Y * probesCounts.Z; diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 6cfb7fa0d..c6a680291 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -236,9 +236,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount; if (probeCoords[planeIndex] == coord) { - // Clear probe and return - //RWOutput[outputCoords] = float4(0, 0, 0, 0); - //if (!skip) RWOutput[outputCoords] = float4(0, 0, 0, 0); + // Skip scrolled probes skip = true; } } @@ -249,23 +247,20 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd if (probeState == DDGI_PROBE_STATE_INACTIVE) skip = true; - // Calculate octahedral projection for probe (unwraps spherical projection into a square) - float2 octahedralCoords = GetOctahedralCoords(DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); - float3 octahedralDirection = GetOctahedralDirection(octahedralCoords); - - // Load trace rays results into shared memory to reuse across whole thread group - uint count = (uint)(ceil((float)(DDGI_TRACE_RAYS_LIMIT) / (float)(DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTION))); - for (uint i = 0; i < count; i++) + if (!skip) { - uint rayIndex = (GroupIndex * count) + i; - if (rayIndex >= DDGI.RaysCount) - break; - CachedProbesTraceRadiance[rayIndex] = ProbesTrace[uint2(rayIndex, probeIndex)]; - CachedProbesTraceDirection[rayIndex] = GetProbeRayDirection(DDGI, rayIndex); + // Load trace rays results into shared memory to reuse across whole thread group + uint count = (uint)(ceil((float)(DDGI_TRACE_RAYS_LIMIT) / (float)(DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTION))); + for (uint i = 0; i < count; i++) + { + uint rayIndex = (GroupIndex * count) + i; + if (rayIndex >= DDGI.RaysCount) + break; + CachedProbesTraceRadiance[rayIndex] = ProbesTrace[uint2(rayIndex, probeIndex)]; + CachedProbesTraceDirection[rayIndex] = GetProbeRayDirection(DDGI, rayIndex); + } } GroupMemoryBarrierWithGroupSync(); - - // TODO: optimize probes updating to build indirect dispatch args and probes indices list before tracing rays and blending irradiance/distance if (skip) { // Clear probe @@ -273,6 +268,10 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd return; } + // Calculate octahedral projection for probe (unwraps spherical projection into a square) + float2 octahedralCoords = GetOctahedralCoords(DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); + float3 octahedralDirection = GetOctahedralDirection(octahedralCoords); + // Loop over rays float4 result = float4(0, 0, 0, 0); #if DDGI_PROBE_UPDATE_MODE == 0 From 5ee62be16631685872a79c11b1105eb19ff43417 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 6 Jun 2022 15:04:58 +0200 Subject: [PATCH 19/27] Various DDGI improvements to the quality of the final effect --- Content/Shaders/GI/DDGI.flax | 4 +- .../GI/DynamicDiffuseGlobalIllumination.cpp | 98 ++++++++----------- Source/Shaders/GI/DDGI.shader | 9 +- Source/Shaders/GI/GlobalSurfaceAtlas.hlsl | 59 +++++++++-- 4 files changed, 100 insertions(+), 70 deletions(-) diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index d68e89a7b..e240f1dd1 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:589d77f14b69b0af963b848ee5a4342c0986304f64296a29d21f45d7e0e6ea88 -size 18536 +oid sha256:6472e0a74ad1b42770b0ae80b05fd1c7ff44de32bf4294e8e4b9f97be40535c5 +size 18517 diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 8521cd1c0..d8adfacd0 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -116,44 +116,6 @@ void CalculateVolumeRandomRotation(Matrix3x3& matrix) matrix.M33 = 1.0f - 2.0f * u3; } -int32 AbsFloor(const float value) -{ - return value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value); -} - -int32 GetSignNotZero(const float value) -{ - return value >= 0.0f ? 1 : -1; -} - -Vector3 GetVolumeOrigin(DDGICustomBuffer& ddgiData) -{ - return ddgiData.ProbesOrigin + Vector3(ddgiData.ProbeScrollOffsets) * ddgiData.ProbesSpacing; -} - -void CalculateVolumeScrolling(DDGICustomBuffer& ddgiData, const Vector3& viewOrigin) -{ - // Reset the volume origin and scroll offsets for each axis - for (int32 axis = 0; axis < 3; axis++) - { - if (ddgiData.ProbeScrollOffsets.Raw[axis] != 0 && (ddgiData.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0)) - { - ddgiData.ProbesOrigin.Raw[axis] += (float)ddgiData.ProbeCounts.Raw[axis] * ddgiData.ProbesSpacing * (float)ddgiData.ProbeScrollDirections.Raw[axis]; - ddgiData.ProbeScrollOffsets.Raw[axis] = 0; - } - } - - // Calculate the count of grid cells between the view origin and the scroll anchor - const Vector3 translation = viewOrigin - GetVolumeOrigin(ddgiData); - for (int32 axis = 0; axis < 3; axis++) - { - const int32 scroll = AbsFloor(translation.Raw[axis] / ddgiData.ProbesSpacing); - ddgiData.ProbeScrollOffsets.Raw[axis] += scroll; - ddgiData.ProbeScrollClear[axis] = scroll != 0; - ddgiData.ProbeScrollDirections.Raw[axis] = GetSignNotZero(translation.Raw[axis]); - } -} - String DynamicDiffuseGlobalIlluminationPass::ToString() const { return TEXT("DynamicDiffuseGlobalIlluminationPass"); @@ -294,6 +256,17 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, const Vector3 probesDistance = Vector3(probesCounts) * giResolution; const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality + // Calculate view origin + Vector3 viewOrigin = renderContext.View.Position; + Vector3 viewDirection = renderContext.View.Direction; + const float probesDistanceMax = probesDistance.MaxValue(); + const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (probesDistanceMax * 2.0f), viewOrigin - probesDistance, viewOrigin + probesDistance); + const float viewOriginOffset = viewRayHit.Y * probesDistanceMax * 0.8f; + viewOrigin += viewDirection * viewOriginOffset; + const float viewOriginSnapping = giResolution; + viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping; + //viewOrigin = Vector3::Zero; + // Init buffers const int32 probesCount = probesCounts.X * probesCounts.Y * probesCounts.Z; if (probesCount == 0 || indirectLightingIntensity <= ZeroTolerance) @@ -308,6 +281,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, ddgiData.ProbeRaysCount = probeRaysCount; ddgiData.ProbesSpacing = giResolution; ddgiData.ProbeCounts = probesCounts; + ddgiData.ProbesOrigin = viewOrigin; // Allocate probes textures uint64 memUsage = 0; @@ -335,29 +309,36 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, context->ClearUA(ddgiData.ProbesDistance, Vector4::Zero); } - // Compute random rotation matrix for probe rays orientation (randomized every frame) - Matrix3x3 raysRotationMatrix; - CalculateVolumeRandomRotation(raysRotationMatrix); - // Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement) - Vector3 viewOrigin = renderContext.View.Position; - Vector3 viewDirection = renderContext.View.Direction; - const float probesDistanceMax = probesDistance.MaxValue(); - const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (probesDistanceMax * 2.0f), viewOrigin - probesDistance, viewOrigin + probesDistance); - const float viewOriginOffset = viewRayHit.Y * probesDistanceMax * 0.8f; - viewOrigin += viewDirection * viewOriginOffset; - const float viewOriginSnapping = giResolution; - viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping; - //viewOrigin = Vector3::Zero; - CalculateVolumeScrolling(ddgiData, viewOrigin); + { + + // Reset the volume origin and scroll offsets for each axis + for (int32 axis = 0; axis < 3; axis++) + { + if (ddgiData.ProbeScrollOffsets.Raw[axis] != 0 && (ddgiData.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0)) + { + ddgiData.ProbesOrigin.Raw[axis] += (float)ddgiData.ProbeCounts.Raw[axis] * ddgiData.ProbesSpacing * (float)ddgiData.ProbeScrollDirections.Raw[axis]; + ddgiData.ProbeScrollOffsets.Raw[axis] = 0; + } + } + + // Calculate the count of grid cells between the view origin and the scroll anchor + const Vector3 volumeOrigin = ddgiData.ProbesOrigin + Vector3(ddgiData.ProbeScrollOffsets) * ddgiData.ProbesSpacing; + const Vector3 translation = viewOrigin - volumeOrigin; + for (int32 axis = 0; axis < 3; axis++) + { + const float value = translation.Raw[axis] / ddgiData.ProbesSpacing; + const int32 scroll = value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value); + ddgiData.ProbeScrollOffsets.Raw[axis] += scroll; + ddgiData.ProbeScrollClear[axis] = scroll != 0; + ddgiData.ProbeScrollDirections.Raw[axis] = translation.Raw[axis] >= 0.0f ? 1 : -1; + } + } // Upload constants { ddgiData.Result.Constants.ProbesOrigin = ddgiData.ProbesOrigin; ddgiData.Result.Constants.ProbesSpacing = ddgiData.ProbesSpacing; - Quaternion& raysRotation = *(Quaternion*)&ddgiData.Result.Constants.RaysRotation; - Quaternion::RotationMatrix(raysRotationMatrix, raysRotation); - raysRotation.Conjugate(); ddgiData.Result.Constants.ProbesCounts[0] = probesCounts.X; ddgiData.Result.Constants.ProbesCounts[1] = probesCounts.Y; ddgiData.Result.Constants.ProbesCounts[2] = probesCounts.Z; @@ -375,6 +356,13 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->View(); ddgiData.Result.ProbesIrradiance = ddgiData.ProbesIrradiance->View(); + // Compute random rotation matrix for probe rays orientation (randomized every frame) + Matrix3x3 raysRotationMatrix; + CalculateVolumeRandomRotation(raysRotationMatrix); + Quaternion& raysRotation = *(Quaternion*)&ddgiData.Result.Constants.RaysRotation; + Quaternion::RotationMatrix(raysRotationMatrix, raysRotation); + raysRotation.Conjugate(); + Data0 data; data.DDGI = ddgiData.Result.Constants; data.GlobalSDF = bindingDataSDF.Constants; diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index c6a680291..e13ae04fe 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -119,7 +119,7 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) probeState.xyz = float3(0, 0, 0); } } - + RWProbesState[probeDataCoords] = probeState; } @@ -236,7 +236,8 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount; if (probeCoords[planeIndex] == coord) { - // Skip scrolled probes + // Clear and skip scrolled probes + RWOutput[outputCoords] = float4(0, 0, 0, 0); skip = true; } } @@ -262,11 +263,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd } GroupMemoryBarrierWithGroupSync(); if (skip) - { - // Clear probe - RWOutput[outputCoords] = float4(0, 0, 0, 0); return; - } // Calculate octahedral projection for probe (unwraps spherical projection into a square) float2 octahedralCoords = GetOctahedralCoords(DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl index 6d1d0adbb..92f727db2 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl @@ -7,6 +7,7 @@ #define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling #define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4 #define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED 1 // Enables using tile normal threshold to prevent sampling pixels behind the view point (but might cause back artifacts) #define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.05f // Cut-off value for tiles transitions blending during sampling #define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) @@ -108,12 +109,14 @@ float3 SampleGlobalSurfaceAtlasTex(Texture2D atlas, float2 atlasUV, float4 bilin float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold) { +#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED // Tile normal weight based on the sampling angle float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal)); normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD); if (normalWeight <= 0.0f) return 0; +#endif // Get tile UV and depth at the world position float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; @@ -141,7 +144,10 @@ float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSur if (tileZ[i] >= 1.0f) depthVisibility[i] = 0.0f; } - float sampleWeight = normalWeight * dot(depthVisibility, bilinearWeights); + float sampleWeight = dot(depthVisibility, bilinearWeights); +#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED + sampleWeight *= normalWeight; +#endif if (sampleWeight <= 0.0f) return 0; bilinearWeights = depthVisibility * bilinearWeights; @@ -209,26 +215,65 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu worldToLocal[2] *= invScale.z; // Sample tiles based on the directionality +#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED float3 localNormal = normalize(mul(worldNormal, worldToLocal)); float3 localNormalSq = localNormal * localNormal; - uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; - if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + uint tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 0 : 1]; + if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3]; + if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5]; + if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } +#else + uint tileOffset = object.TileOffsets[0]; + if (tileOffset != 0) { GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } - tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3]; - if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + tileOffset = object.TileOffsets[1]; + if (tileOffset != 0) { GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } - tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5]; - if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + tileOffset = object.TileOffsets[2]; + if (tileOffset != 0) { GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } + tileOffset = object.TileOffsets[3]; + if (tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[4]; + if (tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[5]; + if (tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } +#endif } // Normalize result From 73d762cf0cadd60552fde2ab851aeedd1a689bb8 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 6 Jun 2022 15:47:56 +0200 Subject: [PATCH 20/27] Fix regression from 5ee62be16631685872a79c11b1105eb19ff43417 --- Source/Shaders/GI/GlobalSurfaceAtlas.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl index 92f727db2..5df1b5475 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl @@ -218,7 +218,7 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu #if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED float3 localNormal = normalize(mul(worldNormal, worldToLocal)); float3 localNormalSq = localNormal * localNormal; - uint tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 0 : 1]; + uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) { GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); From 6a74ebd62eff77f1793f106ee30aae4d39b1a601 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 9 Jun 2022 08:55:45 +0200 Subject: [PATCH 21/27] Add support for cascades to DDGI --- .../DebugMaterials/DDGIDebugProbes.flax | 4 +- Content/Shaders/GI/DDGI.flax | 4 +- Content/Shaders/SSR.flax | 4 +- Source/Engine/Graphics/PostProcessSettings.h | 4 +- .../GI/DynamicDiffuseGlobalIllumination.cpp | 349 ++++++++++++------ .../GI/DynamicDiffuseGlobalIllumination.h | 18 +- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 4 +- .../Renderer/GlobalSignDistanceFieldPass.cpp | 43 +-- Source/Shaders/GI/DDGI.hlsl | 109 +++--- Source/Shaders/GI/DDGI.shader | 59 +-- Source/Shaders/GlobalSignDistanceField.hlsl | 2 +- Source/Shaders/Random.hlsl | 12 + Source/Shaders/SSR.hlsl | 15 +- Source/Shaders/SSR.shader | 2 +- 14 files changed, 395 insertions(+), 234 deletions(-) diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax index 49c91d5b4..67269d782 100644 --- a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78c41dd01f6f2740fbdd08c2e0f05875398f38ba8e09bd91495636281a6ec6cc -size 37799 +oid sha256:dc3bbdd9c663f9ba6b21c9f49a645c59ba4ecd340b9d046fe60aff26bab26b3a +size 39880 diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index e240f1dd1..8d8ba1d43 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6472e0a74ad1b42770b0ae80b05fd1c7ff44de32bf4294e8e4b9f97be40535c5 -size 18517 +oid sha256:6ad0a077984ef2749700d6eb08bf1f6a23ea1fd84e4296128ecbe3d57994b007 +size 19306 diff --git a/Content/Shaders/SSR.flax b/Content/Shaders/SSR.flax index 7e249afb5..e2c77a59c 100644 --- a/Content/Shaders/SSR.flax +++ b/Content/Shaders/SSR.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd2d0c05638d8e4c6c307d70d57a29f2475c9c0d8dfb43f8b8ff09189e4a62bb -size 9341 +oid sha256:15dce5625a1a074796d700946e02d8cb16f3841eb55b0031ad60c77cac5ad783 +size 9342 diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index f896b14d0..f1c9a353f 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -202,13 +202,13 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings); /// /// Ambient occlusion intensity. /// - API_FIELD(Attributes="DefaultValue(0.8f), Limit(0, 5.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Intensity)") + API_FIELD(Attributes="DefaultValue(0.8f), Limit(0, 10.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Intensity)") float Intensity = 0.8f; /// /// Ambient occlusion power. /// - API_FIELD(Attributes="DefaultValue(0.75f), Limit(0, 4.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Power)") + API_FIELD(Attributes="DefaultValue(0.75f), Limit(0, 10.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Power)") float Power = 0.75f; /// diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index d8adfacd0..679df6254 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -12,6 +12,7 @@ #include "Engine/Engine/Engine.h" #include "Engine/Content/Content.h" #include "Engine/Debug/DebugDraw.h" +#include "Engine/Engine/Time.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" @@ -43,35 +44,51 @@ PACK_STRUCT(struct Data0 GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; GBufferData GBuffer; - Vector2 Padding0; float ResetBlend; + float TemporalTime; float IndirectLightingIntensity; + float Padding0; + }); + +PACK_STRUCT(struct Data1 + { + Vector3 Padding1; + uint32 CascadeIndex; // TODO: use push constants on Vulkan or root signature data on DX12 to reduce overhead of changing single DWORD }); class DDGICustomBuffer : public RenderBuffers::CustomBuffer { public: + struct + { + Vector3 ProbesOrigin; + float ProbesSpacing = 0.0f; + Int3 ProbeScrollOffsets; + Int3 ProbeScrollDirections; + bool ProbeScrollClear[3]; + + void Clear() + { + ProbesOrigin = Vector3::Zero; + ProbeScrollOffsets = Int3::Zero; + ProbeScrollDirections = Int3::Zero; + ProbeScrollClear[0] = false; + ProbeScrollClear[1] = false; + ProbeScrollClear[2] = false; + } + } Cascades[4]; + + int32 CascadesCount = 0; int32 ProbeRaysCount = 0; - float ProbesSpacing = 0.0f; Int3 ProbeCounts = Int3::Zero; - Vector3 ProbesOrigin; - Int3 ProbeScrollOffsets; - Int3 ProbeScrollDirections; - bool ProbeScrollClear[3]; GPUTexture* ProbesTrace = nullptr; // Probes ray tracing: (RGB: hit radiance, A: hit distance) GPUTexture* ProbesState = nullptr; // Probes state: (RGB: world-space offset, A: state) GPUTexture* ProbesIrradiance = nullptr; // Probes irradiance (RGB: sRGB color) GPUTexture* ProbesDistance = nullptr; // Probes distance (R: mean distance, G: mean distance^2) DynamicDiffuseGlobalIlluminationPass::BindingData Result; - FORCE_INLINE void Clear() + FORCE_INLINE void Release() { - ProbesOrigin = Vector3::Zero; - ProbeScrollOffsets = Int3::Zero; - ProbeScrollDirections = Int3::Zero; - ProbeScrollClear[0] = false; - ProbeScrollClear[1] = false; - ProbeScrollClear[2] = false; RenderTargetPool::Release(ProbesTrace); RenderTargetPool::Release(ProbesState); RenderTargetPool::Release(ProbesIrradiance); @@ -80,7 +97,7 @@ public: ~DDGICustomBuffer() { - Clear(); + Release(); } }; @@ -150,7 +167,8 @@ bool DynamicDiffuseGlobalIlluminationPass::setupResources() // Initialize resources const auto shader = _shader->GetShader(); _cb0 = shader->GetCB(0); - if (!_cb0) + _cb1 = shader->GetCB(1); + if (!_cb0 || !_cb1) return true; _csClassify = shader->GetCS("CS_Classify"); _csTraceRays = shader->GetCS("CS_TraceRays"); @@ -199,6 +217,7 @@ void DynamicDiffuseGlobalIlluminationPass::Dispose() // Cleanup _cb0 = nullptr; + _cb1 = nullptr; _csTraceRays = nullptr; _shader = nullptr; SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); @@ -250,51 +269,77 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, // TODO: configurable via postFx settings (maybe use Global SDF distance?) const float indirectLightingIntensity = 1.0f; const float probeHistoryWeight = 0.8f; - const Vector3 giDistance(2000, 2000, 2000); // GI distance around the view (in each direction) - const float giResolution = 100.0f; // GI probes placement spacing - const Int3 probesCounts(Vector3::Ceil(giDistance / giResolution)); - const Vector3 probesDistance = Vector3(probesCounts) * giResolution; + const int32 cascadesCount = 4; // in range 1-4 + // TODO: use GI.Distance as a easier to adjust total distance and automatically calculate distanceExtent from it + const float distance = 20000.0f; // GI distance around the view (in each direction) + const float cascadesDistanceScales[] = { 1.0f, 3.0f, 6.0f, 10.0f }; // Scales each cascade further away from the camera origin + const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; + const float verticalRangeScale = 0.8f; // Scales the probes volume size at Y axis (horizontal aspect ratio makes the DDGI use less probes vertically to cover whole screen) + const float probesSpacing = 200.0f; // GI probes placement spacing nearby camera (for closest cascade; gets automatically reduced for further cascades) + const Int3 probesCounts(Vector3::Ceil(Vector3(distanceExtent, distanceExtent * verticalRangeScale, distanceExtent) / probesSpacing)); const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality - // Calculate view origin - Vector3 viewOrigin = renderContext.View.Position; - Vector3 viewDirection = renderContext.View.Direction; - const float probesDistanceMax = probesDistance.MaxValue(); - const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (probesDistanceMax * 2.0f), viewOrigin - probesDistance, viewOrigin + probesDistance); - const float viewOriginOffset = viewRayHit.Y * probesDistanceMax * 0.8f; - viewOrigin += viewDirection * viewOriginOffset; - const float viewOriginSnapping = giResolution; - viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping; - //viewOrigin = Vector3::Zero; + // Initialize cascades + float probesSpacings[4]; + Vector3 viewOrigins[4]; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + // Each cascade has higher spacing between probes + float cascadeDistanceScale = cascadesDistanceScales[cascadeIndex]; + float cascadeProbesSpacing = probesSpacing * cascadeDistanceScale; + probesSpacings[cascadeIndex] = cascadeProbesSpacing; + + // Calculate view origin for cascade by shifting it towards the view direction to account for better view frustum coverage + Vector3 viewOrigin = renderContext.View.Position; + Vector3 viewDirection = renderContext.View.Direction; + const Vector3 probesDistance = Vector3(probesCounts) * cascadeProbesSpacing; + const float probesDistanceMax = probesDistance.MaxValue(); + const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (probesDistanceMax * 2.0f), viewOrigin - probesDistance, viewOrigin + probesDistance); + const float viewOriginOffset = viewRayHit.Y * probesDistanceMax * 0.6f; + viewOrigin += viewDirection * viewOriginOffset; + const float viewOriginSnapping = cascadeProbesSpacing; + viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping; + //viewOrigin = Vector3::Zero; + viewOrigins[cascadeIndex] = viewOrigin; + } // Init buffers - const int32 probesCount = probesCounts.X * probesCounts.Y * probesCounts.Z; - if (probesCount == 0 || indirectLightingIntensity <= ZeroTolerance) + const int32 probesCountCascade = probesCounts.X * probesCounts.Y * probesCounts.Z; + const int32 probesCountTotal = probesCountCascade * cascadesCount; + if (probesCountTotal == 0 || indirectLightingIntensity <= ZeroTolerance) return true; - int32 probesCountX = probesCounts.X * probesCounts.Y; - int32 probesCountY = probesCounts.Z; + int32 probesCountCascadeX = probesCounts.X * probesCounts.Y; + int32 probesCountCascadeY = probesCounts.Z; + int32 probesCountTotalX = probesCountCascadeX; + int32 probesCountTotalY = probesCountCascadeY * cascadesCount; bool clear = false; - if (Math::NotNearEqual(ddgiData.ProbesSpacing, giResolution) || ddgiData.ProbeCounts != probesCounts || ddgiData.ProbeRaysCount != probeRaysCount) + if (ddgiData.CascadesCount != cascadesCount || Math::NotNearEqual(ddgiData.Cascades[0].ProbesSpacing, probesSpacing) || ddgiData.ProbeCounts != probesCounts || ddgiData.ProbeRaysCount != probeRaysCount) { PROFILE_CPU_NAMED("Init"); - ddgiData.Clear(); + ddgiData.Release(); + ddgiData.CascadesCount = cascadesCount; ddgiData.ProbeRaysCount = probeRaysCount; - ddgiData.ProbesSpacing = giResolution; ddgiData.ProbeCounts = probesCounts; - ddgiData.ProbesOrigin = viewOrigin; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + auto& cascade = ddgiData.Cascades[cascadeIndex]; + cascade.Clear(); + cascade.ProbesSpacing = probesSpacings[cascadeIndex]; + cascade.ProbesOrigin = viewOrigins[cascadeIndex]; + } // Allocate probes textures uint64 memUsage = 0; - auto desc = GPUTextureDescription::New2D(probesCountX, probesCountY, PixelFormat::Unknown); + auto desc = GPUTextureDescription::New2D(probesCountTotalX, probesCountTotalY, PixelFormat::Unknown); // TODO rethink probes data placement in memory -> what if we get [50x50x30] resolution? That's 75000 probes! Use sparse storage with active-only probes #define INIT_TEXTURE(texture, format, width, height) desc.Format = format; desc.Width = width; desc.Height = height; ddgiData.texture = RenderTargetPool::Get(desc); if (!ddgiData.texture) return true; memUsage += ddgiData.texture->GetMemoryUsage() desc.Flags = GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess; - INIT_TEXTURE(ProbesTrace, PixelFormat::R16G16B16A16_Float, probeRaysCount, probesCount); - INIT_TEXTURE(ProbesState, PixelFormat::R16G16B16A16_Float, probesCountX, probesCountY); // TODO: optimize to a RGBA32 (pos offset can be normalized to [0-0.5] range of ProbesSpacing and packed with state flag) - INIT_TEXTURE(ProbesIrradiance, PixelFormat::R11G11B10_Float, probesCountX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), probesCountY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2)); - INIT_TEXTURE(ProbesDistance, PixelFormat::R16G16_Float, probesCountX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), probesCountY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2)); + INIT_TEXTURE(ProbesTrace, PixelFormat::R16G16B16A16_Float, probeRaysCount, probesCountTotal); // TODO: limit to 4k probes for a single batch to trace + INIT_TEXTURE(ProbesState, PixelFormat::R16G16B16A16_Float, probesCountTotalX, probesCountTotalY); // TODO: optimize to a RGBA32 (pos offset can be normalized to [0-0.5] range of ProbesSpacing and packed with state flag) + INIT_TEXTURE(ProbesIrradiance, PixelFormat::R11G11B10_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2)); + INIT_TEXTURE(ProbesDistance, PixelFormat::R16G16_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2)); #undef INIT_TEXTURE - LOG(Info, "Dynamic Diffuse Global Illumination memory usage: {0} MB, probes: {1}", memUsage / 1024 / 1024, probesCount); + LOG(Info, "Dynamic Diffuse Global Illumination memory usage: {0} MB, probes: {1}", memUsage / 1024 / 1024, probesCountTotal); clear = true; } #if USE_EDITOR @@ -309,46 +354,62 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, context->ClearUA(ddgiData.ProbesDistance, Vector4::Zero); } - // Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement) + // Calculate which cascades should be updated this frame + //const uint64 cascadeFrequencies[] = { 1, 2, 3, 5 }; + // TODO: prevent updating 2 cascades at once on Low quality + const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; + bool cascadeSkipUpdate[4]; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { - + cascadeSkipUpdate[cascadeIndex] = !clear && (currentFrame % cascadeFrequencies[cascadeIndex]) != 0; + } + + // Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement) + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + if (cascadeSkipUpdate[cascadeIndex]) + continue; + auto& cascade = ddgiData.Cascades[cascadeIndex]; + // Reset the volume origin and scroll offsets for each axis for (int32 axis = 0; axis < 3; axis++) { - if (ddgiData.ProbeScrollOffsets.Raw[axis] != 0 && (ddgiData.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0)) + if (cascade.ProbeScrollOffsets.Raw[axis] != 0 && (cascade.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0)) { - ddgiData.ProbesOrigin.Raw[axis] += (float)ddgiData.ProbeCounts.Raw[axis] * ddgiData.ProbesSpacing * (float)ddgiData.ProbeScrollDirections.Raw[axis]; - ddgiData.ProbeScrollOffsets.Raw[axis] = 0; + cascade.ProbesOrigin.Raw[axis] += (float)ddgiData.ProbeCounts.Raw[axis] * cascade.ProbesSpacing * (float)cascade.ProbeScrollDirections.Raw[axis]; + cascade.ProbeScrollOffsets.Raw[axis] = 0; } } // Calculate the count of grid cells between the view origin and the scroll anchor - const Vector3 volumeOrigin = ddgiData.ProbesOrigin + Vector3(ddgiData.ProbeScrollOffsets) * ddgiData.ProbesSpacing; - const Vector3 translation = viewOrigin - volumeOrigin; + const Vector3 volumeOrigin = cascade.ProbesOrigin + Vector3(cascade.ProbeScrollOffsets) * cascade.ProbesSpacing; + const Vector3 translation = viewOrigins[cascadeIndex] - volumeOrigin; for (int32 axis = 0; axis < 3; axis++) { - const float value = translation.Raw[axis] / ddgiData.ProbesSpacing; + const float value = translation.Raw[axis] / cascade.ProbesSpacing; const int32 scroll = value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value); - ddgiData.ProbeScrollOffsets.Raw[axis] += scroll; - ddgiData.ProbeScrollClear[axis] = scroll != 0; - ddgiData.ProbeScrollDirections.Raw[axis] = translation.Raw[axis] >= 0.0f ? 1 : -1; + cascade.ProbeScrollOffsets.Raw[axis] += scroll; + cascade.ProbeScrollClear[axis] = scroll != 0; + cascade.ProbeScrollDirections.Raw[axis] = translation.Raw[axis] >= 0.0f ? 1 : -1; } } // Upload constants { - ddgiData.Result.Constants.ProbesOrigin = ddgiData.ProbesOrigin; - ddgiData.Result.Constants.ProbesSpacing = ddgiData.ProbesSpacing; + ddgiData.Result.Constants.CascadesCount = cascadesCount; ddgiData.Result.Constants.ProbesCounts[0] = probesCounts.X; ddgiData.Result.Constants.ProbesCounts[1] = probesCounts.Y; ddgiData.Result.Constants.ProbesCounts[2] = probesCounts.Z; - ddgiData.Result.Constants.ProbesScrollOffsets = ddgiData.ProbeScrollOffsets; - ddgiData.Result.Constants.ProbeScrollDirections = ddgiData.ProbeScrollDirections; - ddgiData.Result.Constants.ProbeScrollClear[0] = ddgiData.ProbeScrollClear[0] != 0; - ddgiData.Result.Constants.ProbeScrollClear[1] = ddgiData.ProbeScrollClear[1] != 0; - ddgiData.Result.Constants.ProbeScrollClear[2] = ddgiData.ProbeScrollClear[2] != 0; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + auto& cascade = ddgiData.Cascades[cascadeIndex]; + int32 probeScrollClear = cascade.ProbeScrollClear[0] + cascade.ProbeScrollClear[1] * 2 + cascade.ProbeScrollClear[2] * 4; // Pack clear flags into bits + ddgiData.Result.Constants.ProbesOriginAndSpacing[cascadeIndex] = Vector4(cascade.ProbesOrigin, cascade.ProbesSpacing); + ddgiData.Result.Constants.ProbesScrollOffsets[cascadeIndex] = Int4(cascade.ProbeScrollOffsets, probeScrollClear); + ddgiData.Result.Constants.ProbeScrollDirections[cascadeIndex] = Int4(cascade.ProbeScrollDirections, 0); + } ddgiData.Result.Constants.RayMaxDistance = 10000.0f; // TODO: adjust to match perf/quality ratio (make it based on Global SDF and Global Surface Atlas distance) - ddgiData.Result.Constants.ViewDir = viewDirection; + ddgiData.Result.Constants.ViewDir = renderContext.View.Direction; ddgiData.Result.Constants.RaysCount = probeRaysCount; ddgiData.Result.Constants.ProbeHistoryWeight = probeHistoryWeight; ddgiData.Result.Constants.IrradianceGamma = 5.0f; @@ -368,6 +429,18 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, data.GlobalSDF = bindingDataSDF.Constants; data.GlobalSurfaceAtlas = bindingDataSurfaceAtlas.Constants; data.ResetBlend = clear ? 1.0f : 0.0f; + if (renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing) + { + // Use temporal offset in the dithering factor (gets cleaned out by TAA) + const float time = Time::Draw.UnscaledTime.GetTotalSeconds(); + const float scale = 10; + const float integral = roundf(time / scale) * scale; + data.TemporalTime = time - integral; + } + else + { + data.TemporalTime = 0.0f; + } data.IndirectLightingIntensity = indirectLightingIntensity; GBufferPass::SetInputs(renderContext.View, data.GBuffer); context->UpdateCB(_cb0, &data); @@ -377,72 +450,117 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, // Classify probes (activation/deactivation and relocation) { PROFILE_GPU_CPU("Probes Classification"); - uint32 threadGroups = Math::DivideAndRoundUp(probesCount, DDGI_PROBE_CLASSIFY_GROUP_SIZE); + uint32 threadGroups = Math::DivideAndRoundUp(probesCountCascade, DDGI_PROBE_CLASSIFY_GROUP_SIZE); for (int32 i = 0; i < 4; i++) { context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); } context->BindUA(0, ddgiData.Result.ProbesState); - context->Dispatch(_csClassify, threadGroups, 1, 1); - context->ResetUA(); - } - - // Trace rays from probes - { - PROFILE_GPU_CPU("Trace Rays"); - - // Global SDF with Global Surface Atlas software raytracing (X - per probe ray, Y - per probe) - ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0); - for (int32 i = 0; i < 4; i++) + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { - context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); + if (cascadeSkipUpdate[cascadeIndex]) + continue; + Data1 data; + data.CascadeIndex = cascadeIndex; + context->UpdateCB(_cb1, &data); + context->BindCB(1, _cb1); + context->Dispatch(_csClassify, threadGroups, 1, 1); } - context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr); - context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr); - context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); - context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View()); - context->BindSR(12, ddgiData.Result.ProbesState); - context->BindSR(13, skybox); - context->BindUA(0, ddgiData.ProbesTrace->View()); - context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesCount, 1); context->ResetUA(); - context->ResetSR(); - -#if 0 - // Probes trace debug preview - context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); - context->SetRenderTarget(lightBuffer); - context->Draw(ddgiData.ProbesTrace); - return false; -#endif } // Update probes { - PROFILE_GPU_CPU("Update Probes"); - context->BindSR(0, ddgiData.Result.ProbesState); - context->BindSR(1, ddgiData.ProbesTrace->View()); + PROFILE_GPU_CPU("Probes Update"); + bool anyDirty = false; + uint32 threadGroupsX, threadGroupsY; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + if (cascadeSkipUpdate[cascadeIndex]) + continue; + anyDirty = true; + Data1 data; + data.CascadeIndex = cascadeIndex; + context->UpdateCB(_cb1, &data); + context->BindCB(1, _cb1); - // Update irradiance - context->BindUA(0, ddgiData.Result.ProbesIrradiance); - context->Dispatch(_csUpdateProbesIrradiance, probesCountX, probesCountY, 1); - uint32 threadGroupsX = Math::DivideAndRoundUp(probesCountX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); - uint32 threadGroupsY = Math::DivideAndRoundUp(probesCountY, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); - context->Dispatch(_csUpdateBordersIrradianceRow, threadGroupsX, threadGroupsY, 1); - threadGroupsX = Math::DivideAndRoundUp(probesCountX, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); - threadGroupsY = Math::DivideAndRoundUp(probesCountY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); - context->Dispatch(_csUpdateBordersIrradianceCollumn, threadGroupsX, threadGroupsY, 1); + // TODO: run probes tracing+update in 4k batches - // Update distance - context->BindUA(0, ddgiData.Result.ProbesDistance); - context->Dispatch(_csUpdateProbesDistance, probesCountX, probesCountY, 1); - threadGroupsX = Math::DivideAndRoundUp(probesCountX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); - threadGroupsY = Math::DivideAndRoundUp(probesCountY, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); - context->Dispatch(_csUpdateBordersDistanceRow, threadGroupsX, threadGroupsY, 1); - threadGroupsX = Math::DivideAndRoundUp(probesCountX, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); - threadGroupsY = Math::DivideAndRoundUp(probesCountY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); - context->Dispatch(_csUpdateBordersDistanceCollumn, threadGroupsX, threadGroupsY, 1); + // Trace rays from probes + { + PROFILE_GPU_CPU("Trace Rays"); + + // Global SDF with Global Surface Atlas software raytracing (thread X - per probe ray, thread Y - per probe) + ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0); + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); + } + context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr); + context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr); + context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); + context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View()); + context->BindSR(12, ddgiData.Result.ProbesState); + context->BindSR(13, skybox); + context->BindUA(0, ddgiData.ProbesTrace->View()); + context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesCountCascade, 1); + context->ResetUA(); + context->ResetSR(); + +#if 0 + // Probes trace debug preview + context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); + context->SetRenderTarget(lightBuffer); + context->Draw(ddgiData.ProbesTrace); + return false; +#endif + } + + context->BindSR(0, ddgiData.Result.ProbesState); + context->BindSR(1, ddgiData.ProbesTrace->View()); + + // Update probes irradiance texture + { + PROFILE_GPU_CPU("Update Irradiance"); + context->BindUA(0, ddgiData.Result.ProbesIrradiance); + context->Dispatch(_csUpdateProbesIrradiance, probesCountCascadeX, probesCountCascadeY, 1); + } + + // Update probes distance texture + { + PROFILE_GPU_CPU("Update Distance"); + context->BindUA(0, ddgiData.Result.ProbesDistance); + context->Dispatch(_csUpdateProbesDistance, probesCountCascadeX, probesCountCascadeY, 1); + } + } + + // Update probes border pixels + if (anyDirty) + { + PROFILE_GPU_CPU("Update Borders"); + + // Irradiance + context->BindUA(0, ddgiData.Result.ProbesIrradiance); + threadGroupsX = Math::DivideAndRoundUp(probesCountTotalX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountTotalY, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersIrradianceRow, threadGroupsX, threadGroupsY, 1); + threadGroupsX = Math::DivideAndRoundUp(probesCountTotalX, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountTotalY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersIrradianceCollumn, threadGroupsX, threadGroupsY, 1); + + // Distance + context->BindUA(0, ddgiData.Result.ProbesDistance); + threadGroupsX = Math::DivideAndRoundUp(probesCountTotalX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountTotalY, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersDistanceRow, threadGroupsX, threadGroupsY, 1); + threadGroupsX = Math::DivideAndRoundUp(probesCountTotalX, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountTotalY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersDistanceCollumn, threadGroupsX, threadGroupsY, 1); + + context->ResetUA(); + context->ResetSR(); + } } // Render indirect lighting @@ -453,7 +571,6 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, // DDGI indirect lighting debug preview context->Clear(lightBuffer, Color::Transparent); #endif - context->ResetUA(); context->BindSR(0, renderContext.Buffers->GBuffer0->View()); context->BindSR(1, renderContext.Buffers->GBuffer1->View()); context->BindSR(2, renderContext.Buffers->GBuffer2->View()); @@ -485,7 +602,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, Matrix world; Matrix::Scaling(Vector3(0.2f), world); const Mesh& debugMesh = _debugModel->LODs[0].Meshes[0]; - for (int32 probeIndex = 0; probeIndex < probesCount; probeIndex++) + for (int32 probeIndex = 0; probeIndex < probesCountTotal; probeIndex++) debugMesh.Draw(debugRenderContext, _debugMaterial, world, StaticFlags::None, true, DrawPass::GBuffer, (float)probeIndex); debugRenderContext.List->SortDrawCalls(debugRenderContext, false, DrawCallsListType::GBuffer); context->SetViewportAndScissors(debugRenderContext.View.ScreenSize.X, debugRenderContext.View.ScreenSize.Y); diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h index 5440cf7be..64bc0b3ca 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h @@ -3,7 +3,7 @@ #pragma once #include "../RendererPass.h" -#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Math/Int4.h" #include "Engine/Graphics/Textures/GPUTexture.h" /// @@ -15,19 +15,18 @@ public: // Constant buffer data for DDGI access on a GPU. PACK_STRUCT(struct ConstantsData { - Vector3 ProbesOrigin; - float ProbesSpacing; - Vector4 RaysRotation; + Vector4 ProbesOriginAndSpacing[4]; + Int4 ProbesScrollOffsets[4]; + Int4 ProbeScrollDirections[4]; uint32 ProbesCounts[3]; + uint32 CascadesCount; float IrradianceGamma; - Int3 ProbesScrollOffsets; float ProbeHistoryWeight; + float RayMaxDistance; + float Padding0; + Vector4 RaysRotation; Vector3 ViewDir; uint32 RaysCount; - Int3 ProbeScrollDirections; - float RayMaxDistance; - uint32 ProbeScrollClear[3]; - uint32 Padding0; }); // Binding data for the GPU. @@ -43,6 +42,7 @@ private: bool _supported = false; AssetReference _shader; GPUConstantBuffer* _cb0 = nullptr; + GPUConstantBuffer* _cb1 = nullptr; GPUShaderProgramCS* _csClassify; GPUShaderProgramCS* _csTraceRays; GPUShaderProgramCS* _csUpdateProbesIrradiance; diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index c67709a6f..eb89f0372 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -346,8 +346,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // TODO: configurable via graphics settings const int32 resolution = 2048; const float resolutionInv = 1.0f / resolution; - // TODO: configurable via postFx settings (maybe use Global SDF distance?) - const float distance = 20000; + // TODO: configurable via postFx settings (use GI distance) + const float distance = 20000.0f; // Initialize buffers bool noCache = surfaceAtlasData.Resolution != resolution; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index c94b41b6f..d9682867b 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -383,8 +383,10 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const int32 resolution = 256; const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); // TODO: configurable via postFx settings - const float distanceExtent = 2000.0f; - const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; + const int32 cascadesCount = 4; // in range 1-4 + const float distance = true ? 20000.0f : 16000.0f; // TODO: switch based if using GI, then use GI range + const float cascadesDistanceScales[] = { 1.0f, 2.0f, 4.0f, 8.0f }; + const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; // Initialize buffers auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); @@ -449,23 +451,22 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex bool anyDraw = false; const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; - for (int32 cascade = 0; cascade < 4; cascade++) for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) { // Reduce frequency of the updates if (useCache && (Engine::FrameCount % cascadeFrequencies[cascadeIndex]) != 0) continue; auto& cascade = sdfData.Cascades[cascadeIndex]; - const float distance = cascadesDistances[cascadeIndex]; - const float maxDistance = distance * 2; - const float voxelSize = maxDistance / resolution; - const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex]; + const float cascadeMaxDistance = cascadeDistance * 2; + const float cascadeVoxelSize = cascadeMaxDistance / resolution; + const float cascadeChunkSize = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); - const Vector3 center = Vector3::Floor(renderContext.View.Position / chunkSize) * chunkSize; + const Vector3 center = Vector3::Floor(renderContext.View.Position / cascadeChunkSize) * cascadeChunkSize; //const Vector3 center = Vector3::Zero; - BoundingBox cascadeBounds(center - distance, center + distance); + BoundingBox cascadeBounds(center - cascadeDistance, center + cascadeDistance); // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) - const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade + const float minObjectRadius = Math::Max(20.0f, cascadeVoxelSize * 0.5f); // Skip too small objects for this cascade GPUTextureView* cascadeView = cascade.Texture->ViewVolume(); GPUTextureView* cascadeMipView = cascade.Mip->ViewVolume(); @@ -478,18 +479,18 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex } // Check if cascade center has been moved - if (!(useCache && Vector3::NearEqual(cascade.Position, center, voxelSize))) + if (!(useCache && Vector3::NearEqual(cascade.Position, center, cascadeVoxelSize))) { // TODO: optimize for moving camera (copy sdf for cached chunks) cascade.StaticChunks.Clear(); } cascade.Position = center; - cascade.VoxelSize = voxelSize; + cascade.VoxelSize = cascadeVoxelSize; cascade.Bounds = cascadeBounds; // Draw all objects from all scenes into the cascade _objectsBufferCount = 0; - _voxelSize = voxelSize; + _voxelSize = cascadeVoxelSize; _cascadeBounds = cascadeBounds; _cascadeIndex = cascadeIndex; _sdfData = &sdfData; @@ -518,12 +519,12 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex } ModelsRasterizeData data; data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution; - data.CascadeCoordToPosAdd = cascadeBounds.Minimum + voxelSize * 0.5f; - data.MaxDistance = maxDistance; + data.CascadeCoordToPosAdd = cascadeBounds.Minimum + cascadeVoxelSize * 0.5f; + data.MaxDistance = cascadeMaxDistance; data.CascadeResolution = resolution; data.CascadeMipResolution = resolutionMip; data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; - data.CascadeVoxelSize = voxelSize; + data.CascadeVoxelSize = cascadeVoxelSize; context->BindUA(0, cascadeView); context->BindCB(1, _cb1); const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; @@ -728,12 +729,12 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) { auto& cascade = sdfData.Cascades[cascadeIndex]; - const float distance = cascadesDistances[cascadeIndex]; - const float maxDistance = distance * 2; - const float voxelSize = maxDistance / resolution; + const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex]; + const float cascadeMaxDistance = cascadeDistance * 2; + const float cascadeVoxelSize = cascadeMaxDistance / resolution; const Vector3 center = cascade.Position; - result.Constants.CascadePosDistance[cascadeIndex] = Vector4(center, distance); - result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = voxelSize; + result.Constants.CascadePosDistance[cascadeIndex] = Vector4(center, cascadeDistance); + result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = cascadeVoxelSize; result.Cascades[cascadeIndex] = cascade.Texture; result.CascadeMips[cascadeIndex] = cascade.Mip; } diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index 3017a6c8a..e09b364f4 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -22,19 +22,18 @@ // DDGI data for a constant buffer struct DDGIData { - float3 ProbesOrigin; - float ProbesSpacing; - float4 RaysRotation; + float4 ProbesOriginAndSpacing[4]; + int4 ProbesScrollOffsets[4]; + int4 ProbeScrollDirections[4]; uint3 ProbesCounts; + uint CascadesCount; float IrradianceGamma; - int3 ProbesScrollOffsets; float ProbeHistoryWeight; + float RayMaxDistance; + float Padding0; + float4 RaysRotation; float3 ViewDir; uint RaysCount; - int3 ProbeScrollDirections; - float RayMaxDistance; - uint3 ProbeScrollClear; // TODO: pack into bits - uint Padding0; }; uint GetDDGIProbeIndex(DDGIData data, uint3 probeCoords) @@ -62,88 +61,106 @@ uint3 GetDDGIProbeCoords(DDGIData data, uint probeIndex) return probeCoords; } -uint2 GetDDGIProbeTexelCoords(DDGIData data, uint probeIndex) +uint2 GetDDGIProbeTexelCoords(DDGIData data, uint cascadeIndex, uint probeIndex) { uint probesPerPlane = data.ProbesCounts.x * data.ProbesCounts.z; uint planeIndex = probeIndex / probesPerPlane; uint gridSpaceX = probeIndex % data.ProbesCounts.x; uint gridSpaceY = probeIndex / data.ProbesCounts.x; uint x = gridSpaceX + (planeIndex * data.ProbesCounts.x); - uint y = gridSpaceY % data.ProbesCounts.z; + uint y = gridSpaceY % data.ProbesCounts.z + cascadeIndex * data.ProbesCounts.z; return uint2(x, y); } -uint GetDDGIScrollingProbeIndex(DDGIData data, uint3 probeCoords) +uint GetDDGIScrollingProbeIndex(DDGIData data, uint cascadeIndex, uint3 probeCoords) { // Probes are scrolled on edges to stabilize GI when camera moves - return GetDDGIProbeIndex(data, (probeCoords + data.ProbesScrollOffsets + data.ProbesCounts) % data.ProbesCounts); + return GetDDGIProbeIndex(data, (probeCoords + data.ProbesScrollOffsets[cascadeIndex].xyz + data.ProbesCounts) % data.ProbesCounts); } -float3 GetDDGIProbeWorldPosition(DDGIData data, uint3 probeCoords) +float3 GetDDGIProbeWorldPosition(DDGIData data, uint cascadeIndex, uint3 probeCoords) { - float3 probePosition = probeCoords * data.ProbesSpacing; - float3 probeGridOffset = (data.ProbesSpacing * (data.ProbesCounts - 1)) * 0.5f; - return data.ProbesOrigin + probePosition - probeGridOffset + (data.ProbesScrollOffsets * data.ProbesSpacing); + float3 probesOrigin = data.ProbesOriginAndSpacing[cascadeIndex].xyz; + float probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; + float3 probePosition = probeCoords * probesSpacing; + float3 probeGridOffset = (probesSpacing * (data.ProbesCounts - 1)) * 0.5f; + return probesOrigin + probePosition - probeGridOffset + (data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing); } // Loads probe probe state -float LoadDDGIProbeState(DDGIData data, Texture2D probesState, uint probeIndex) +float LoadDDGIProbeState(DDGIData data, Texture2D probesState, uint cascadeIndex, uint probeIndex) { - int2 probeDataCoords = GetDDGIProbeTexelCoords(data, probeIndex); + int2 probeDataCoords = GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex); float4 probeState = probesState.Load(int3(probeDataCoords, 0)); return probeState.w; } // Loads probe world-space position (XYZ) and probe state (W) -float4 LoadDDGIProbePositionAndState(DDGIData data, Texture2D probesState, uint probeIndex, uint3 probeCoords) +float4 LoadDDGIProbePositionAndState(DDGIData data, Texture2D probesState, uint cascadeIndex, uint probeIndex, uint3 probeCoords) { - int2 probeDataCoords = GetDDGIProbeTexelCoords(data, probeIndex); + int2 probeDataCoords = GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex); float4 probeState = probesState.Load(int3(probeDataCoords, 0)); - probeState.xyz += GetDDGIProbeWorldPosition(data, probeCoords); + probeState.xyz += GetDDGIProbeWorldPosition(data, cascadeIndex, probeCoords); return probeState; } // Calculates texture UVs for sampling probes atlas texture (irradiance or distance) -float2 GetDDGIProbeUV(DDGIData data, uint probeIndex, float2 octahedralCoords, uint resolution) +float2 GetDDGIProbeUV(DDGIData data, uint cascadeIndex, uint probeIndex, float2 octahedralCoords, uint resolution) { - uint2 coords = GetDDGIProbeTexelCoords(data, probeIndex); + uint2 coords = GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex); float probeTexelSize = resolution + 2.0f; - float textureWidth = probeTexelSize * (data.ProbesCounts.x * data.ProbesCounts.y); - float textureHeight = probeTexelSize * data.ProbesCounts.z; + float2 textureSize = float2(data.ProbesCounts.x * data.ProbesCounts.y, data.ProbesCounts.z * data.CascadesCount) * probeTexelSize; float2 uv = float2(coords.x * probeTexelSize, coords.y * probeTexelSize) + (probeTexelSize * 0.5f); uv += octahedralCoords.xy * (resolution * 0.5f); - uv /= float2(textureWidth, textureHeight); + uv /= textureSize; return uv; } // Samples DDGI probes volume at the given world-space position and returns the irradiance. -float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Texture2D probesDistance, Texture2D probesIrradiance, float3 worldPosition, float3 worldNormal, float bias) +// rand - randomized per-pixel value in range 0-1, used to smooth dithering for cascades blending +float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Texture2D probesDistance, Texture2D probesIrradiance, float3 worldPosition, float3 worldNormal, float bias, float dither = 0.0f) { - float4 irradiance = float4(0, 0, 0, 0); - float3 probesOrigin = data.ProbesScrollOffsets * data.ProbesSpacing + data.ProbesOrigin; - float3 probesExtent = (data.ProbesCounts - 1) * (data.ProbesSpacing * 0.5f); + // Select the highest cascade that contains the sample location + uint cascadeIndex = 0; + for (; cascadeIndex < data.CascadesCount; cascadeIndex++) + { + float probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; + float3 probesOrigin = data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing + data.ProbesOriginAndSpacing[cascadeIndex].xyz; + float3 probesExtent = (data.ProbesCounts - 1) * (probesSpacing * 0.5f); + float fadeDistance = probesSpacing * 0.5f; + float cascadeWeight = saturate(Min3(probesExtent - abs(worldPosition - probesOrigin)) / fadeDistance); + if (cascadeWeight > dither) // Use dither to make transition smoother + break; + } + if (cascadeIndex == data.CascadesCount) + return float3(0, 0, 0); + + float probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; + float3 probesOrigin = data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing + data.ProbesOriginAndSpacing[cascadeIndex].xyz; + float3 probesExtent = (data.ProbesCounts - 1) * (probesSpacing * 0.5f); // Bias the world-space position to reduce artifacts float3 surfaceBias = (worldNormal * bias) + (data.ViewDir * (bias * -4.0f)); float3 biasedWorldPosition = worldPosition + surfaceBias; // Get the grid coordinates of the probe nearest the biased world position - uint3 baseProbeCoords = clamp(uint3((worldPosition - probesOrigin + probesExtent) / data.ProbesSpacing), 0, data.ProbesCounts - 1); - float3 baseProbeWorldPosition = GetDDGIProbeWorldPosition(data, baseProbeCoords); - float3 biasAlpha = saturate((biasedWorldPosition - baseProbeWorldPosition) / data.ProbesSpacing); + uint3 baseProbeCoords = clamp(uint3((worldPosition - probesOrigin + probesExtent) / probesSpacing), 0, data.ProbesCounts - 1); + float3 baseProbeWorldPosition = GetDDGIProbeWorldPosition(data, cascadeIndex, baseProbeCoords); + float3 biasAlpha = saturate((biasedWorldPosition - baseProbeWorldPosition) / probesSpacing); // Loop over the closest probes to accumulate their contributions + float4 irradiance = float4(0, 0, 0, 0); for (uint i = 0; i < 8; i++) { uint3 probeCoordsOffset = uint3(i, i >> 1, i >> 2) & 1; uint3 probeCoords = clamp(baseProbeCoords + probeCoordsOffset, 0, data.ProbesCounts - 1); - uint probeIndex = GetDDGIScrollingProbeIndex(data, probeCoords); + uint probeIndex = GetDDGIScrollingProbeIndex(data, cascadeIndex, probeCoords); // Load probe position and state - float4 probeState = probesState.Load(int3(GetDDGIProbeTexelCoords(data, probeIndex), 0)); + float4 probeState = probesState.Load(int3(GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex), 0)); if (probeState.w == DDGI_PROBE_STATE_INACTIVE) continue; - float3 probeBasePosition = baseProbeWorldPosition + ((probeCoords - baseProbeCoords) * data.ProbesSpacing); + float3 probeBasePosition = baseProbeWorldPosition + ((probeCoords - baseProbeCoords) * probesSpacing); float3 probePosition = probeBasePosition + probeState.xyz; // Calculate the distance and direction from the (biased and non-biased) shading point and the probe @@ -156,7 +173,7 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur // Sample distance texture float2 octahedralCoords = GetOctahedralCoords(-biasedPosToProbe); - float2 uv = GetDDGIProbeUV(data, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_DISTANCE); + float2 uv = GetDDGIProbeUV(data, cascadeIndex, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_DISTANCE); float2 probeDistance = probesDistance.SampleLevel(SamplerLinearClamp, uv, 0).rg * 2.0f; float probeDistanceMean = probeDistance.x; float probeDistanceMean2 = probeDistance.y; @@ -183,7 +200,7 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur // Sample irradiance texture octahedralCoords = GetOctahedralCoords(worldNormal); - uv = GetDDGIProbeUV(data, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE); + uv = GetDDGIProbeUV(data, cascadeIndex, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE); float3 probeIrradiance = probesIrradiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb; #if DDGI_SRGB_BLENDING probeIrradiance = pow(probeIrradiance, data.IrradianceGamma * 0.5f); @@ -196,6 +213,18 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur irradiance += float4(probeIrradiance * weight, weight); } +#if 0 + // Debug DDGI cascades with colors + if (cascadeIndex == 0) + irradiance = float4(1, 0, 0, 1); + else if (cascadeIndex == 1) + irradiance = float4(0, 1, 0, 1); + else if (cascadeIndex == 2) + irradiance = float4(0, 0, 1, 1); + else + irradiance = float4(1, 0, 1, 1); +#endif + if (irradiance.a > 0.0f) { // Normalize irradiance @@ -204,10 +233,6 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur irradiance.rgb *= irradiance.rgb; #endif irradiance.rgb *= 2.0f * PI; - - // Fade-out outside the probes volume - float fadeDistance = data.ProbesSpacing * 0.5f; - irradiance.rgb *= saturate(Min3(probesExtent - abs(worldPosition - probesOrigin)) / fadeDistance); } return irradiance.rgb; } diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index e13ae04fe..ec576ede2 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -22,14 +22,20 @@ #define DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE 8 #define DDGI_PROBE_CLASSIFY_GROUP_SIZE 32 -META_CB_BEGIN(0, Data) +META_CB_BEGIN(0, Data0) DDGIData DDGI; GlobalSDFData GlobalSDF; GlobalSurfaceAtlasData GlobalSurfaceAtlas; GBufferData GBuffer; -float2 Padding0; float ResetBlend; +float TemporalTime; float IndirectLightingIntensity; +float2 Padding0; +META_CB_END + +META_CB_BEGIN(1, Data1) +float3 Padding1; +uint CascadeIndex; META_CB_END // Calculates the evenly distributed direction ray on a sphere (Spherical Fibonacci lattice) @@ -66,22 +72,24 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) if (probeIndex >= probesCount) return; uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); - probeIndex = GetDDGIScrollingProbeIndex(DDGI, probeCoords); - int2 probeDataCoords = GetDDGIProbeTexelCoords(DDGI, probeIndex); + probeIndex = GetDDGIScrollingProbeIndex(DDGI, CascadeIndex, probeCoords); + int2 probeDataCoords = GetDDGIProbeTexelCoords(DDGI, CascadeIndex, probeIndex); + float probesSpacing = DDGI.ProbesOriginAndSpacing[CascadeIndex].w; // Load probe state and position float4 probeState = RWProbesState[probeDataCoords]; - float3 probeBasePosition = GetDDGIProbeWorldPosition(DDGI, probeCoords); + float3 probeBasePosition = GetDDGIProbeWorldPosition(DDGI, CascadeIndex, probeCoords); float3 probePosition = probeBasePosition + probeState.xyz; probeState.w = DDGI_PROBE_STATE_ACTIVE; // Use Global SDF to quickly get distance and direction to the scene geometry float sdf; float3 sdfNormal = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, probePosition.xyz, sdf)); - float threshold = GlobalSDF.CascadeVoxelSize[0] * 0.5f; - float distanceLimit = length(DDGI.ProbesSpacing) * 2.0f; - float relocateLimit = length(DDGI.ProbesSpacing) * 0.6f; - if (abs(sdf) > distanceLimit) // Probe is too far from geometry + float sdfDst = abs(sdf); + float threshold = GlobalSDF.CascadeVoxelSize[CascadeIndex] * 0.5f; + float distanceLimit = length(probesSpacing) * 2.0f; + float relocateLimit = length(probesSpacing) * 0.6f; + if (sdfDst > distanceLimit) // Probe is too far from geometry { // Disable it probeState = float4(0, 0, 0, DDGI_PROBE_STATE_INACTIVE); @@ -90,9 +98,9 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) { if (sdf < threshold) // Probe is inside geometry { - if (abs(sdf) < relocateLimit) + if (sdfDst < relocateLimit) { - float3 offsetToAdd = sdfNormal * sdf; + float3 offsetToAdd = sdfNormal * (sdf + threshold); if (distance(probeState.xyz, offsetToAdd) < relocateLimit) { // Relocate it @@ -105,7 +113,7 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) probeState.xyz = float3(0, 0, 0); } } - else if (sdf > threshold * 2.0f) // Probe is far enough any geometry + else if (sdf > threshold * 4.0f) // Probe is far enough any geometry { // Reset relocation probeState.xyz = float3(0, 0, 0); @@ -146,10 +154,10 @@ void CS_TraceRays(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_Dispat uint rayIndex = DispatchThreadId.x; uint probeIndex = DispatchThreadId.y; uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); - probeIndex = GetDDGIScrollingProbeIndex(DDGI, probeCoords); + probeIndex = GetDDGIScrollingProbeIndex(DDGI, CascadeIndex, probeCoords); // Load current probe state and position - float4 probePositionAndState = LoadDDGIProbePositionAndState(DDGI, ProbesState, probeIndex, probeCoords); + float4 probePositionAndState = LoadDDGIProbePositionAndState(DDGI, ProbesState, CascadeIndex, probeIndex, probeCoords); if (probePositionAndState.w == DDGI_PROBE_STATE_INACTIVE) return; // Skip disabled probes float3 probeRayDirection = GetProbeRayDirection(DDGI, rayIndex); @@ -222,16 +230,20 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd uint probesCount = DDGI.ProbesCounts.x * DDGI.ProbesCounts.y * DDGI.ProbesCounts.z; bool skip = probeIndex >= probesCount; uint2 outputCoords = uint2(1, 1) + DispatchThreadId.xy + (DispatchThreadId.xy / DDGI_PROBE_RESOLUTION) * 2; - + outputCoords.y += CascadeIndex * DDGI.ProbesCounts.z * (DDGI_PROBE_RESOLUTION + 2); + // Clear probes that have been scrolled to a new positions (blending with current irradiance will happen the next frame) uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + int3 probesScrollOffsets = DDGI.ProbesScrollOffsets[CascadeIndex].xyz; + int probeScrollClear = DDGI.ProbesScrollOffsets[CascadeIndex].w; + int3 probeScrollDirections = DDGI.ProbeScrollDirections[CascadeIndex].xyz; UNROLL for (uint planeIndex = 0; planeIndex < 3; planeIndex++) { - if (DDGI.ProbeScrollClear[planeIndex]) + if (probeScrollClear & (1 << planeIndex) && !skip) { - int scrollOffset = DDGI.ProbesScrollOffsets[planeIndex]; - int scrollDirection = DDGI.ProbeScrollDirections[planeIndex]; + int scrollOffset = probesScrollOffsets[planeIndex]; + int scrollDirection = probeScrollDirections[planeIndex]; uint probeCount = DDGI.ProbesCounts[planeIndex]; uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount; if (probeCoords[planeIndex] == coord) @@ -244,7 +256,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd } // Skip disabled probes - float probeState = LoadDDGIProbeState(DDGI, ProbesState, probeIndex); + float probeState = LoadDDGIProbeState(DDGI, ProbesState, CascadeIndex, probeIndex); if (probeState == DDGI_PROBE_STATE_INACTIVE) skip = true; @@ -275,7 +287,8 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd uint backfacesCount = 0; uint backfacesLimit = uint(DDGI.RaysCount * 0.1f); #else - float distanceLimit = length(DDGI.ProbesSpacing) * 1.5f; + float probesSpacing = DDGI.ProbesOriginAndSpacing[CascadeIndex].w; + float distanceLimit = length(probesSpacing) * 1.5f; #endif LOOP for (uint rayIndex = 0; rayIndex < DDGI.RaysCount; rayIndex++) @@ -420,6 +433,7 @@ void CS_UpdateBorders(uint3 DispatchThreadId : SV_DispatchThreadID) #ifdef _PS_IndirectLighting #include "./Flax/GBuffer.hlsl" +#include "./Flax/Random.hlsl" #include "./Flax/LightingCommon.hlsl" Texture2D ProbesState : register(t4); @@ -445,8 +459,9 @@ void PS_IndirectLighting(Quad_VS2PS input, out float4 output : SV_Target0) // Sample irradiance float bias = 1.0f; - float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesState, ProbesDistance, ProbesIrradiance, gBuffer.WorldPos, gBuffer.Normal, bias); - + float dither = RandN2(input.TexCoord + TemporalTime).x; + float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesState, ProbesDistance, ProbesIrradiance, gBuffer.WorldPos, gBuffer.Normal, bias, dither); + // Calculate lighting float3 diffuseColor = GetDiffuseColor(gBuffer); float3 diffuse = Diffuse_Lambert(diffuseColor); diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index 0d561db69..6ee0efc75 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -209,5 +209,5 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] float GetGlobalSurfaceAtlasThreshold(GlobalSDFHit hit) { // Scale the threshold based on the hit cascade (less precision) - return hit.HitCascade * 10.0f + 20.0f; + return hit.HitCascade * 20.0f + 25.0f; } diff --git a/Source/Shaders/Random.hlsl b/Source/Shaders/Random.hlsl index 98f6009bd..33aa3cbb4 100644 --- a/Source/Shaders/Random.hlsl +++ b/Source/Shaders/Random.hlsl @@ -9,6 +9,18 @@ float PseudoRandom(float2 xy) return frac(dot(p.xyx * p.xyy, float3(20.390625f, 60.703125f, 2.4281209f))); } +// Generic noise (1-component) +float RandN1(float n) +{ + return frac(sin(n) * 43758.5453123); +} + +// Generic noise (2-components) +float2 RandN2(float2 n) +{ + return frac(sin(dot(n, float2(12.9898, 78.233))) * float2(43758.5453123, 28001.8384)); +} + void FindBestAxisVectors(float3 input, out float3 axis1, out float3 axis2) { const float3 a = abs(input); diff --git a/Source/Shaders/SSR.hlsl b/Source/Shaders/SSR.hlsl index 86d8c5f6b..ccb2b1e89 100644 --- a/Source/Shaders/SSR.hlsl +++ b/Source/Shaders/SSR.hlsl @@ -6,16 +6,6 @@ #include "./Flax/MonteCarlo.hlsl" #include "./Flax/GBufferCommon.hlsl" -float max2(float2 v) -{ - return max(v.x, v.y); -} - -float2 RandN2(float2 pos, float2 random) -{ - return frac(sin(dot(pos.xy + random, float2(12.9898, 78.233))) * float2(43758.5453, 28001.8384)); -} - // 1:-1 to 0:1 float2 ClipToUv(float2 clipPos) { @@ -62,7 +52,7 @@ float3 TraceSceenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D dep float3 normalVS = mul(gBuffer.Normal, (float3x3)viewMatrix); // Randomize it a little - float2 jitter = RandN2(uv, temporalTime); + float2 jitter = RandN2(uv + temporalTime); float2 Xi = jitter; Xi.y = lerp(Xi.y, 0.0, brdfBias); float3 H = temporal ? TangentToWorld(gBuffer.Normal, ImportanceSampleGGX(Xi, gBuffer.Roughness)) : gBuffer.Normal; @@ -80,7 +70,8 @@ float3 TraceSceenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D dep float3 endUV = ProjectWorldToUv(startWS + reflectWS, viewProjectionMatrix); float3 rayUV = endUV - startUV; - rayUV *= stepSize / max2(abs(rayUV.xy)); + float2 rayUVAbs = abs(rayUV.xy); + rayUV *= stepSize / max(rayUVAbs.x, rayUVAbs.y); float3 startUv = startUV + rayUV * 2; float3 currOffset = startUv; diff --git a/Source/Shaders/SSR.shader b/Source/Shaders/SSR.shader index 5893a6ad1..15d1975e0 100644 --- a/Source/Shaders/SSR.shader +++ b/Source/Shaders/SSR.shader @@ -138,7 +138,7 @@ float4 PS_ResolvePass(Quad_VS2PS input) : SV_Target0 float3 viewVector = normalize(gBufferData.ViewPos - gBuffer.WorldPos); // Randomize it a little - float2 random = RandN2(uv, TemporalTime); + float2 random = RandN2(uv + TemporalTime); float2 blueNoise = random.xy * 2.0 - 1.0; float2x2 offsetRotationMatrix = float2x2(blueNoise.x, blueNoise.y, -blueNoise.y, blueNoise.x); From 211491f3d2b5caa7378a3aa9e3e62244fec0bf2d Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 9 Jun 2022 10:55:26 +0200 Subject: [PATCH 22/27] Add missing AO for DDGI irradiance --- Source/Shaders/GI/DDGI.shader | 2 +- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index ec576ede2..ab29d23d5 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -465,7 +465,7 @@ void PS_IndirectLighting(Quad_VS2PS input, out float4 output : SV_Target0) // Calculate lighting float3 diffuseColor = GetDiffuseColor(gBuffer); float3 diffuse = Diffuse_Lambert(diffuseColor); - output = float4(diffuse * irradiance, 1); + output = float4(diffuse * irradiance * gBuffer.AO, 1); } #endif diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 4491efabd..194375636 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -126,7 +126,7 @@ float4 PS_Lighting(AtlasVertexOutput input) : SV_Target float3 diffuseColor = GetDiffuseColor(gBuffer); diffuseColor = min(diffuseColor, 0.9f); // Nothing reflects diffuse like perfectly in the real world (ensure to have energy loss at each light bounce) float3 diffuse = Diffuse_Lambert(diffuseColor); - float4 light = float4(diffuse * irradiance, 1); + float4 light = float4(diffuse * irradiance * gBuffer.AO, 1); #else // Calculate shadowing float3 L = Light.Direction; From 3a8e5e0bbec5b8908ab5e2681f99756047cb6584 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Thu, 9 Jun 2022 11:31:20 +0200 Subject: [PATCH 23/27] Various DDGI quality improvements --- Content/Shaders/GI/DDGI.flax | 4 ++-- Content/Shaders/GI/GlobalSurfaceAtlas.flax | 4 ++-- .../Renderer/GI/DynamicDiffuseGlobalIllumination.cpp | 2 ++ .../Renderer/GI/DynamicDiffuseGlobalIllumination.h | 2 ++ Source/Shaders/GI/DDGI.hlsl | 4 +++- Source/Shaders/GI/DDGI.shader | 9 ++++++--- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 8d8ba1d43..25b849638 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ad0a077984ef2749700d6eb08bf1f6a23ea1fd84e4296128ecbe3d57994b007 -size 19306 +oid sha256:5d0a6384fca382ceb56dd0623dfef87eec29002f667b0608317f9519b492cfd1 +size 19388 diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index 6c82a055f..8a5772f9e 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f207d29b09af5c4e3564ee73f87b4d0eb0075d5982bbbd7c6275a723ab84ef1d -size 11787 +oid sha256:c34156f4c30f14b6d6bae1505b640d186557060c13bfec5ccf75777ca929c033 +size 11800 diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 679df6254..1d4bdbb27 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -276,6 +276,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; const float verticalRangeScale = 0.8f; // Scales the probes volume size at Y axis (horizontal aspect ratio makes the DDGI use less probes vertically to cover whole screen) const float probesSpacing = 200.0f; // GI probes placement spacing nearby camera (for closest cascade; gets automatically reduced for further cascades) + const Color fallbackIrradiance = Color::Black; // Irradiance lighting outside the DDGI range used as a fallback to prevent pure-black scene outside the GI range const Int3 probesCounts(Vector3::Ceil(Vector3(distanceExtent, distanceExtent * verticalRangeScale, distanceExtent) / probesSpacing)); const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality @@ -413,6 +414,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, ddgiData.Result.Constants.RaysCount = probeRaysCount; ddgiData.Result.Constants.ProbeHistoryWeight = probeHistoryWeight; ddgiData.Result.Constants.IrradianceGamma = 5.0f; + ddgiData.Result.Constants.FallbackIrradiance = fallbackIrradiance.ToVector3() * fallbackIrradiance.A; ddgiData.Result.ProbesState = ddgiData.ProbesState->View(); ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->View(); ddgiData.Result.ProbesIrradiance = ddgiData.ProbesIrradiance->View(); diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h index 64bc0b3ca..35158a7bc 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h @@ -27,6 +27,8 @@ public: Vector4 RaysRotation; Vector3 ViewDir; uint32 RaysCount; + Vector3 FallbackIrradiance; + float Padding1; }); // Binding data for the GPU. diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index e09b364f4..7aaf6527b 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -34,6 +34,8 @@ struct DDGIData float4 RaysRotation; float3 ViewDir; uint RaysCount; + float3 FallbackIrradiance; + float Padding1; }; uint GetDDGIProbeIndex(DDGIData data, uint3 probeCoords) @@ -133,7 +135,7 @@ float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Textur break; } if (cascadeIndex == data.CascadesCount) - return float3(0, 0, 0); + return data.FallbackIrradiance; float probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; float3 probesOrigin = data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing + data.ProbesOriginAndSpacing[cascadeIndex].xyz; diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index ab29d23d5..c22b33200 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -86,7 +86,7 @@ void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) float sdf; float3 sdfNormal = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, probePosition.xyz, sdf)); float sdfDst = abs(sdf); - float threshold = GlobalSDF.CascadeVoxelSize[CascadeIndex] * 0.5f; + float threshold = GlobalSDF.CascadeVoxelSize[CascadeIndex]; float distanceLimit = length(probesSpacing) * 2.0f; float relocateLimit = length(probesSpacing) * 0.6f; if (sdfDst > distanceLimit) // Probe is too far from geometry @@ -305,7 +305,10 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd // Skip further blending after reaching backfaces limit if (backfacesCount >= backfacesLimit) - return; + { + result = float4(0, 0, 0, 1); + break; + } continue; } @@ -313,7 +316,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd result += float4(rayRadiance.rgb * rayWeight, rayWeight); #else // Increase reaction speed for depth discontinuities - rayWeight = pow(rayWeight, 4.0f); + rayWeight = pow(rayWeight, 10.0f); // Add distance (R), distance^2 (G) and weight (A) float rayDistance = min(abs(rayRadiance.w), distanceLimit); From 3b27ae5fa98e585d067a279bb690b6684f3cb1b3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 10 Jun 2022 10:39:46 +0200 Subject: [PATCH 24/27] Optimize `ProbesTrace` texture in DDGI to use batched probes update --- .../GI/DynamicDiffuseGlobalIllumination.cpp | 97 ++++++++++--------- Source/Shaders/GI/DDGI.shader | 54 ++++++----- 2 files changed, 80 insertions(+), 71 deletions(-) diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 1d4bdbb27..f471cb89a 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -31,6 +31,7 @@ // "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields", https://jcgt.org/published/0008/02/01/ // This must match HLSL +#define DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT 4096 // Maximum amount of probes to update at once during rays tracing and blending #define DDGI_TRACE_RAYS_GROUP_SIZE_X 32 #define DDGI_TRACE_RAYS_LIMIT 512 // Limit of rays per-probe (runtime value can be smaller) #define DDGI_PROBE_RESOLUTION_IRRADIANCE 6 // Resolution (in texels) for probe irradiance data (excluding 1px padding on each side) @@ -52,8 +53,10 @@ PACK_STRUCT(struct Data0 PACK_STRUCT(struct Data1 { - Vector3 Padding1; - uint32 CascadeIndex; // TODO: use push constants on Vulkan or root signature data on DX12 to reduce overhead of changing single DWORD + // TODO: use push constants on Vulkan or root signature data on DX12 to reduce overhead of changing single DWORD + Vector2 Padding1; + uint32 CascadeIndex; + uint32 ProbeIndexOffset; }); class DDGICustomBuffer : public RenderBuffers::CustomBuffer @@ -335,7 +338,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, // TODO rethink probes data placement in memory -> what if we get [50x50x30] resolution? That's 75000 probes! Use sparse storage with active-only probes #define INIT_TEXTURE(texture, format, width, height) desc.Format = format; desc.Width = width; desc.Height = height; ddgiData.texture = RenderTargetPool::Get(desc); if (!ddgiData.texture) return true; memUsage += ddgiData.texture->GetMemoryUsage() desc.Flags = GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess; - INIT_TEXTURE(ProbesTrace, PixelFormat::R16G16B16A16_Float, probeRaysCount, probesCountTotal); // TODO: limit to 4k probes for a single batch to trace + INIT_TEXTURE(ProbesTrace, PixelFormat::R16G16B16A16_Float, probeRaysCount, Math::Min(probesCountCascade, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT)); INIT_TEXTURE(ProbesState, PixelFormat::R16G16B16A16_Float, probesCountTotalX, probesCountTotalY); // TODO: optimize to a RGBA32 (pos offset can be normalized to [0-0.5] range of ProbesSpacing and packed with state flag) INIT_TEXTURE(ProbesIrradiance, PixelFormat::R11G11B10_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2)); INIT_TEXTURE(ProbesDistance, PixelFormat::R16G16_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2)); @@ -481,59 +484,57 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, if (cascadeSkipUpdate[cascadeIndex]) continue; anyDirty = true; - Data1 data; - data.CascadeIndex = cascadeIndex; - context->UpdateCB(_cb1, &data); - context->BindCB(1, _cb1); - // TODO: run probes tracing+update in 4k batches - - // Trace rays from probes + // Update probes in batches so ProbesTrace texture can be smaller + for (int32 probesOffset = 0; probesOffset < probesCountCascade; probesOffset += DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT) { - PROFILE_GPU_CPU("Trace Rays"); + uint32 probesBatchSize = Math::Min(probesCountCascade - probesOffset, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT); + Data1 data; + data.CascadeIndex = cascadeIndex; + data.ProbeIndexOffset = probesOffset; + context->UpdateCB(_cb1, &data); + context->BindCB(1, _cb1); - // Global SDF with Global Surface Atlas software raytracing (thread X - per probe ray, thread Y - per probe) - ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0); - for (int32 i = 0; i < 4; i++) + // Trace rays from probes { - context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); - } - context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr); - context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr); - context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); - context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View()); - context->BindSR(12, ddgiData.Result.ProbesState); - context->BindSR(13, skybox); - context->BindUA(0, ddgiData.ProbesTrace->View()); - context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesCountCascade, 1); - context->ResetUA(); - context->ResetSR(); + PROFILE_GPU_CPU("Trace Rays"); + // Global SDF with Global Surface Atlas software raytracing (thread X - per probe ray, thread Y - per probe) + ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0); + for (int32 i = 0; i < 4; i++) + { + context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); + context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); + } + context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr); + context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr); + context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); + context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View()); + context->BindSR(12, ddgiData.Result.ProbesState); + context->BindSR(13, skybox); + context->BindUA(0, ddgiData.ProbesTrace->View()); + context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesBatchSize, 1); + context->ResetUA(); + context->ResetSR(); #if 0 - // Probes trace debug preview - context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); - context->SetRenderTarget(lightBuffer); - context->Draw(ddgiData.ProbesTrace); - return false; + // Probes trace debug preview + context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); + context->SetRenderTarget(lightBuffer); + context->Draw(ddgiData.ProbesTrace); + return false; #endif - } + } - context->BindSR(0, ddgiData.Result.ProbesState); - context->BindSR(1, ddgiData.ProbesTrace->View()); - - // Update probes irradiance texture - { - PROFILE_GPU_CPU("Update Irradiance"); - context->BindUA(0, ddgiData.Result.ProbesIrradiance); - context->Dispatch(_csUpdateProbesIrradiance, probesCountCascadeX, probesCountCascadeY, 1); - } - - // Update probes distance texture - { - PROFILE_GPU_CPU("Update Distance"); - context->BindUA(0, ddgiData.Result.ProbesDistance); - context->Dispatch(_csUpdateProbesDistance, probesCountCascadeX, probesCountCascadeY, 1); + // Update probes irradiance and distance textures (one thread-group per probe) + { + PROFILE_GPU_CPU("Update Probes"); + context->BindSR(0, ddgiData.Result.ProbesState); + context->BindSR(1, ddgiData.ProbesTrace->View()); + context->BindUA(0, ddgiData.Result.ProbesIrradiance); + context->Dispatch(_csUpdateProbesIrradiance, probesBatchSize, 1, 1); + context->BindUA(0, ddgiData.Result.ProbesDistance); + context->Dispatch(_csUpdateProbesDistance, probesBatchSize, 1, 1); + } } } diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index c22b33200..08a3525e1 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -34,8 +34,9 @@ float2 Padding0; META_CB_END META_CB_BEGIN(1, Data1) -float3 Padding1; +float2 Padding1; uint CascadeIndex; +uint ProbeIndexOffset; META_CB_END // Calculates the evenly distributed direction ray on a sphere (Spherical Fibonacci lattice) @@ -149,10 +150,10 @@ TextureCube Skybox : register(t13); // Compute shader for tracing rays for probes using Global SDF and Global Surface Atlas. META_CS(true, FEATURE_LEVEL_SM5) [numthreads(DDGI_TRACE_RAYS_GROUP_SIZE_X, 1, 1)] -void CS_TraceRays(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID) +void CS_TraceRays(uint3 DispatchThreadId : SV_DispatchThreadID) { uint rayIndex = DispatchThreadId.x; - uint probeIndex = DispatchThreadId.y; + uint probeIndex = DispatchThreadId.y + ProbeIndexOffset; uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); probeIndex = GetDDGIScrollingProbeIndex(DDGI, CascadeIndex, probeCoords); @@ -196,7 +197,7 @@ void CS_TraceRays(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_Dispat } // Write into probes trace results - RWProbesTrace[uint2(rayIndex, probeIndex)] = radiance; + RWProbesTrace[uint2(rayIndex, DispatchThreadId.y)] = radiance; } #endif @@ -223,24 +224,28 @@ META_CS(true, FEATURE_LEVEL_SM5) META_PERMUTATION_1(DDGI_PROBE_UPDATE_MODE=0) META_PERMUTATION_1(DDGI_PROBE_UPDATE_MODE=1) [numthreads(DDGI_PROBE_RESOLUTION, DDGI_PROBE_RESOLUTION, 1)] -void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupIndex : SV_GroupIndex) +void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) { + // GroupThreadId.xy - coordinates of the probe texel: [0; DDGI_PROBE_RESOLUTION) + // GroupId.x - index of the thread group which is probe index within a batch: [0; batchSize) + // GroupIndex.x - index of the thread within a thread group: [0; DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTION) + // Get probe index and atlas location in the atlas - uint probeIndex = GetDDGIProbeIndex(DDGI, DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); - uint probesCount = DDGI.ProbesCounts.x * DDGI.ProbesCounts.y * DDGI.ProbesCounts.z; - bool skip = probeIndex >= probesCount; - uint2 outputCoords = uint2(1, 1) + DispatchThreadId.xy + (DispatchThreadId.xy / DDGI_PROBE_RESOLUTION) * 2; - outputCoords.y += CascadeIndex * DDGI.ProbesCounts.z * (DDGI_PROBE_RESOLUTION + 2); - - // Clear probes that have been scrolled to a new positions (blending with current irradiance will happen the next frame) + uint probeIndex = GroupId.x + ProbeIndexOffset; uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + probeIndex = GetDDGIScrollingProbeIndex(DDGI, CascadeIndex, probeCoords); + probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + uint2 outputCoords = GetDDGIProbeTexelCoords(DDGI, CascadeIndex, probeIndex) * (DDGI_PROBE_RESOLUTION + 2) + 1 + GroupThreadId.xy; + + // Clear probes that have been scrolled to a new positions (blending with current irradiance will happen the next frame) int3 probesScrollOffsets = DDGI.ProbesScrollOffsets[CascadeIndex].xyz; int probeScrollClear = DDGI.ProbesScrollOffsets[CascadeIndex].w; int3 probeScrollDirections = DDGI.ProbeScrollDirections[CascadeIndex].xyz; + bool skip = false; UNROLL for (uint planeIndex = 0; planeIndex < 3; planeIndex++) { - if (probeScrollClear & (1 << planeIndex) && !skip) + if (probeScrollClear & (1 << planeIndex)) { int scrollOffset = probesScrollOffsets[planeIndex]; int scrollDirection = probeScrollDirections[planeIndex]; @@ -248,12 +253,15 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount; if (probeCoords[planeIndex] == coord) { - // Clear and skip scrolled probes - RWOutput[outputCoords] = float4(0, 0, 0, 0); skip = true; } } } + if (skip) + { + // Clear scrolled probe + RWOutput[outputCoords] = float4(0, 0, 0, 0); + } // Skip disabled probes float probeState = LoadDDGIProbeState(DDGI, ProbesState, CascadeIndex, probeIndex); @@ -262,14 +270,14 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd if (!skip) { - // Load trace rays results into shared memory to reuse across whole thread group - uint count = (uint)(ceil((float)(DDGI_TRACE_RAYS_LIMIT) / (float)(DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTION))); - for (uint i = 0; i < count; i++) + // Load trace rays results into shared memory to reuse across whole thread group (raysCount per thread) + uint raysCount = (uint)(ceil((float)DDGI.RaysCount / (float)(DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTION))); + uint raysStart = GroupIndex * raysCount; + raysCount = max(min(raysStart + raysCount, DDGI.RaysCount), raysStart) - raysStart; + for (uint i = 0; i < raysCount; i++) { - uint rayIndex = (GroupIndex * count) + i; - if (rayIndex >= DDGI.RaysCount) - break; - CachedProbesTraceRadiance[rayIndex] = ProbesTrace[uint2(rayIndex, probeIndex)]; + uint rayIndex = raysStart + i; + CachedProbesTraceRadiance[rayIndex] = ProbesTrace[uint2(rayIndex, GroupId.x)]; CachedProbesTraceDirection[rayIndex] = GetProbeRayDirection(DDGI, rayIndex); } } @@ -278,7 +286,7 @@ void CS_UpdateProbes(uint3 DispatchThreadId : SV_DispatchThreadID, uint GroupInd return; // Calculate octahedral projection for probe (unwraps spherical projection into a square) - float2 octahedralCoords = GetOctahedralCoords(DispatchThreadId.xy, DDGI_PROBE_RESOLUTION); + float2 octahedralCoords = GetOctahedralCoords(GroupThreadId.xy, DDGI_PROBE_RESOLUTION); float3 octahedralDirection = GetOctahedralDirection(octahedralCoords); // Loop over rays From f685c672759a3309af509a07830fc0f3042b914f Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 10 Jun 2022 14:30:40 +0200 Subject: [PATCH 25/27] Add Global SDF quality setting and support for variable cascades count and resolution --- .../Editor/Windows/GraphicsQualityWindow.cs | 8 ++ .../Engine/Core/Config/GraphicsSettings.cpp | 1 + Source/Engine/Core/Config/GraphicsSettings.h | 6 + Source/Engine/Graphics/Graphics.cpp | 1 + Source/Engine/Graphics/Graphics.h | 5 + .../Graphics/Materials/MaterialParams.cpp | 3 +- .../GI/DynamicDiffuseGlobalIllumination.cpp | 12 +- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 21 +-- .../Renderer/GlobalSignDistanceFieldPass.cpp | 123 ++++++++++++------ .../Renderer/GlobalSignDistanceFieldPass.h | 6 +- Source/Shaders/GI/DDGI.shader | 2 +- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 2 +- Source/Shaders/GlobalSignDistanceField.hlsl | 15 ++- 13 files changed, 128 insertions(+), 77 deletions(-) diff --git a/Source/Editor/Windows/GraphicsQualityWindow.cs b/Source/Editor/Windows/GraphicsQualityWindow.cs index 9ece021c5..3442a4e7c 100644 --- a/Source/Editor/Windows/GraphicsQualityWindow.cs +++ b/Source/Editor/Windows/GraphicsQualityWindow.cs @@ -64,6 +64,14 @@ namespace FlaxEditor.Windows set => Graphics.VolumetricFogQuality = value; } + [DefaultValue(Quality.High)] + [EditorOrder(1280), EditorDisplay("Quality"), Tooltip("The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.")] + public Quality GlobalSDFQuality + { + get => Graphics.GlobalSDFQuality; + set => Graphics.GlobalSDFQuality = value; + } + [DefaultValue(Quality.Medium)] [EditorOrder(1300), EditorDisplay("Quality", "Shadows Quality"), Tooltip("The shadows quality.")] public Quality ShadowsQuality diff --git a/Source/Engine/Core/Config/GraphicsSettings.cpp b/Source/Engine/Core/Config/GraphicsSettings.cpp index c411fe969..f3e08b63b 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.cpp +++ b/Source/Engine/Core/Config/GraphicsSettings.cpp @@ -13,4 +13,5 @@ void GraphicsSettings::Apply() Graphics::ShadowsQuality = ShadowsQuality; Graphics::ShadowMapsQuality = ShadowMapsQuality; Graphics::AllowCSMBlending = AllowCSMBlending; + Graphics::GlobalSDFQuality = GlobalSDFQuality; } diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 53bf89a61..0ec2b26b1 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -68,6 +68,12 @@ public: API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")") bool EnableGlobalSDF = false; + /// + /// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. + /// + API_FIELD(Attributes="EditorOrder(2005), DefaultValue(Quality.High), EditorDisplay(\"Quality\")") + Quality GlobalSDFQuality = Quality::High; + #if USE_EDITOR /// /// If checked, the 'Generate SDF' option will be checked on model import options by default. Use it if your project uses Global SDF (eg. for Global Illumination or particles). diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index a1004064a..4cdbf9eb2 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -14,6 +14,7 @@ Quality Graphics::VolumetricFogQuality = Quality::High; Quality Graphics::ShadowsQuality = Quality::Medium; Quality Graphics::ShadowMapsQuality = Quality::Medium; bool Graphics::AllowCSMBlending = false; +Quality Graphics::GlobalSDFQuality = Quality::High; #if GRAPHICS_API_NULL extern GPUDevice* CreateGPUDeviceNull(); diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index 986623b1f..a39c89fd9 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -53,6 +53,11 @@ public: /// API_FIELD() static bool AllowCSMBlending; + /// + /// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. + /// + API_FIELD() static Quality GlobalSDFQuality; + public: /// diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 87d4193ef..a118526cf 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -488,8 +488,7 @@ void MaterialParameter::Bind(BindMeta& meta) const GlobalSignDistanceFieldPass::BindingData bindingData; if (GlobalSignDistanceFieldPass::Instance()->Get(meta.Buffers, bindingData)) Platform::MemoryClear(&bindingData, sizeof(bindingData)); - for (int32 i = 0; i < 4; i++) - meta.Context->BindSR(_registerIndex + i, bindingData.Cascades[i] ? bindingData.Cascades[i]->ViewVolume() : nullptr); + bindingData.BindCascades(meta.Context, _registerIndex); *((GlobalSignDistanceFieldPass::ConstantsData*)(meta.Constants.Get() + _offset)) = bindingData.Constants; break; } diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index f471cb89a..0e2a9ce7a 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -456,10 +456,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, { PROFILE_GPU_CPU("Probes Classification"); uint32 threadGroups = Math::DivideAndRoundUp(probesCountCascade, DDGI_PROBE_CLASSIFY_GROUP_SIZE); - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); - } + bindingDataSDF.BindCascades(context, 0); context->BindUA(0, ddgiData.Result.ProbesState); for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { @@ -501,11 +498,8 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, // Global SDF with Global Surface Atlas software raytracing (thread X - per probe ray, thread Y - per probe) ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0); - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); - } + bindingDataSDF.BindCascades(context, 0); + bindingDataSDF.BindCascadeMips(context, 4); context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr); context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr); context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index eb89f0372..446981076 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -753,11 +753,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View()); context->BindSR(3, surfaceAtlasData.AtlasDepth->View()); context->BindSR(4, _objectsBuffer->GetBuffer()->View()); - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i + 5, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 9, bindingDataSDF.CascadeMips[i]->ViewVolume()); - } + bindingDataSDF.BindCascades(context, 5); + bindingDataSDF.BindCascadeMips(context, 9); context->BindCB(0, _cb0); Data0 data; data.ViewWorldPos = renderContext.View.Position; @@ -921,11 +918,8 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); - } + bindingDataSDF.BindCascades(context, 0); + bindingDataSDF.BindCascadeMips(context, 4); context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); context->BindSR(10, bindingData.AtlasDepth->View()); @@ -954,11 +948,8 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->ResetRenderTarget(); // Rebind resources - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); - } + bindingDataSDF.BindCascades(context, 0); + bindingDataSDF.BindCascadeMips(context, 4); context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); context->BindSR(10, bindingData.AtlasDepth->View()); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index d9682867b..a9346ca2c 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -7,6 +7,7 @@ #include "Engine/Engine/Engine.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" @@ -173,7 +174,8 @@ struct CascadeData class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener { public: - CascadeData Cascades[4]; + int32 Resolution = 0; + Array> Cascades; HashSet ObjectTypes; HashSet SDFTextures; GlobalSignDistanceFieldPass::BindingData Result; @@ -349,6 +351,18 @@ void GlobalSignDistanceFieldPass::Dispose() ChunksCache.SetCapacity(0); } +void GlobalSignDistanceFieldPass::BindingData::BindCascades(GPUContext* context, int32 srvSlot) +{ + for (int32 i = 0; i < 4; i++) + context->BindSR(srvSlot + i, Cascades[i] ? Cascades[i]->ViewVolume() : nullptr); +} + +void GlobalSignDistanceFieldPass::BindingData::BindCascadeMips(GPUContext* context, int32 srvSlot) +{ + for (int32 i = 0; i < 4; i++) + context->BindSR(srvSlot + i, CascadeMips[i] ? CascadeMips[i]->ViewVolume() : nullptr); +} + bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) { auto* sdfData = buffers ? buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")) : nullptr; @@ -379,49 +393,71 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex sdfData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global SDF"); - // TODO: configurable via graphics settings - const int32 resolution = 256; + // Setup options + int32 resolution, cascadesCount; + switch (Graphics::GlobalSDFQuality) + { + case Quality::Low: + resolution = 128; + cascadesCount = 2; + break; + case Quality::Medium: + resolution = 128; + cascadesCount = 3; + break; + case Quality::High: + resolution = 192; + cascadesCount = 4; + break; + case Quality::Ultra: + default: + resolution = 256; + cascadesCount = 4; + break; + } const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); - // TODO: configurable via postFx settings - const int32 cascadesCount = 4; // in range 1-4 const float distance = true ? 20000.0f : 16000.0f; // TODO: switch based if using GI, then use GI range - const float cascadesDistanceScales[] = { 1.0f, 2.0f, 4.0f, 8.0f }; + const float cascadesDistanceScales[] = { 1.0f, 2.5f, 5.0f, 10.0f }; const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; // Initialize buffers - auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); bool updated = false; - for (auto& cascade : sdfData.Cascades) + if (sdfData.Cascades.Count() != cascadesCount || sdfData.Resolution != resolution) { - GPUTexture*& texture = cascade.Texture; - if (texture && texture->Width() != desc.Width) + sdfData.Cascades.Resize(cascadesCount); + sdfData.Resolution = resolution; + updated = true; + auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + for (auto& cascade : sdfData.Cascades) { - RenderTargetPool::Release(texture); - texture = nullptr; - } - if (!texture) - { - texture = RenderTargetPool::Get(desc); + GPUTexture*& texture = cascade.Texture; + if (texture && texture->Width() != desc.Width) + { + RenderTargetPool::Release(texture); + texture = nullptr; + } if (!texture) - return true; - updated = true; + { + texture = RenderTargetPool::Get(desc); + if (!texture) + return true; + } } - } - desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); - for (auto& cascade : sdfData.Cascades) - { - GPUTexture*& texture = cascade.Mip; - if (texture && texture->Width() != desc.Width) + desc.Width = desc.Height = desc.Depth = resolutionMip; + for (auto& cascade : sdfData.Cascades) { - RenderTargetPool::Release(texture); - texture = nullptr; - } - if (!texture) - { - texture = RenderTargetPool::Get(desc); + GPUTexture*& texture = cascade.Mip; + if (texture && texture->Width() != desc.Width) + { + RenderTargetPool::Release(texture); + texture = nullptr; + } if (!texture) - return true; - updated = true; + { + texture = RenderTargetPool::Get(desc); + if (!texture) + return true; + } } } GPUTexture* tmpMip = nullptr; @@ -451,7 +487,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex bool anyDraw = false; const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; - for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { // Reduce frequency of the updates if (useCache && (Engine::FrameCount % cascadeFrequencies[cascadeIndex]) != 0) @@ -513,12 +549,13 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex { anyDraw = true; context->ResetSR(); + auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); tmpMip = RenderTargetPool::Get(desc); if (!tmpMip) return true; } ModelsRasterizeData data; - data.CascadeCoordToPosMul = cascadeBounds.GetSize() / resolution; + data.CascadeCoordToPosMul = cascadeBounds.GetSize() / (float)resolution; data.CascadeCoordToPosAdd = cascadeBounds.Minimum + cascadeVoxelSize * 0.5f; data.MaxDistance = cascadeMaxDistance; data.CascadeResolution = resolution; @@ -725,8 +762,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // Copy results static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); - static_assert(ARRAY_COUNT(sdfData.Cascades) == 4, "Invalid cascades count."); - for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { auto& cascade = sdfData.Cascades[cascadeIndex]; const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex]; @@ -738,7 +774,15 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex result.Cascades[cascadeIndex] = cascade.Texture; result.CascadeMips[cascadeIndex] = cascade.Mip; } + for (int32 cascadeIndex = cascadesCount; cascadeIndex < 4; cascadeIndex++) + { + result.Constants.CascadePosDistance[cascadeIndex] = result.Constants.CascadePosDistance[cascadesCount - 1]; + result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = result.Constants.CascadeVoxelSize.Raw[cascadesCount - 1]; + result.Cascades[cascadeIndex] = nullptr; + result.CascadeMips[cascadeIndex] = nullptr; + } result.Constants.Resolution = (float)resolution; + result.Constants.CascadesCount = cascadesCount; sdfData.Result = result; return false; } @@ -765,11 +809,8 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i, bindingData.Cascades[i]->ViewVolume()); - context->BindSR(i + 4, bindingData.CascadeMips[i]->ViewVolume()); - } + bindingData.BindCascades(context, 0); + bindingData.BindCascadeMips(context, 4); context->SetState(_psDebug); context->SetRenderTarget(output->View()); context->SetViewportAndScissors(outputSize.X, outputSize.Y); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index 5e1f0461a..df3f730cc 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -15,7 +15,8 @@ public: { Vector4 CascadePosDistance[4]; Vector4 CascadeVoxelSize; - Vector3 Padding; + Vector2 Padding; + uint32 CascadesCount; float Resolution; }); @@ -25,6 +26,9 @@ public: GPUTexture* Cascades[4]; GPUTexture* CascadeMips[4]; ConstantsData Constants; + + void BindCascades(GPUContext* context, int32 srvSlot); + void BindCascadeMips(GPUContext* context, int32 srvSlot); }; private: diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 08a3525e1..70cb92834 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -181,7 +181,7 @@ void CS_TraceRays(uint3 DispatchThreadId : SV_DispatchThreadID) { // Sample Global Surface Atlas to get the lighting at the hit location float3 hitPosition = hit.GetHitPosition(trace); - float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit); + float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(GlobalSDF, hit); float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hitPosition, -probeRayDirection, surfaceThreshold); radiance = float4(surfaceColor.rgb, hit.HitTime); diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 194375636..30f455675 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -293,7 +293,7 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target if (hit.IsHit()) { // Sample Global Surface Atlas at the hit location - float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(hit); + float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(GlobalSDF, hit); color = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold).rgb; //color = hit.HitNormal * 0.5f + 0.5f; } diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index 6ee0efc75..e7b4c99c1 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -13,7 +13,8 @@ struct GlobalSDFData { float4 CascadePosDistance[4]; float4 CascadeVoxelSize; - float3 Padding; + float2 Padding; + uint CascadesCount; float Resolution; }; @@ -65,7 +66,7 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 if (distance <= 0.0f) return GLOBAL_SDF_WORLD_SIZE; UNROLL - for (uint cascade = 0; cascade < 4; cascade++) + for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -89,7 +90,7 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4] if (data.CascadePosDistance[3].w <= 0.0f) return gradient; UNROLL - for (uint cascade = 0; cascade < 4; cascade++) + for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -124,7 +125,7 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] float traceMaxDistance = min(trace.MaxDistance, data.CascadePosDistance[3].w * 2); float3 traceEndPosition = trace.WorldPosition + trace.WorldDirection * traceMaxDistance; UNROLL - for (uint cascade = 0; cascade < 4 && hit.HitTime < 0.0f; cascade++) + for (uint cascade = 0; cascade < data.CascadesCount && hit.HitTime < 0.0f; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -206,8 +207,8 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] } // Calculates the surface threshold for Global Surface Atlas sampling which matches the Global SDF trace to reduce artifacts -float GetGlobalSurfaceAtlasThreshold(GlobalSDFHit hit) +float GetGlobalSurfaceAtlasThreshold(const GlobalSDFData data, const GlobalSDFHit hit) { - // Scale the threshold based on the hit cascade (less precision) - return hit.HitCascade * 20.0f + 25.0f; + // Scale the threshold based on the hit cascade (less precision) + return data.CascadeVoxelSize[hit.HitCascade] * 1.1f; } From aa586657409a1a44f45010d493192d61ea6b5aa3 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 10 Jun 2022 14:31:01 +0200 Subject: [PATCH 26/27] Move Global SDF towards the view direction --- .../Engine/Renderer/GlobalSignDistanceFieldPass.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index a9346ca2c..3f566c45b 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -476,6 +476,16 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex for (SceneRendering* scene : renderContext.List->Scenes) sdfData.ListenSceneRendering(scene); + // Calculate origin for Global SDF by shifting it towards the view direction to account for better view frustum coverage + Vector3 viewOrigin = renderContext.View.Position; + { + Vector3 viewDirection = renderContext.View.Direction; + const float cascade0Distance = distanceExtent * cascadesDistanceScales[0]; + const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (cascade0Distance * 2.0f), viewOrigin - cascade0Distance, viewOrigin + cascade0Distance); + const float viewOriginOffset = viewRayHit.Y * cascade0Distance * 0.6f; + viewOrigin += viewDirection * viewOriginOffset; + } + // Rasterize world geometry into Global SDF renderContext.View.Pass = DrawPass::GlobalSDF; uint32 viewMask = renderContext.View.RenderLayersMask; @@ -498,7 +508,7 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex const float cascadeVoxelSize = cascadeMaxDistance / resolution; const float cascadeChunkSize = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); - const Vector3 center = Vector3::Floor(renderContext.View.Position / cascadeChunkSize) * cascadeChunkSize; + const Vector3 center = Vector3::Floor(viewOrigin / cascadeChunkSize) * cascadeChunkSize; //const Vector3 center = Vector3::Zero; BoundingBox cascadeBounds(center - cascadeDistance, center + cascadeDistance); // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) From d4839b9c7877919b62207ddb1880f5aed0659687 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Fri, 10 Jun 2022 18:48:17 +0200 Subject: [PATCH 27/27] Add Global Illumination options to PostFx Settings --- Content/Shaders/GI/DDGI.flax | 4 +- Content/Shaders/GI/GlobalSurfaceAtlas.flax | 4 +- .../Dedicated/PostProcessSettingsEditor.cs | 19 ++ .../Editor/Windows/GraphicsQualityWindow.cs | 8 + .../Engine/Core/Config/GraphicsSettings.cpp | 1 + Source/Engine/Core/Config/GraphicsSettings.h | 6 + Source/Engine/Graphics/Graphics.cpp | 1 + Source/Engine/Graphics/Graphics.h | 5 + .../Engine/Graphics/PostProcessSettings.cpp | 16 ++ Source/Engine/Graphics/PostProcessSettings.h | 187 ++++++++++++++---- Source/Engine/Level/Actors/PostFxVolume.cpp | 5 + Source/Engine/Level/Actors/PostFxVolume.h | 6 + .../GI/DynamicDiffuseGlobalIllumination.cpp | 60 ++++-- .../GI/DynamicDiffuseGlobalIllumination.h | 4 +- .../Renderer/GI/GlobalSurfaceAtlasPass.cpp | 61 +++--- .../Renderer/GlobalSignDistanceFieldPass.cpp | 3 +- Source/Engine/Renderer/Renderer.cpp | 8 +- Source/Shaders/GI/DDGI.hlsl | 4 +- Source/Shaders/GI/DDGI.shader | 5 +- Source/Shaders/GI/GlobalSurfaceAtlas.shader | 1 + 20 files changed, 314 insertions(+), 94 deletions(-) diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 25b849638..9a21ebf60 100644 --- a/Content/Shaders/GI/DDGI.flax +++ b/Content/Shaders/GI/DDGI.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d0a6384fca382ceb56dd0623dfef87eec29002f667b0608317f9519b492cfd1 -size 19388 +oid sha256:0cf42c544950dd16b46719bafbcb24ce8fe3a82992aa77c57f8f13c1cb37e72f +size 19671 diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index 8a5772f9e..e3d29362c 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c34156f4c30f14b6d6bae1505b640d186557060c13bfec5ccf75777ca929c033 -size 11800 +oid sha256:4987c395ab87cedb82dedcab53de0166865a5a2a718aedef8b5bc45060466c9f +size 11862 diff --git a/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs index 490268ebb..abde71340 100644 --- a/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs @@ -110,6 +110,25 @@ namespace FlaxEditor.CustomEditors.Dedicated } } + /// + /// Editor for type. + /// + [CustomEditor(typeof(GlobalIlluminationSettings)), DefaultEditor] + sealed class GlobalIlluminationSettingsEditor : PostProcessSettingsEditor + { + /// + protected override int OverrideFlags + { + get => (int)((GlobalIlluminationSettings)Values[0]).OverrideFlags; + set + { + var settings = (GlobalIlluminationSettings)Values[0]; + settings.OverrideFlags = (GlobalIlluminationSettingsOverride)value; + SetValue(settings); + } + } + } + /// /// Editor for type. /// diff --git a/Source/Editor/Windows/GraphicsQualityWindow.cs b/Source/Editor/Windows/GraphicsQualityWindow.cs index 3442a4e7c..957d1af1a 100644 --- a/Source/Editor/Windows/GraphicsQualityWindow.cs +++ b/Source/Editor/Windows/GraphicsQualityWindow.cs @@ -72,6 +72,14 @@ namespace FlaxEditor.Windows set => Graphics.GlobalSDFQuality = value; } + [DefaultValue(Quality.High)] + [EditorOrder(1290), EditorDisplay("Quality"), Tooltip("The Global Illumination quality. Controls the quality of the GI effect.")] + public Quality GIQuality + { + get => Graphics.GIQuality; + set => Graphics.GIQuality = value; + } + [DefaultValue(Quality.Medium)] [EditorOrder(1300), EditorDisplay("Quality", "Shadows Quality"), Tooltip("The shadows quality.")] public Quality ShadowsQuality diff --git a/Source/Engine/Core/Config/GraphicsSettings.cpp b/Source/Engine/Core/Config/GraphicsSettings.cpp index f3e08b63b..654e671db 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.cpp +++ b/Source/Engine/Core/Config/GraphicsSettings.cpp @@ -14,4 +14,5 @@ void GraphicsSettings::Apply() Graphics::ShadowMapsQuality = ShadowMapsQuality; Graphics::AllowCSMBlending = AllowCSMBlending; Graphics::GlobalSDFQuality = GlobalSDFQuality; + Graphics::GIQuality = GIQuality; } diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 0ec2b26b1..85caf77d8 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -82,6 +82,12 @@ public: bool GenerateSDFOnModelImport = false; #endif + /// + /// The Global Illumination quality. Controls the quality of the GI effect. + /// + API_FIELD(Attributes="EditorOrder(2100), DefaultValue(Quality.High), EditorDisplay(\"Quality\")") + Quality GIQuality = Quality::High; + public: /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index 4cdbf9eb2..f1baee478 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -15,6 +15,7 @@ Quality Graphics::ShadowsQuality = Quality::Medium; Quality Graphics::ShadowMapsQuality = Quality::Medium; bool Graphics::AllowCSMBlending = false; Quality Graphics::GlobalSDFQuality = Quality::High; +Quality Graphics::GIQuality = Quality::High; #if GRAPHICS_API_NULL extern GPUDevice* CreateGPUDeviceNull(); diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index a39c89fd9..01fa60749 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -58,6 +58,11 @@ public: /// API_FIELD() static Quality GlobalSDFQuality; + /// + /// The Global Illumination quality. Controls the quality of the GI effect. + /// + API_FIELD() static Quality GIQuality; + public: /// diff --git a/Source/Engine/Graphics/PostProcessSettings.cpp b/Source/Engine/Graphics/PostProcessSettings.cpp index b15cb70d1..0ea38d354 100644 --- a/Source/Engine/Graphics/PostProcessSettings.cpp +++ b/Source/Engine/Graphics/PostProcessSettings.cpp @@ -8,6 +8,7 @@ #define BLEND_FLOAT(name) if ((((int32)other.OverrideFlags & (int32)Override::name) != 0)) name = Math::Lerp(name, other.name, weight) #define BLEND_INT(name) BLEND_FLOAT(name) #define BLEND_VEC3(name) if ((((int32)other.OverrideFlags & (int32)Override::name) != 0)) name = Vector3::Lerp(name, other.name, weight) +#define BLEND_COL(name) if ((((int32)other.OverrideFlags & (int32)Override::name) != 0)) name = Color::Lerp(name, other.name, weight) #define BLEND_ENUM(name) BLEND_BOOL(name) #define BLEND_PROPERTY(name) BLEND_BOOL(name) @@ -23,6 +24,16 @@ void AmbientOcclusionSettings::BlendWith(AmbientOcclusionSettings& other, float BLEND_FLOAT(FadeDistance); } +void GlobalIlluminationSettings::BlendWith(GlobalIlluminationSettings& other, float weight) +{ + const bool isHalf = weight >= 0.5f; + BLEND_BOOL(Mode); + BLEND_FLOAT(Intensity); + BLEND_FLOAT(TemporalResponse); + BLEND_FLOAT(Distance); + BLEND_COL(FallbackIrradiance); +} + void BloomSettings::BlendWith(BloomSettings& other, float weight) { const bool isHalf = weight >= 0.5f; @@ -209,6 +220,7 @@ void PostFxMaterialsSettings::BlendWith(PostFxMaterialsSettings& other, float we void PostProcessSettings::BlendWith(PostProcessSettings& other, float weight) { AmbientOcclusion.BlendWith(other.AmbientOcclusion, weight); + GlobalIllumination.BlendWith(other.GlobalIllumination, weight); Bloom.BlendWith(other.Bloom, weight); ToneMapping.BlendWith(other.ToneMapping, weight); ColorGrading.BlendWith(other.ColorGrading, weight); @@ -251,6 +263,9 @@ void PostProcessSettings::Serialize(SerializeStream& stream, const void* otherOb stream.JKEY("AO"); stream.Object(&AmbientOcclusion, other ? &other->AmbientOcclusion : nullptr); + + stream.JKEY("GI"); + stream.Object(&GlobalIllumination, other ? &other->GlobalIllumination : nullptr); stream.JKEY("Bloom"); stream.Object(&Bloom, other ? &other->Bloom : nullptr); @@ -289,6 +304,7 @@ void PostProcessSettings::Serialize(SerializeStream& stream, const void* otherOb void PostProcessSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { AmbientOcclusion.DeserializeIfExists(stream, "AO", modifier); + GlobalIllumination.DeserializeIfExists(stream, "GI", modifier); Bloom.DeserializeIfExists(stream, "Bloom", modifier); ToneMapping.DeserializeIfExists(stream, "ToneMapping", modifier); ColorGrading.DeserializeIfExists(stream, "ColorGrading", modifier); diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index f1c9a353f..4afa62835 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -9,6 +9,27 @@ #include "Engine/Content/Assets/Texture.h" #include "Engine/Content/Assets/MaterialBase.h" +/// +/// Global Illumination effect rendering modes. +/// +API_ENUM() enum class GlobalIlluminationMode +{ + /// + /// Disabled GI effect. + /// + None = 0, + + /// + /// Dynamic Diffuse Global Illumination algorithm with scrolling probes volume (with cascades). Uses software raytracing - requires Global SDF and Global Surface Atlas. + /// + DDGI = 1, + + /// + /// The custom GI algorithm - plugged-in externally. + /// + Custom = 2, +}; + /// /// Tone mapping effect rendering modes. /// @@ -183,8 +204,8 @@ API_ENUM(Attributes="Flags") enum class AmbientOcclusionSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API AmbientOcclusionSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings); typedef AmbientOcclusionSettingsOverride Override; /// @@ -230,7 +251,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings); float FadeDistance = 500.0f; public: - /// /// Blends the settings using given weight. /// @@ -239,6 +259,101 @@ public: void BlendWith(AmbientOcclusionSettings& other, float weight); }; +/// +/// The structure members override flags. +/// +API_ENUM(Attributes="Flags") enum class GlobalIlluminationSettingsOverride : int32 +{ + /// + /// None properties. + /// + None = 0, + + /// + /// Overrides property. + /// + Mode = 1 << 0, + + /// + /// Overrides property. + /// + Intensity = 1 << 1, + + /// + /// Overrides property. + /// + TemporalResponse = 1 << 2, + + /// + /// Overrides property. + /// + Distance = 1 << 3, + + /// + /// Overrides property. + /// + FallbackIrradiance = 1 << 4, + + /// + /// All properties. + /// + All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance, +}; + +/// +/// Contains settings for Global Illumination effect rendering. +/// +API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable +{ + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(GlobalIlluminationSettings); + typedef GlobalIlluminationSettingsOverride Override; + + /// + /// The flags for overriden properties. + /// + API_FIELD(Attributes="HideInEditor") + GlobalIlluminationSettingsOverride OverrideFlags = Override::None; + + /// + /// The Global Illumination mode to use. + /// + API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Mode)") + GlobalIlluminationMode Mode = GlobalIlluminationMode::None; + + /// + /// Global Illumination indirect lighting intensity scale. Can be used to boost or reduce GI effect. + /// + API_FIELD(Attributes="EditorOrder(10), Limit(0, 10, 0.01f), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Intensity)") + float Intensity = 1.0f; + + /// + /// Defines how quickly GI blends between the the current frame and the history buffer. Lower values update GI faster, but with more jittering and noise. If the camera in your game doesn't move much, we recommend values closer to 1. + /// + API_FIELD(Attributes="EditorOrder(20), Limit(0, 1), PostProcessSetting((int)GlobalIlluminationSettingsOverride.TemporalResponse)") + float TemporalResponse = 0.8f; + + /// + /// Draw distance of the Global Illumination effect. Scene outside the range will use fallback irradiance. + /// + API_FIELD(Attributes="EditorOrder(30), Limit(1000), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Distance)") + float Distance = 20000.0f; + + /// + /// The irradiance lighting outside the GI range used as a fallback to prevent pure-black scene outside the Global Illumination range. + /// + API_FIELD(Attributes="EditorOrder(40), PostProcessSetting((int)GlobalIlluminationSettingsOverride.FallbackIrradiance)") + Color FallbackIrradiance = Color::Black; + +public: + /// + /// Blends the settings using given weight. + /// + /// The other settings. + /// The blend weight. + void BlendWith(GlobalIlluminationSettings& other, float weight); +}; + /// /// The structure members override flags. /// @@ -285,8 +400,8 @@ API_ENUM(Attributes="Flags") enum class BloomSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API BloomSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(BloomSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(BloomSettings); typedef BloomSettingsOverride Override; /// @@ -326,7 +441,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(BloomSettings); float Limit = 10.0f; public: - /// /// Blends the settings using given weight. /// @@ -371,8 +485,8 @@ API_ENUM(Attributes="Flags") enum class ToneMappingSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API ToneMappingSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(ToneMappingSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(ToneMappingSettings); typedef ToneMappingSettingsOverride Override; /// @@ -400,7 +514,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ToneMappingSettings); ToneMappingMode Mode = ToneMappingMode::ACES; public: - /// /// Blends the settings using given weight. /// @@ -550,8 +663,8 @@ API_ENUM(Attributes="Flags") enum class ColorGradingSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(ColorGradingSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(ColorGradingSettings); typedef ColorGradingSettingsOverride Override; /// @@ -717,7 +830,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ColorGradingSettings); float LutWeight = 1.0f; public: - /// /// Blends the settings using given weight. /// @@ -792,8 +904,8 @@ API_ENUM(Attributes="Flags") enum class EyeAdaptationSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(EyeAdaptationSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(EyeAdaptationSettings); typedef EyeAdaptationSettingsOverride Override; /// @@ -857,7 +969,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(EyeAdaptationSettings); float HistogramHighPercent = 98.0f; public: - /// /// Blends the settings using given weight. /// @@ -927,8 +1038,8 @@ API_ENUM(Attributes="Flags") enum class CameraArtifactsSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(CameraArtifactsSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(CameraArtifactsSettings); typedef CameraArtifactsSettingsOverride Override; /// @@ -986,7 +1097,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(CameraArtifactsSettings); Color ScreenFadeColor = Color::Transparent; public: - /// /// Blends the settings using given weight. /// @@ -1076,8 +1186,8 @@ API_ENUM(Attributes="Flags") enum class LensFlaresSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(LensFlaresSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(LensFlaresSettings); typedef LensFlaresSettingsOverride Override; /// @@ -1159,7 +1269,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(LensFlaresSettings); AssetReference LensStar; public: - /// /// Blends the settings using given weight. /// @@ -1269,8 +1378,8 @@ API_ENUM(Attributes="Flags") enum class DepthOfFieldSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(DepthOfFieldSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(DepthOfFieldSettings); typedef DepthOfFieldSettingsOverride Override; /// @@ -1376,7 +1485,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DepthOfFieldSettings); float BokehDepthCutoff = 1.5f; public: - /// /// Blends the settings using given weight. /// @@ -1426,8 +1534,8 @@ API_ENUM(Attributes="Flags") enum class MotionBlurSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(MotionBlurSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(MotionBlurSettings); typedef MotionBlurSettingsOverride Override; /// @@ -1461,7 +1569,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(MotionBlurSettings); ResolutionMode MotionVectorsResolution = ResolutionMode::Half; public: - /// /// Blends the settings using given weight. /// @@ -1566,8 +1673,8 @@ API_ENUM(Attributes="Flags") enum class ScreenSpaceReflectionsSettingsOverride : /// API_STRUCT() struct FLAXENGINE_API ScreenSpaceReflectionsSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(ScreenSpaceReflectionsSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(ScreenSpaceReflectionsSettings); typedef ScreenSpaceReflectionsSettingsOverride Override; /// @@ -1667,7 +1774,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ScreenSpaceReflectionsSettings); float TemporalResponse = 0.8f; public: - /// /// Blends the settings using given weight. /// @@ -1722,8 +1828,8 @@ API_ENUM(Attributes="Flags") enum class AntiAliasingSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(AntiAliasingSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(AntiAliasingSettings); typedef AntiAliasingSettingsOverride Override; /// @@ -1763,7 +1869,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AntiAliasingSettings); float TAA_MotionBlending = 0.4f; public: - /// /// Blends the settings using given weight. /// @@ -1777,8 +1882,8 @@ public: /// API_STRUCT() struct FLAXENGINE_API PostFxMaterialsSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostFxMaterialsSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostFxMaterialsSettings); /// /// The post-process materials collection for rendering (fixed capacity). @@ -1787,7 +1892,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostFxMaterialsSettings); Array, FixedAllocation> Materials; public: - /// /// Blends the settings using given weight. /// @@ -1801,7 +1905,7 @@ public: /// API_STRUCT() struct FLAXENGINE_API PostProcessSettings : ISerializable { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings); /// /// The ambient occlusion effect settings. @@ -1809,6 +1913,12 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings); API_FIELD() AmbientOcclusionSettings AmbientOcclusion; + /// + /// The global illumination effect settings. + /// + API_FIELD() + GlobalIlluminationSettings GlobalIllumination; + /// /// The bloom effect settings. /// @@ -1876,7 +1986,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings); PostFxMaterialsSettings PostFxMaterials; public: - /// /// Blends the settings using given weight. /// @@ -1891,13 +2000,13 @@ public: bool HasContentLoaded() const; public: - // [ISerializable] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; DECLARE_ENUM_OPERATORS(AmbientOcclusionSettingsOverride); +DECLARE_ENUM_OPERATORS(GlobalIlluminationSettingsOverride); DECLARE_ENUM_OPERATORS(BloomSettingsOverride); DECLARE_ENUM_OPERATORS(ToneMappingSettingsOverride); DECLARE_ENUM_OPERATORS(ColorGradingSettingsOverride); diff --git a/Source/Engine/Level/Actors/PostFxVolume.cpp b/Source/Engine/Level/Actors/PostFxVolume.cpp index c66d3fc45..52abe59e5 100644 --- a/Source/Engine/Level/Actors/PostFxVolume.cpp +++ b/Source/Engine/Level/Actors/PostFxVolume.cpp @@ -43,6 +43,7 @@ void PostFxVolume::Collect(RenderContext& renderContext) void PostFxVolume::Blend(PostProcessSettings& other, float weight) { other.AmbientOcclusion.BlendWith(AmbientOcclusion, weight); + other.GlobalIllumination.BlendWith(GlobalIllumination, weight); other.Bloom.BlendWith(Bloom, weight); other.ToneMapping.BlendWith(ToneMapping, weight); other.ColorGrading.BlendWith(ColorGrading, weight); @@ -118,6 +119,9 @@ void PostFxVolume::Serialize(SerializeStream& stream, const void* otherObj) stream.JKEY("AO"); stream.Object(&AmbientOcclusion, other ? &other->AmbientOcclusion : nullptr); + stream.JKEY("GI"); + stream.Object(&GlobalIllumination, other ? &other->GlobalIllumination : nullptr); + stream.JKEY("Bloom"); stream.Object(&Bloom, other ? &other->Bloom : nullptr); @@ -169,6 +173,7 @@ void PostFxVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* mo { auto& settingsStream = settingsMember->value; AmbientOcclusion.DeserializeIfExists(settingsStream, "AO", modifier); + GlobalIllumination.DeserializeIfExists(settingsStream, "GI", modifier); Bloom.DeserializeIfExists(settingsStream, "Bloom", modifier); ToneMapping.DeserializeIfExists(settingsStream, "ToneMapping", modifier); ColorGrading.DeserializeIfExists(settingsStream, "ColorGrading", modifier); diff --git a/Source/Engine/Level/Actors/PostFxVolume.h b/Source/Engine/Level/Actors/PostFxVolume.h index 2b5d0b9f6..b7764ce3b 100644 --- a/Source/Engine/Level/Actors/PostFxVolume.h +++ b/Source/Engine/Level/Actors/PostFxVolume.h @@ -27,6 +27,12 @@ public: API_FIELD(Attributes="EditorDisplay(\"Ambient Occlusion\"), EditorOrder(100)") AmbientOcclusionSettings AmbientOcclusion; + /// + /// The Global Illumination effect settings. + /// + API_FIELD(Attributes="EditorDisplay(\"Global Illumination\"), EditorOrder(150)") + GlobalIlluminationSettings GlobalIllumination; + /// /// The bloom effect settings. /// diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 0e2a9ce7a..7bf9a7cfc 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -14,6 +14,7 @@ #include "Engine/Debug/DebugDraw.h" #include "Engine/Engine/Time.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" @@ -45,10 +46,9 @@ PACK_STRUCT(struct Data0 GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; GBufferData GBuffer; + Vector2 Padding0; float ResetBlend; float TemporalTime; - float IndirectLightingIntensity; - float Padding0; }); PACK_STRUCT(struct Data1 @@ -266,22 +266,52 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, ddgiData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Dynamic Diffuse Global Illumination"); - // TODO: configurable via graphics settings - const Quality quality = Quality::Ultra; + // Setup options + auto& settings = renderContext.List->Settings.GlobalIllumination; + // TODO: implement GI Quality to affect cascades update rate, probes spacing and rays count per probe + const float probesSpacing = 100.0f; // GI probes placement spacing nearby camera (for closest cascade; gets automatically reduced for further cascades) + switch (Graphics::GIQuality) + { + case Quality::Low: + break; + case Quality::Medium: + break; + case Quality::High: + break; + case Quality::Ultra: + default: + break; + } bool debugProbes = false; // TODO: add debug option to draw probes locations -> in Graphics window - Editor-only - // TODO: configurable via postFx settings (maybe use Global SDF distance?) - const float indirectLightingIntensity = 1.0f; - const float probeHistoryWeight = 0.8f; - const int32 cascadesCount = 4; // in range 1-4 - // TODO: use GI.Distance as a easier to adjust total distance and automatically calculate distanceExtent from it - const float distance = 20000.0f; // GI distance around the view (in each direction) + const float indirectLightingIntensity = settings.Intensity; + const float probeHistoryWeight = Math::Clamp(settings.TemporalResponse, 0.0f, 0.98f); + const float distance = settings.Distance; + const Color fallbackIrradiance = settings.FallbackIrradiance; + const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality + + // Automatically calculate amount of cascades to cover the GI distance at the current probes spacing + const int32 idealProbesCount = 20; // Ideal amount of probes per-cascade to try to fit in order to cover whole distance + int32 cascadesCount = 1; + float idealDistance = idealProbesCount * probesSpacing; + while (cascadesCount < 4 && idealDistance < distance) + { + idealDistance *= 2; + cascadesCount++; + } + + // Calculate the probes count based on the amount of cascades and the distance to cover const float cascadesDistanceScales[] = { 1.0f, 3.0f, 6.0f, 10.0f }; // Scales each cascade further away from the camera origin const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; const float verticalRangeScale = 0.8f; // Scales the probes volume size at Y axis (horizontal aspect ratio makes the DDGI use less probes vertically to cover whole screen) - const float probesSpacing = 200.0f; // GI probes placement spacing nearby camera (for closest cascade; gets automatically reduced for further cascades) - const Color fallbackIrradiance = Color::Black; // Irradiance lighting outside the DDGI range used as a fallback to prevent pure-black scene outside the GI range - const Int3 probesCounts(Vector3::Ceil(Vector3(distanceExtent, distanceExtent * verticalRangeScale, distanceExtent) / probesSpacing)); - const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality + Int3 probesCounts(Vector3::Ceil(Vector3(distanceExtent, distanceExtent * verticalRangeScale, distanceExtent) / probesSpacing)); + const int32 maxProbeSize = Math::Max(DDGI_PROBE_RESOLUTION_IRRADIANCE, DDGI_PROBE_RESOLUTION_DISTANCE) + 2; + const int32 maxTextureSize = Math::Min(GPUDevice::Instance->Limits.MaximumTexture2DSize, GPU_MAX_TEXTURE_SIZE); + while (probesCounts.X * probesCounts.Y * maxProbeSize > maxTextureSize + || probesCounts.Z * cascadesCount * maxProbeSize > maxTextureSize) + { + // Decrease quality to ensure the probes texture won't overflow + probesCounts -= 1; + } // Initialize cascades float probesSpacings[4]; @@ -417,6 +447,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, ddgiData.Result.Constants.RaysCount = probeRaysCount; ddgiData.Result.Constants.ProbeHistoryWeight = probeHistoryWeight; ddgiData.Result.Constants.IrradianceGamma = 5.0f; + ddgiData.Result.Constants.IndirectLightingIntensity = indirectLightingIntensity; ddgiData.Result.Constants.FallbackIrradiance = fallbackIrradiance.ToVector3() * fallbackIrradiance.A; ddgiData.Result.ProbesState = ddgiData.ProbesState->View(); ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->View(); @@ -446,7 +477,6 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, { data.TemporalTime = 0.0f; } - data.IndirectLightingIntensity = indirectLightingIntensity; GBufferPass::SetInputs(renderContext.View, data.GBuffer); context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h index 35158a7bc..09e6b7bd6 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h @@ -23,12 +23,12 @@ public: float IrradianceGamma; float ProbeHistoryWeight; float RayMaxDistance; - float Padding0; + float IndirectLightingIntensity; Vector4 RaysRotation; Vector3 ViewDir; uint32 RaysCount; Vector3 FallbackIrradiance; - float Padding1; + float Padding0; }); // Binding data for the GPU. diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 446981076..9ede2ca62 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -343,11 +343,12 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global Surface Atlas"); + // Setup options // TODO: configurable via graphics settings const int32 resolution = 2048; const float resolutionInv = 1.0f / resolution; - // TODO: configurable via postFx settings (use GI distance) - const float distance = 20000.0f; + auto& giSettings = renderContext.List->Settings.GlobalIllumination; + const float distance = giSettings.Distance; // Initialize buffers bool noCache = surfaceAtlasData.Resolution != resolution; @@ -847,32 +848,36 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co } if (renderContext.View.Flags & ViewFlags::GI) { - // TODO: add option to PostFx Volume for realtime GI type (None, DDGI) - DynamicDiffuseGlobalIlluminationPass::BindingData bindingDataDDGI; - if (!DynamicDiffuseGlobalIlluminationPass::Instance()->Get(renderContext.Buffers, bindingDataDDGI)) + // Draw draw indirect light from Global Illumination + switch (renderContext.List->Settings.GlobalIllumination.Mode) { - // Collect tiles to shade - _vertexBuffer->Clear(); - for (const auto& e : surfaceAtlasData.Objects) + case GlobalIlluminationMode::DDGI: + { + DynamicDiffuseGlobalIlluminationPass::BindingData bindingDataDDGI; + if (!DynamicDiffuseGlobalIlluminationPass::Instance()->Get(renderContext.Buffers, bindingDataDDGI)) { - const auto& object = e.Value; - for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) { - auto* tile = object.Tiles[tileIndex]; - if (!tile) - continue; - VB_WRITE_TILE(tile); + const auto& object = e.Value; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + VB_WRITE_TILE(tile); + } } + data.DDGI = bindingDataDDGI.Constants; + context->BindSR(5, bindingDataDDGI.ProbesState); + context->BindSR(6, bindingDataDDGI.ProbesDistance); + context->BindSR(7, bindingDataDDGI.ProbesIrradiance); + context->UpdateCB(_cb0, &data); + context->SetState(_psIndirectLighting); + VB_DRAW(); } - - // Draw draw indirect light - data.DDGI = bindingDataDDGI.Constants; - context->BindSR(5, bindingDataDDGI.ProbesState); - context->BindSR(6, bindingDataDDGI.ProbesDistance); - context->BindSR(7, bindingDataDDGI.ProbesIrradiance); - context->UpdateCB(_cb0, &data); - context->SetState(_psIndirectLighting); - VB_DRAW(); + break; + } } } @@ -888,11 +893,15 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output) { - // Render all dependant effects + // Render all dependant effects before if (renderContext.View.Flags & ViewFlags::GI) { - // TODO: add option to PostFx Volume for realtime GI type (None, DDGI) - DynamicDiffuseGlobalIlluminationPass::Instance()->Render(renderContext, context, nullptr); + switch (renderContext.List->Settings.GlobalIllumination.Mode) + { + case GlobalIlluminationMode::DDGI: + DynamicDiffuseGlobalIlluminationPass::Instance()->Render(renderContext, context, nullptr); + break; + } } GlobalSignDistanceFieldPass::BindingData bindingDataSDF; BindingData bindingData; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 3f566c45b..b80fc7a0a 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -416,7 +416,8 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex break; } const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); - const float distance = true ? 20000.0f : 16000.0f; // TODO: switch based if using GI, then use GI range + auto& giSettings = renderContext.List->Settings.GlobalIllumination; + const float distance = giSettings.Mode == GlobalIlluminationMode::DDGI ? giSettings.Distance : 15000.0f; const float cascadesDistanceScales[] = { 1.0f, 2.5f, 5.0f, 10.0f }; const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index ea3d127f0..e59694ecd 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -401,8 +401,12 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) LightPass::Instance()->RenderLight(renderContext, *lightBuffer); if (renderContext.View.Flags & ViewFlags::GI) { - // TODO: add option to PostFx Volume for realtime GI type (None, DDGI) - DynamicDiffuseGlobalIlluminationPass::Instance()->Render(renderContext, context, *lightBuffer); + switch (renderContext.List->Settings.GlobalIllumination.Mode) + { + case GlobalIlluminationMode::DDGI: + DynamicDiffuseGlobalIlluminationPass::Instance()->Render(renderContext, context, *lightBuffer); + break; + } } if (renderContext.View.Mode == ViewMode::LightBuffer) { diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index 7aaf6527b..c40e414f3 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -30,12 +30,12 @@ struct DDGIData float IrradianceGamma; float ProbeHistoryWeight; float RayMaxDistance; - float Padding0; + float IndirectLightingIntensity; float4 RaysRotation; float3 ViewDir; uint RaysCount; float3 FallbackIrradiance; - float Padding1; + float Padding0; }; uint GetDDGIProbeIndex(DDGIData data, uint3 probeCoords) diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index 70cb92834..cb3aaaf04 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -27,10 +27,9 @@ DDGIData DDGI; GlobalSDFData GlobalSDF; GlobalSurfaceAtlasData GlobalSurfaceAtlas; GBufferData GBuffer; +float2 Padding0; float ResetBlend; float TemporalTime; -float IndirectLightingIntensity; -float2 Padding0; META_CB_END META_CB_BEGIN(1, Data1) @@ -343,7 +342,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ if (ResetBlend || dot(previous, previous) == 0) historyWeight = 0.0f; #if DDGI_PROBE_UPDATE_MODE == 0 - result *= IndirectLightingIntensity; + result *= DDGI.IndirectLightingIntensity; #if DDGI_SRGB_BLENDING result.rgb = pow(result.rgb, 1.0f / DDGI.IrradianceGamma); #endif diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index 30f455675..7ae54ed83 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -120,6 +120,7 @@ float4 PS_Lighting(AtlasVertexOutput input) : SV_Target // Sample irradiance float bias = 1.0f; float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesState, ProbesDistance, ProbesIrradiance, gBuffer.WorldPos, gBuffer.Normal, bias); + irradiance /= DDGI.IndirectLightingIntensity; //irradiance = 0; // Calculate lighting