From 401bfbd52d0160b8cc6784028f3d0b272a6d6942 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 14 Jul 2026 23:09:34 +0200 Subject: [PATCH] Add new DDGI Reflections rendering mode for fully dynamic indirect specular --- .../DebugMaterials/DDGIDebugProbes.flax | 4 +- Content/Shaders/GI/DDGI.flax | 4 +- .../Engine/Graphics/PostProcessSettings.cpp | 1 + Source/Engine/Graphics/PostProcessSettings.h | 31 +- .../GI/DynamicDiffuseGlobalIllumination.cpp | 148 ++++++-- .../GI/DynamicDiffuseGlobalIllumination.h | 13 + Source/Engine/Renderer/ReflectionsPass.cpp | 70 ++-- Source/Engine/Renderer/Renderer.cpp | 2 +- Source/Shaders/GI/DDGI.hlsl | 349 ++++++++++++------ Source/Shaders/GI/DDGI.shader | 90 +++-- 10 files changed, 507 insertions(+), 205 deletions(-) diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax index 4633c6dfb..6b2bd2b42 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:b90d90d83ebb074bafeac4b8977e283db665220a1c09456e4ea4afb0c103d98c -size 40808 +oid sha256:919e82bfdb9e62c29cf996739126733a95c5e78d5cf8a673c16e4e6ee7480264 +size 40107 diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax index 1656e24fe..eecc8f066 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:88b1ec9f43bc086bda9cbf1f12f17da230b4495a9f916369e98e17b9479d855f -size 41302 +oid sha256:70c90b5ea9ecfa243ff57caa8c3101ce2ff08758a0677910b07b610d5ff2e67a +size 42473 diff --git a/Source/Engine/Graphics/PostProcessSettings.cpp b/Source/Engine/Graphics/PostProcessSettings.cpp index 6205fdce7..688871bb3 100644 --- a/Source/Engine/Graphics/PostProcessSettings.cpp +++ b/Source/Engine/Graphics/PostProcessSettings.cpp @@ -36,6 +36,7 @@ void GlobalIlluminationSettings::BlendWith(GlobalIlluminationSettings& other, fl BLEND_FLOAT(IndirectShadowsStrength); BLEND_COL(FallbackIrradiance); BLEND_ENUM(IndirectResolution); + BLEND_ENUM(Reflections); } void BloomSettings::BlendWith(BloomSettings& other, float weight) diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index 1a8ecf989..3fe51085a 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -23,7 +23,7 @@ API_ENUM() enum class GlobalIlluminationMode None = 0, /// - /// Dynamic Diffuse Global Illumination algorithm with scrolling probes volume (with cascades). Uses software raytracing - requires Global SDF and Global Surface Atlas. + /// Dynamic Diffuse Global Illumination algorithm with scrolling probes volume (and cascades). Uses software raytracing - requires Global SDF and Global Surface Atlas. /// DDGI = 1, @@ -161,6 +161,22 @@ API_ENUM() enum class ResolutionMode : int32 Half = 2, }; +/// +/// The reflections modes. +/// +API_ENUM() enum class ReflectionsMode : int32 +{ + /// + /// Reflections from Environment Probes placed on the scene. + /// + EnvironmentProbes = 0, + + /// + /// Reflections from Dynamic Diffuse Global Illumination algorithm with scrolling probes volume (and cascades). Uses software raytracing - requires Global SDF and Global Surface Atlas. + /// + DDGI = 1, +}; + /// /// The screen space reflections modes. /// @@ -344,10 +360,15 @@ API_ENUM(Attributes="Flags") enum class GlobalIlluminationSettingsOverride : int /// IndirectResolution = 1 << 7, + /// + /// Overrides property. + /// + Reflections = 1 << 8, + /// /// All properties. /// - All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance | BounceIntensity | IndirectShadowsStrength | IndirectResolution, + All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance | BounceIntensity | IndirectShadowsStrength | IndirectResolution | Reflections, }; /// @@ -413,6 +434,12 @@ API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable API_FIELD(Attributes="EditorOrder(50), PostProcessSetting((int)GlobalIlluminationSettingsOverride.IndirectResolution)") ResolutionMode IndirectResolution = ResolutionMode::Full; + /// + /// The indirect specular reflections rendering mode to use. + /// + API_FIELD(Attributes = "EditorOrder(100), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Reflections)") + ReflectionsMode Reflections = ReflectionsMode::EnvironmentProbes; + public: /// /// Blends the settings using given weight. diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index 49d25e23c..b664e3993 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -41,6 +41,7 @@ #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_LIMIT 256 // 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_RADIANCE 14 // Resolution (in texels) for probe radiance 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_CLASSIFY_GROUP_SIZE 32 #define DDGI_PROBE_EMPTY_AREA_DENSITY 8 // Spacing (in probe grid) between fallback probes placed into empty areas to provide valid GI for nearby dynamic objects or transparency @@ -127,6 +128,7 @@ public: GPUTexture* ProbesTrace = nullptr; // Probes ray tracing: (RGB: hit radiance, A: hit distance) GPUTexture* ProbesData = nullptr; // Probes data: (RGB: probe-space offset, A: state/data) GPUTexture* ProbesIrradiance = nullptr; // Probes irradiance (RGB: sRGB color) + GPUTexture* ProbesRadiance = nullptr; // Probes radiance (RGB: HDR color) GPUTexture* ProbesDistance = nullptr; // Probes distance (R: mean distance, G: mean distance^2) GPUBuffer* ActiveProbes = nullptr; // List with indices of the active probes (built during probes classification to use indirect dispatches for probes updating), counter at 0 GPUBuffer* UpdateProbesInitArgs = nullptr; // Indirect dispatch buffer for active-only probes updating (trace+blend) @@ -147,7 +149,13 @@ public: RenderTargetPool::Release(ProbesTrace); RenderTargetPool::Release(ProbesData); RenderTargetPool::Release(ProbesIrradiance); + RenderTargetPool::Release(ProbesRadiance); RenderTargetPool::Release(ProbesDistance); + ProbesTrace = nullptr; + ProbesData = nullptr; + ProbesIrradiance = nullptr; + ProbesRadiance = nullptr; + ProbesDistance = nullptr; SAFE_DELETE_GPU_RESOURCE(ActiveProbes); SAFE_DELETE_GPU_RESOURCE(UpdateProbesInitArgs); #if DDGI_DEBUG_STATS @@ -290,6 +298,7 @@ bool DynamicDiffuseGlobalIlluminationPass::setupResources() _csTraceRays[3] = shader->GetCS("CS_TraceRays", 3); _csUpdateProbesIrradiance = shader->GetCS("CS_UpdateProbes", 0); _csUpdateProbesDistance = shader->GetCS("CS_UpdateProbes", 1); + _csUpdateProbesRadiance = shader->GetCS("CS_UpdateProbes", 2); auto device = GPUDevice::Instance; auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; if (!_psIndirectLighting[0]) @@ -311,6 +320,15 @@ bool DynamicDiffuseGlobalIlluminationPass::setupResources() if (_psIndirectLighting[3]->Init(psDesc)) return true; } + if (!_psSpecularLighting) + { + _psSpecularLighting = device->CreatePipelineState(); + psDesc.DepthEnable = psDesc.DepthBoundsEnable = false; + psDesc.BlendMode = BlendingMode::Opaque; + psDesc.PS = shader->GetPS("PS_SpecularLighting"); + if (_psSpecularLighting->Init(psDesc)) + return true; + } return false; } @@ -329,7 +347,9 @@ void DynamicDiffuseGlobalIlluminationPass::OnShaderReloading(Asset* obj) _csTraceRays[3] = nullptr; _csUpdateProbesIrradiance = nullptr; _csUpdateProbesDistance = nullptr; - SAFE_DELETE_GPU_RESOURCES(_psIndirectLighting) + _csUpdateProbesRadiance = nullptr; + SAFE_DELETE_GPU_RESOURCES(_psIndirectLighting); + SAFE_DELETE_GPU_RESOURCE(_psSpecularLighting); invalidateResources(); } @@ -343,7 +363,8 @@ void DynamicDiffuseGlobalIlluminationPass::Dispose() _cb0 = nullptr; _cb1 = nullptr; _shader = nullptr; - SAFE_DELETE_GPU_RESOURCES(_psIndirectLighting) + SAFE_DELETE_GPU_RESOURCES(_psIndirectLighting); + SAFE_DELETE_GPU_RESOURCE(_psSpecularLighting); #if GPU_ENABLE_DEVELOPMENT _debugModel = nullptr; _debugMaterial = nullptr; @@ -371,6 +392,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont if (GlobalSurfaceAtlasPass::Instance()->Render(renderContext, context, bindingDataSurfaceAtlas)) return true; GPUTextureView* skybox = GBufferPass::Instance()->RenderSkybox(renderContext, context); + PROFILE_GPU_CPU("Dynamic Diffuse Global Illumination"); // Setup options auto& settings = renderContext.List->Settings.GlobalIllumination; @@ -413,7 +435,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont 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) Int3 probesCounts(Float3::Ceil(Float3(distanceExtent, distanceExtent * verticalRangeScale, distanceExtent) / probesSpacing)); - const int32 maxProbeSize = Math::Max(DDGI_PROBE_RESOLUTION_IRRADIANCE, DDGI_PROBE_RESOLUTION_DISTANCE) + 2; + const int32 maxProbeSize = Math::Max(DDGI_PROBE_RESOLUTION_IRRADIANCE, DDGI_PROBE_RESOLUTION_RADIANCE, 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) @@ -458,7 +480,8 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont int32 probesCountTotalX = probesCountCascadeX; int32 probesCountTotalY = probesCountCascadeY * cascadesCount; bool clear = false; - if (ddgiData.CascadesCount != cascadesCount || Math::NotNearEqual(ddgiData.Cascades[0].ProbesSpacing, probesSpacing) || ddgiData.ProbeCounts != probesCounts || ddgiData.ProbeRaysCount != probeRaysCount) + bool withRadiance = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::Reflections) && renderContext.List->Settings.GlobalIllumination.Reflections == ReflectionsMode::DDGI; + if (ddgiData.CascadesCount != cascadesCount || Math::NotNearEqual(ddgiData.Cascades[0].ProbesSpacing, probesSpacing) || ddgiData.ProbeCounts != probesCounts || ddgiData.ProbeRaysCount != probeRaysCount || withRadiance != (ddgiData.ProbesRadiance != nullptr)) { PROFILE_CPU_NAMED("Init"); ddgiData.Release(); @@ -483,6 +506,10 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont INIT_TEXTURE(ProbesData, PixelFormat::R8G8B8A8_SNorm, probesCountTotalX, probesCountTotalY); // TODO: add BC6H compression to probes data (https://github.com/knarkowicz/GPURealTimeBC6H) INIT_TEXTURE(ProbesIrradiance, PixelFormat::R11G11B10_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2)); + if (withRadiance) + { + INIT_TEXTURE(ProbesRadiance, PixelFormat::R11G11B10_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_RADIANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_RADIANCE + 2)); + } INIT_TEXTURE(ProbesDistance, PixelFormat::R16G16_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2)); #if DDGI_DEBUG_INSTABILITY INIT_TEXTURE(ProbesInstability, PixelFormat::R16_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2)); @@ -513,6 +540,8 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont PROFILE_GPU("Clear"); context->ClearUA(ddgiData.ProbesData, Float4::Zero); context->ClearUA(ddgiData.ProbesIrradiance, Float4::Zero); + if (ddgiData.ProbesRadiance) + context->ClearUA(ddgiData.ProbesRadiance, Float4::Zero); context->ClearUA(ddgiData.ProbesDistance, Float4::Zero); #if DDGI_DEBUG_INSTABILITY context->ClearUA(ddgiData.ProbesInstability, Float4::Zero); @@ -610,6 +639,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont ddgiData.Result.ProbesData = ddgiData.ProbesData->View(); ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->View(); ddgiData.Result.ProbesIrradiance = ddgiData.ProbesIrradiance->View(); + ddgiData.Result.ProbesRadiance = ddgiData.ProbesRadiance ? ddgiData.ProbesRadiance->View() : nullptr; Data0 data; @@ -643,7 +673,6 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont // Update probes { - PROFILE_GPU_CPU_NAMED("Probes Update"); uint32 threadGroupsX; #if DDGI_DEBUG_STATS uint32 zero[4] = {}; @@ -739,18 +768,27 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont PROFILE_GPU_CPU_NAMED("Update Probes"); GPUComputePass pass(context); + // Batch barriers to avoid them mid-pass + pass.Transition(ddgiData.ProbesData, GPUResourceAccess::UnorderedAccess); + pass.Transition(ddgiData.ProbesIrradiance, GPUResourceAccess::UnorderedAccess); + if (ddgiData.ProbesRadiance) + pass.Transition(ddgiData.ProbesRadiance, GPUResourceAccess::UnorderedAccess); + // Distance - context->BindSR(0, ddgiData.Result.ProbesData); context->BindSR(1, ddgiData.ProbesTrace->View()); context->BindSR(2, ddgiData.ActiveProbes->View()); context->BindUA(0, ddgiData.Result.ProbesDistance); + context->BindUA(1, ddgiData.Result.ProbesData); context->DispatchIndirect(_csUpdateProbesDistance, ddgiData.UpdateProbesInitArgs, arg); - context->ResetUA(); - context->ResetSR(); + + // Radiance + if (ddgiData.ProbesRadiance) + { + context->BindUA(0, ddgiData.Result.ProbesRadiance); + context->DispatchIndirect(_csUpdateProbesRadiance, ddgiData.UpdateProbesInitArgs, arg); + } // Irradiance - context->BindSR(1, ddgiData.ProbesTrace->View()); - context->BindSR(2, ddgiData.ActiveProbes->View()); context->BindUA(0, ddgiData.Result.ProbesIrradiance); context->BindUA(1, ddgiData.Result.ProbesData); #if DDGI_DEBUG_INSTABILITY @@ -795,14 +833,14 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont return false; } -bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer) +bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, GPUContext* context, DDGICustomBuffer*& ddgiDataPtr, bool& render) { if (checkIfSkipPass()) return true; if (renderContext.List->Scenes.Count() == 0) return true; RenderBuffers* renderBuffers = renderContext.Buffers; - bool render = true; + render = true; if (renderContext.View.IsOfflinePass) { // During offline pass (eg. probes rendering) we can try reuse main game viewport or editor viewport DDGI probes @@ -823,9 +861,9 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, } } auto& ddgiData = *renderBuffers->GetCustomBuffer(TEXT("DDGI")); + ddgiDataPtr = &ddgiData; if (render && ddgiData.LastFrameUsed == Engine::FrameCount) render = false; - PROFILE_GPU_CPU("Dynamic Diffuse Global Illumination"); if (render) { @@ -839,6 +877,17 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, } } + return false; +} + +bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer) +{ + // Get DDGI + DDGICustomBuffer* ddgiData = nullptr; + bool render = false; + if (Render(renderContext, context, ddgiData, render)) + return true; + // Render indirect lighting if (lightBuffer) { @@ -850,8 +899,9 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, if (!render) { Data0 data; - data.DDGI = ddgiData.Result.Constants; - data.TemporalTime = 0.0f; + data.DDGI = ddgiData->Result.Constants; + data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f; + data.FrameIndexMod8 = (int32)(Engine::FrameCount % 8); GBufferPass::SetInputs(renderContext.View, data.GBuffer); context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); @@ -861,9 +911,9 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, context->BindSR(1, renderContext.Buffers->GBuffer1->View()); context->BindSR(2, renderContext.Buffers->GBuffer2->View()); context->BindSR(3, depthBuffer->View()); - context->BindSR(4, ddgiData.Result.ProbesData); - context->BindSR(5, ddgiData.Result.ProbesDistance); - context->BindSR(6, ddgiData.Result.ProbesIrradiance); + context->BindSR(4, ddgiData->Result.ProbesData); + context->BindSR(5, ddgiData->Result.ProbesDistance); + context->BindSR(6, ddgiData->Result.ProbesIrradiance); auto& settings = renderContext.List->Settings.GlobalIllumination; if (settings.IndirectResolution == ResolutionMode::Full || !MultiScaler::Instance()->IsReady()) { @@ -889,6 +939,7 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, context->SetState(_psIndirectLighting[Graphics::GICascadesBlending ? 3 : 2]); context->DrawFullscreenTriangle(); context->ResetRenderTarget(); + // TODO: consider mixing with temporal-filter to reduce flickering (GI is quite smooth so easy to get rid of ghosting) MultiScaler::Instance()->BilateralUpscale(context, Viewport(Float2(renderContext.View.ScreenSize)), temp, lightBuffer, depthBuffer, renderContext.Buffers->GBuffer1, BlendingMode::Add); RenderTargetPool::Release(temp); context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); @@ -924,12 +975,12 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, } constexpr int32 maxProbesPerDrawing = 32 * 1024; int32 probesDrawingStart = 0; - while (probesDrawingStart < ddgiData.ProbesCountTotal) + while (probesDrawingStart < ddgiData->ProbesCountTotal) { debugRenderContext.List = RenderList::GetFromPool(); debugRenderContext.View.Pass = DrawPass::GBuffer; debugRenderContext.View.Prepare(debugRenderContext); - const int32 probesDrawingEnd = Math::Min(probesDrawingStart + maxProbesPerDrawing, ddgiData.ProbesCountTotal); + const int32 probesDrawingEnd = Math::Min(probesDrawingStart + maxProbesPerDrawing, ddgiData->ProbesCountTotal); BatchedDrawCall batchedDrawCall(debugRenderContext.List); batchedDrawCall.DrawCall = drawCall; batchedDrawCall.DrawCall.InstanceCount = probesDrawingEnd - probesDrawingStart; @@ -955,17 +1006,18 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, context->SetRenderTarget(*renderContext.Buffers->DepthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers))); { // Pass DDGI data to the material - _debugMaterial->SetParameterValue(TEXT("ProbesData"), Variant(ddgiData.ProbesData)); + _debugMaterial->SetParameterValue(TEXT("ProbesData"), Variant(ddgiData->ProbesData)); #if DDGI_DEBUG_INSTABILITY - _debugMaterial->SetParameterValue(TEXT("ProbesIrradiance"), Variant(ddgiData.ProbesInstability)); + _debugMaterial->SetParameterValue(TEXT("ProbesIrradiance"), Variant(ddgiData->ProbesInstability)); #else - _debugMaterial->SetParameterValue(TEXT("ProbesIrradiance"), Variant(ddgiData.ProbesIrradiance)); + _debugMaterial->SetParameterValue(TEXT("ProbesIrradiance"), Variant(ddgiData->ProbesIrradiance)); #endif - _debugMaterial->SetParameterValue(TEXT("ProbesDistance"), Variant(ddgiData.ProbesDistance)); + _debugMaterial->SetParameterValue(TEXT("ProbesDistance"), Variant(ddgiData->ProbesDistance)); + _debugMaterial->SetParameterValue(TEXT("ProbesRadiance"), Variant(ddgiData->ProbesRadiance)); auto cb = _debugMaterial->GetShader()->GetCB(3); if (cb) { - context->UpdateCB(cb, &ddgiData.Result.Constants); + context->UpdateCB(cb, &ddgiData->Result.Constants); context->BindCB(3, cb); } } @@ -984,6 +1036,52 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, return false; } +bool DynamicDiffuseGlobalIlluminationPass::RenderReflections(RenderContext& renderContext, GPUContext* context, GPUTextureView* reflectionsBuffer) +{ + // Get DDGI + DDGICustomBuffer* ddgiData = nullptr; + bool render = false; + if (Render(renderContext, context, ddgiData, render)) + return true; + + // Render specular lighting + if (reflectionsBuffer && ddgiData->Result.ProbesRadiance) + { + PROFILE_GPU_CPU_NAMED("Specular Lighting"); + if (!render) + { + Data0 data; + data.DDGI = ddgiData->Result.Constants; + data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f; + data.FrameIndexMod8 = (int32)(Engine::FrameCount % 8); + GBufferPass::SetInputs(renderContext.View, data.GBuffer); + context->UpdateCB(_cb0, &data); + context->BindCB(0, _cb0); + } + 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.ProbesData); + context->BindSR(5, ddgiData->Result.ProbesDistance); + context->BindSR(6, ddgiData->Result.ProbesRadiance); + auto& settings = renderContext.List->Settings.GlobalIllumination; + { + // Full-res + auto rtAction = GPUDrawPassAction::Store; + GPUDrawPass pass(context, ToSpan(&reflectionsBuffer, 1), ToSpan(&rtAction, 1)); + context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); + context->SetRenderTarget(reflectionsBuffer); + context->SetState(_psSpecularLighting); + context->DrawFullscreenTriangle(); + } + context->ResetSR(); + context->ResetRenderTarget(); + } + + return false; +} + void DynamicDiffuseGlobalIlluminationPass::UpdateRegions(const RenderBuffers* buffers, Span regions) { #if !DDGI_DEBUG_FORCE_UPDATE diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h index 973305a96..130af0468 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h @@ -35,6 +35,7 @@ public: GPUTextureView* ProbesData; GPUTextureView* ProbesDistance; GPUTextureView* ProbesIrradiance; + GPUTextureView* ProbesRadiance; }; private: @@ -49,7 +50,9 @@ private: GPUShaderProgramCS* _csTraceRays[4]; GPUShaderProgramCS* _csUpdateProbesIrradiance; GPUShaderProgramCS* _csUpdateProbesDistance; + GPUShaderProgramCS* _csUpdateProbesRadiance; GPUPipelineState* _psIndirectLighting[4] = {}; + GPUPipelineState* _psSpecularLighting = nullptr; #if GPU_ENABLE_DEVELOPMENT AssetReference _debugModel; AssetReference _debugMaterial; @@ -73,6 +76,15 @@ public: /// 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); + /// + /// Renders the DDGI Reflections. + /// + /// The rendering context. + /// The GPU context. + /// The reflections buffer (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 RenderReflections(RenderContext& renderContext, GPUContext* context, GPUTextureView* reflectionsBuffer); + /// /// Forces DDGI update for a specific regions (eg. when object is moved). It will refresh the probes in the given regions on the next GI update. /// @@ -85,6 +97,7 @@ private: uint64 LastFrameShaderReload = 0; void OnShaderReloading(Asset* obj); #endif + bool Render(RenderContext& renderContext, GPUContext* context, class DDGICustomBuffer*& ddgiDataPtr, bool& render); bool RenderInner(RenderContext& renderContext, GPUContext* context, class DDGICustomBuffer& ddgiData); public: diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index f999c095d..7686f4f36 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -4,6 +4,7 @@ #include "GBufferPass.h" #include "RenderList.h" #include "ScreenSpaceReflectionsPass.h" +#include "GI/DynamicDiffuseGlobalIllumination.h" #include "Engine/Core/Collections/Sorting.h" #include "Engine/Content/Content.h" #include "Engine/Core/Math/OrientedBoundingBox.h" @@ -266,47 +267,36 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light { auto device = GPUDevice::Instance; auto context = device->GetMainContext(); - if (checkIfSkipPass()) + bool useReflections = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::Reflections); + bool useSSR = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::SSR) && renderContext.List->Settings.ScreenSpaceReflections.Intensity > ZeroTolerance; + int32 probesCount = renderContext.List->EnvironmentProbes.Count(); + bool renderProbes = probesCount > 0 && renderContext.List->Settings.GlobalIllumination.Reflections == ReflectionsMode::EnvironmentProbes; + bool renderDDGI = renderContext.List->Settings.GlobalIllumination.Reflections == ReflectionsMode::DDGI; + auto shader = _shader->GPU; + auto cb = shader->GetCB(0); + + // Check if no need to render reflection environment + if (!useReflections || !(renderProbes || useSSR || renderDDGI) || checkIfSkipPass()) { // Skip pass (just clear buffer when doing debug preview) if (renderContext.View.Mode == ViewMode::Reflections) context->Clear(lightBuffer, Color::Black); return; } - - // Cache data - auto& view = renderContext.View; - bool useReflections = EnumHasAnyFlags(view.Flags, ViewFlags::Reflections); - bool useSSR = EnumHasAnyFlags(view.Flags, ViewFlags::SSR) && renderContext.List->Settings.ScreenSpaceReflections.Intensity > ZeroTolerance; - int32 probesCount = renderContext.List->EnvironmentProbes.Count(); - bool renderProbes = probesCount > 0; - auto shader = _shader->GPU; - auto cb = shader->GetCB(0); - - // Check if no need to render reflection environment - if (!useReflections || !(renderProbes || useSSR)) - return; PROFILE_GPU_CPU("Reflections"); // Setup data const int32 width = renderContext.Buffers->GetWidth(); const int32 height = renderContext.Buffers->GetHeight(); Data data; - GBufferPass::SetInputs(view, data.GBuffer); + GBufferPass::SetInputs(renderContext.View, data.GBuffer); auto& ssrSettings = renderContext.List->Settings.ScreenSpaceReflections; data.SSRTexelSize = Float2(1.0f / (float)RenderTools::GetResolution(width, ssrSettings.ResolvePassResolution), 1.0f / (float)RenderTools::GetResolution(height, ssrSettings.ResolvePassResolution)); - - // Bind GBuffer inputs auto depthBuffer = renderContext.Buffers->GetReadOnlyDepthBuffer(); - context->BindSR(0, renderContext.Buffers->GBuffer0); - context->BindSR(1, renderContext.Buffers->GBuffer1); - context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, depthBuffer.SRV); auto tempDesc = GPUTextureDescription::New2D(renderContext.Buffers->GetWidth(), renderContext.Buffers->GetHeight(), PixelFormat::R11G11B10_Float); auto reflectionsBuffer = RenderTargetPool::Get(tempDesc); RENDER_TARGET_POOL_SET_NAME(reflectionsBuffer, "Reflections"); - context->Clear(*reflectionsBuffer, Color::Black); // Reflection Probes pass if (!useSSR && renderContext.View.Mode == ViewMode::Reflections) @@ -316,16 +306,22 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light PROFILE_GPU_CPU("Env Probes"); context->SetViewportAndScissors((float)tempDesc.Width, (float)tempDesc.Height); context->SetRenderTarget(depthBuffer.RTV, *reflectionsBuffer); + context->Clear(*reflectionsBuffer, Color::Black); + context->BindSR(0, renderContext.Buffers->GBuffer0); + context->BindSR(1, renderContext.Buffers->GBuffer1); + context->BindSR(2, renderContext.Buffers->GBuffer2); + context->BindSR(3, depthBuffer.SRV); - // Sort probes by the radius + // TODO: sort probes in async during draw calls sorting Sorting::QuickSort(renderContext.List->EnvironmentProbes.Get(), renderContext.List->EnvironmentProbes.Count(), &SortProbes); // Render all env probes auto& sphereMesh = _sphereModel->LODs.Get()[0].Meshes.Get()[0]; auto& boxMesh = _boxModel->LODs.Get()[0].Meshes.Get()[0]; + auto probes = renderContext.List->EnvironmentProbes.Get(); for (int32 i = 0; i < probesCount; i++) { - const RenderEnvironmentProbeData& probe = renderContext.List->EnvironmentProbes.Get()[i]; + const RenderEnvironmentProbeData& probe = probes[i]; // Calculate world*view*projection matrix Matrix world, wvp; @@ -334,15 +330,15 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light if (probe.BoxProjection) { OrientedBoundingBox bounds(probe.Radius, Transform(probe.Position, probe.Orientation, probe.Scale)); - minMaxDepth = RenderTools::GetDepthBounds(view, bounds); + minMaxDepth = RenderTools::GetDepthBounds(renderContext.View, bounds); RenderTools::ComputeBoxModelDrawMatrix(renderContext.View, bounds, world, isViewInside); } else { - minMaxDepth = RenderTools::GetDepthBounds(view, BoundingSphere(probe.Position, probe.Radius)); + minMaxDepth = RenderTools::GetDepthBounds(renderContext.View, BoundingSphere(probe.Position, probe.Radius)); RenderTools::ComputeSphereModelDrawMatrix(renderContext.View, probe.Position, probe.Radius, world, isViewInside); } - Matrix::Multiply(world, view.ViewProjection(), wvp); + Matrix::Multiply(world, renderContext.View.ViewProjection(), wvp); // Setup depth bounds (if device supports it) if (_depthBounds) @@ -369,21 +365,29 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light if (_depthBounds) context->SetDepthBounds(); } + else if (renderDDGI) + { + if (DynamicDiffuseGlobalIlluminationPass::Instance()->RenderReflections(renderContext, context, *reflectionsBuffer)) + context->Clear(*reflectionsBuffer, Color::Black); + } + else + { + context->Clear(*reflectionsBuffer, Color::Black); + } // Screen Space Reflections pass GPUTexture* ssrBuffer = nullptr; if (useSSR) { ssrBuffer = ScreenSpaceReflectionsPass::Instance()->Render(renderContext, *reflectionsBuffer, lightBuffer); - - // Restore - context->BindSR(0, renderContext.Buffers->GBuffer0); - context->BindSR(1, renderContext.Buffers->GBuffer1); - context->BindSR(2, renderContext.Buffers->GBuffer2); - context->BindSR(3, depthBuffer.SRV); context->SetViewportAndScissors((float)tempDesc.Width, (float)tempDesc.Height); } + context->BindSR(0, renderContext.Buffers->GBuffer0); + context->BindSR(1, renderContext.Buffers->GBuffer1); + context->BindSR(2, renderContext.Buffers->GBuffer2); + context->BindSR(3, depthBuffer.SRV); + if (renderContext.View.Mode == ViewMode::Reflections) { #if GPU_ENABLE_DEVELOPMENT diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 5742f008c..614bdc342 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -416,6 +416,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont } setup.UseTemporalAAJitter = renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing; setup.UseGlobalSurfaceAtlas = renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas || + (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::Reflections) && renderContext.List->Settings.GlobalIllumination.Reflections == ReflectionsMode::DDGI) || (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI) && renderContext.List->Settings.GlobalIllumination.Mode == GlobalIlluminationMode::DDGI); setup.UseGlobalSDF = (graphicsSettings->EnableGlobalSDF && EnumHasAnyFlags(view.Flags, ViewFlags::GlobalSDF)) || renderContext.View.Mode == ViewMode::GlobalSDF || @@ -438,7 +439,6 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont case ViewMode::SpecularColor: case ViewMode::SubsurfaceColor: case ViewMode::ShadingModel: - case ViewMode::Reflections: case ViewMode::GlobalSDF: case ViewMode::GlobalSDFOverdraw: case ViewMode::GlobalSurfaceAtlas: diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl index a1de04462..a6723e915 100644 --- a/Source/Shaders/GI/DDGI.hlsl +++ b/Source/Shaders/GI/DDGI.hlsl @@ -19,6 +19,7 @@ #define DDGI_PROBE_ATTENTION_MIN 0.02f // Minimum probe attention value that still makes it active. #define DDGI_PROBE_ATTENTION_MAX 0.98f // Maximum probe attention value that still makes it active (but not activated which is 1.0f). #define DDGI_PROBE_RESOLUTION_IRRADIANCE 6 // Resolution (in texels) for probe irradiance data (excluding 1px padding on each side) +#define DDGI_PROBE_RESOLUTION_RADIANCE 14 // Resolution (in texels) for probe radiance 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_CASCADE_BLEND_SIZE 2.0f // Distance in probes over which cascades blending happens #ifndef DDGI_CASCADE_BLEND_SMOOTH @@ -164,16 +165,122 @@ float2 GetDDGIProbeUV(DDGIData data, uint cascadeIndex, uint probeIndex, float2 return uv; } -float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D probesData, Texture2D probesDistance, Texture2D probesIrradiance, float3 worldPosition, float3 worldNormal, uint cascadeIndex, float3 probesOrigin, float3 probesExtent, float probesSpacing, float3 biasedWorldPosition) +struct DDGICascadeSampling { - bool invalidCascade = cascadeIndex >= data.CascadesCount; - cascadeIndex = min(cascadeIndex, data.CascadesCount - 1); + float3 ProbesOrigin; + uint CascadeIndex; + float3 ProbesExtent; + float ProbesSpacing; + float3 BiasedWorldPosition; + float CascadeWeight; +}; + +struct DDGIProbeBase +{ + uint3 ProbeCoords; + float3 ProbeWorldPosition; + float3 BiasAlpha; +}; + +struct DDGIProbeSample +{ + float2 Weights; + uint ProbeIndex; + float3 ProbePosition; +}; + +DDGIProbeBase GetDDGIProbeBase(DDGIData data, DDGICascadeSampling cascade, float3 worldPosition) +{ + // Get the grid coordinates of the probe nearest the biased world position + DDGIProbeBase base; + base.ProbeCoords = clamp(uint3((worldPosition - cascade.ProbesOrigin + cascade.ProbesExtent) / cascade.ProbesSpacing), uint3(0, 0, 0), data.ProbesCounts - uint3(1, 1, 1)); + base.ProbeWorldPosition = GetDDGIProbeWorldPosition(data, cascade.CascadeIndex, base.ProbeCoords); + base.BiasAlpha = saturate((cascade.BiasedWorldPosition - base.ProbeWorldPosition) / cascade.ProbesSpacing); + return base; +} + +DDGIProbeSample SampleDDGIProbe(DDGIData data, Texture2D probesData, Texture2D probesDistance, float3 worldPosition, float3 worldNormal, DDGICascadeSampling cascade, DDGIProbeBase base, uint i, inout uint fallbacks) +{ + DDGIProbeSample probe; + uint3 probeCoordsOffset = uint3(i, i >> 2u, i >> 1u) & uint3(1u, 1u, 1u); + uint3 probeCoords = clamp(base.ProbeCoords + probeCoordsOffset, uint3(0, 0, 0), data.ProbesCounts - uint3(1, 1, 1)); + probe.ProbeIndex = GetDDGIScrollingProbeIndex(data, cascade.CascadeIndex, probeCoords); + + // Load probe position and state + float4 probeData = LoadDDGIProbeData(data, probesData, cascade.CascadeIndex, probe.ProbeIndex); + uint probeState = DecodeDDGIProbeState(probeData); + uint useVisibility = true; + float minWight = 0.001f; + if (probeState == DDGI_PROBE_STATE_INACTIVE) + { + // Use fallback probe that is closest to this one + uint3 fallbackCoords = DDGI_FALLBACK_COORDS_DECODE(probeData); + float fallbackToProbeDist = length((float3)probeCoords - (float3)fallbackCoords); + useVisibility = fallbackToProbeDist <= 1.0f; // Skip visibility test that blocks too far probes due to limiting max distance to 1.5 of probe spacing + if (fallbackToProbeDist > 2.0f) minWight = 1.0f; + probeCoords = fallbackCoords; + probe.ProbeIndex = GetDDGIScrollingProbeIndex(data, cascade.CascadeIndex, fallbackCoords); + probeData = LoadDDGIProbeData(data, probesData, cascade.CascadeIndex, probe.ProbeIndex); + fallbacks++; + //if (DecodeDDGIProbeState(probeData) == DDGI_PROBE_STATE_INACTIVE) continue; + } + + // Calculate probe position + probe.ProbePosition = base.ProbeWorldPosition + (((float3)probeCoords - (float3)base.ProbeCoords) * cascade.ProbesSpacing) + probeData.xyz * cascade.ProbesSpacing; + + // Calculate the distance and direction from the (biased and non-biased) shading point and the probe + float3 worldPosToProbe = normalize(probe.ProbePosition - worldPosition); + float3 biasedPosToProbe = normalize(probe.ProbePosition - cascade.BiasedWorldPosition); + float biasedPosToProbeDist = length(probe.ProbePosition - cascade.BiasedWorldPosition) * 0.95f; + + // Smooth backface test + // x - weight, y - non-directional weight with a bias +#if LIGHTING_NO_DIRECTIONAL + probe.Weights = float2(1, 1); +#else + float backfaceWeight = Square(dot(worldPosToProbe, worldNormal) * 0.5f + 0.5f); + probe.Weights = float2(max(backfaceWeight, 0.1f), backfaceWeight + 0.2f); +#endif + + // Sample distance texture + float2 octahedralCoords = GetOctahedralCoords(-biasedPosToProbe); + float2 uv = GetDDGIProbeUV(data, cascade.CascadeIndex, probe.ProbeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_DISTANCE); + float2 probeDistance = probesDistance.SampleLevel(SamplerLinearClamp, uv, 0).rg; + + // Visibility weight (Chebyshev) + if (biasedPosToProbeDist > probeDistance.x && useVisibility) + { + float variance = abs(Square(probeDistance.x) - probeDistance.y); + float visibilityWeight = variance / (variance + Square(biasedPosToProbeDist - probeDistance.x)); + visibilityWeight = lerp(1, visibilityWeight, data.IndirectShadowsStrength); + probe.Weights *= max(visibilityWeight * visibilityWeight * visibilityWeight, 0.0f); + } + + // Avoid a weight of zero + probe.Weights = max(probe.Weights, minWight); + + // Adjust weight curve to inject a small portion of light + const float minWeightThreshold = 0.2f; + if (probe.Weights.x < minWeightThreshold) + probe.Weights.x *= (probe.Weights.x * probe.Weights.x) * (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 - base.BiasAlpha, base.BiasAlpha, (float3)probeCoordsOffset); + probe.Weights *= saturate(trilinear.x * trilinear.y * trilinear.z * 2.0f); + + return probe; +} + +float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D probesData, Texture2D probesDistance, Texture2D probesIrradiance, float3 worldPosition, float3 worldNormal, DDGICascadeSampling cascade) +{ + bool invalidCascade = cascade.CascadeIndex >= data.CascadesCount; + cascade.CascadeIndex = min(cascade.CascadeIndex, data.CascadesCount - 1); #if DDGI_FALLBACK_OUTER_DEDICATED_PROBE if (invalidCascade) { // Sample a special probe as a fallback for ambient GI outside the last cascade float2 octahedralCoords = GetOctahedralCoords(worldNormal); - float2 uv = GetDDGIProbeUV(data, cascadeIndex, 0, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE); + float2 uv = GetDDGIProbeUV(data, cascade.CascadeIndex, 0, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE); float3 probeIrradiance = probesIrradiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb; #if DDGI_SRGB_BLENDING == 1 probeIrradiance = Square(pow(probeIrradiance, DDGI_SRGB_BLENDING_GAMMA)); @@ -182,12 +289,8 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D probes return probeIrradiance; } #endif - uint3 probeCoordsEnd = data.ProbesCounts - uint3(1, 1, 1); - uint3 baseProbeCoords = clamp(uint3((worldPosition - probesOrigin + probesExtent) / probesSpacing), uint3(0, 0, 0), probeCoordsEnd); - // Get the grid coordinates of the probe nearest the biased world position - float3 baseProbeWorldPosition = GetDDGIProbeWorldPosition(data, cascadeIndex, baseProbeCoords); - float3 biasAlpha = saturate((biasedWorldPosition - baseProbeWorldPosition) / probesSpacing); + DDGIProbeBase base = GetDDGIProbeBase(data, cascade, worldPosition); // Loop over the closest probes to accumulate their contributions float4 totalIrradiance = float4(0, 0, 0, 0); @@ -195,74 +298,11 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D probes float fallbacks = 0; for (uint i = 0; i < 8; i++) { - uint3 probeCoordsOffset = uint3(i, i >> 1, i >> 2) & 1; - uint3 probeCoords = clamp(baseProbeCoords + probeCoordsOffset, uint3(0, 0, 0), probeCoordsEnd); - uint probeIndex = GetDDGIScrollingProbeIndex(data, cascadeIndex, probeCoords); - - // Load probe position and state - float4 probeData = LoadDDGIProbeData(data, probesData, cascadeIndex, probeIndex); - uint probeState = DecodeDDGIProbeState(probeData); - uint useVisibility = true; - float minWight = 0.001f; - if (probeState == DDGI_PROBE_STATE_INACTIVE) - { - // Use fallback probe that is closest to this one - uint3 fallbackCoords = DDGI_FALLBACK_COORDS_DECODE(probeData); - float fallbackToProbeDist = length((float3)probeCoords - (float3)fallbackCoords); - useVisibility = fallbackToProbeDist <= 1.0f; // Skip visibility test that blocks too far probes due to limiting max distance to 1.5 of probe spacing - if (fallbackToProbeDist > 2.0f) minWight = 1.0f; - probeCoords = fallbackCoords; - probeIndex = GetDDGIScrollingProbeIndex(data, cascadeIndex, fallbackCoords); - probeData = LoadDDGIProbeData(data, probesData, cascadeIndex, probeIndex); - fallbacks++; - //if (DecodeDDGIProbeState(probeData) == DDGI_PROBE_STATE_INACTIVE) continue; - } - - // Calculate probe position - float3 probePosition = baseProbeWorldPosition + (((float3)probeCoords - (float3)baseProbeCoords) * probesSpacing) + probeData.xyz * probesSpacing; - - // Calculate the distance and direction from the (biased and non-biased) shading point and the probe - float3 worldPosToProbe = normalize(probePosition - worldPosition); - float3 biasedPosToProbe = normalize(probePosition - biasedWorldPosition); - float biasedPosToProbeDist = length(probePosition - biasedWorldPosition) * 0.95f; - - // Smooth backface test - // x - weight, y - non-directional weight with a bias -#if LIGHTING_NO_DIRECTIONAL - float2 weights = float2(1, 1); -#else - float backfaceWeight = Square(dot(worldPosToProbe, worldNormal) * 0.5f + 0.5f); - float2 weights = float2(max(backfaceWeight, 0.1f), backfaceWeight + 0.2f); -#endif - - // Sample distance texture - float2 octahedralCoords = GetOctahedralCoords(-biasedPosToProbe); - float2 uv = GetDDGIProbeUV(data, cascadeIndex, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_DISTANCE); - float2 probeDistance = probesDistance.SampleLevel(SamplerLinearClamp, uv, 0).rg; - - // Visibility weight (Chebyshev) - if (biasedPosToProbeDist > probeDistance.x && useVisibility) - { - float variance = abs(Square(probeDistance.x) - probeDistance.y); - float visibilityWeight = variance / (variance + Square(biasedPosToProbeDist - probeDistance.x)); - visibilityWeight = lerp(1, visibilityWeight, data.IndirectShadowsStrength); - weights *= max(visibilityWeight * visibilityWeight * visibilityWeight, 0.0f); - } - - // Avoid a weight of zero - weights = max(weights, minWight); - - // Adjust weight curve to inject a small portion of light - const float minWeightThreshold = 0.2f; - if (weights.x < minWeightThreshold) weights.x *= (weights.x * weights.x) * (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, (float3)probeCoordsOffset); - weights *= saturate(trilinear.x * trilinear.y * trilinear.z * 2.0f); + DDGIProbeSample probe = SampleDDGIProbe(data, probesData, probesDistance, worldPosition, worldNormal, cascade, base, i, fallbacks); // Sample irradiance texture - octahedralCoords = GetOctahedralCoords(worldNormal); - uv = GetDDGIProbeUV(data, cascadeIndex, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE); + float2 octahedralCoords = GetOctahedralCoords(worldNormal); + float2 uv = GetDDGIProbeUV(data, cascade.CascadeIndex, probe.ProbeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE); float3 probeIrradiance = probesIrradiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb; #if DDGI_SRGB_BLENDING == 1 probeIrradiance = pow(probeIrradiance, DDGI_SRGB_BLENDING_GAMMA); @@ -271,17 +311,17 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D probes #endif // Accumulate weighted irradiance - totalIrradiance += float4(probeIrradiance * weights.x, weights.x); - totalIrradianceNonDir += float4(probeIrradiance * weights.y, weights.y); + totalIrradiance += float4(probeIrradiance * probe.Weights.x, probe.Weights.x); + totalIrradianceNonDir += float4(probeIrradiance * probe.Weights.y, probe.Weights.y); } #if 0 // Debug DDGI cascades with colors - if (cascadeIndex == 0) + if (cascade.CascadeIndex == 0) totalIrradiance = float4(1, 0, 0, 1); - else if (cascadeIndex == 1) + else if (cascade.CascadeIndex == 1) totalIrradiance = float4(0, 1, 0, 1); - else if (cascadeIndex == 2) + else if (cascade.CascadeIndex == 2) totalIrradiance = float4(0, 0, 1, 1); else if (invalidCascade) // Area outside the last cascade that clamps to it totalIrradiance = float4(1, 0, 1, 1); @@ -309,6 +349,51 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D probes return totalIrradiance.rgb; } +float3 SampleDDGISpecularCascade(DDGIData data, Texture2D probesData, Texture2D probesDistance, Texture2D probesRadiance, float3 worldPosition, float3 worldNormal, float3 reflection, DDGICascadeSampling cascade) +{ + bool invalidCascade = cascade.CascadeIndex >= data.CascadesCount; + cascade.CascadeIndex = min(cascade.CascadeIndex, data.CascadesCount - 1); +#if DDGI_FALLBACK_OUTER_DEDICATED_PROBE + if (invalidCascade) + { + // Sample a special probe as a fallback for ambient sky reflection outside the last cascade + float2 octahedralCoords = GetOctahedralCoords(reflection); + float2 uv = GetDDGIProbeUV(data, cascade.CascadeIndex, 0, octahedralCoords, DDGI_PROBE_RESOLUTION_RADIANCE); + float3 probeRadiance = probesRadiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb; + return probeRadiance; + } +#endif + + DDGIProbeBase base = GetDDGIProbeBase(data, cascade, worldPosition); + + // Loop over the closest probes to accumulate their contributions + float4 totalRadiance = float4(0, 0, 0, 0); + float fallbacks = 0; + for (uint i = 0; i < 8; i++) + { + DDGIProbeSample probe = SampleDDGIProbe(data, probesData, probesDistance, worldPosition, worldNormal, cascade, base, i, fallbacks); + + // Parallax correction + //float3 sampleVector = normalize((worldPosition - probe.ProbePosition) / (cascade.ProbesSpacing * 2) + reflection); + float3 sampleVector = reflection; + + // Sample radiance texture + float2 octahedralCoords = GetOctahedralCoords(sampleVector); + float2 uv = GetDDGIProbeUV(data, cascade.CascadeIndex, probe.ProbeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_RADIANCE); + float3 probeRadiance = probesRadiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb; + // TODO: filter radiance based on roughness + // TODO: or maybe build mip-chain for updated probes radiance texture and sample it based on roughness like env probes? + + // Accumulate weighted radiance + totalRadiance += float4(probeRadiance * probe.Weights.x, probe.Weights.x); + } + + // Normalize radiance + totalRadiance.rgb /= max(totalRadiance.a, 0.0001f); + + return totalRadiance.rgb; +} + float3 GetDDGISurfaceBias(float3 viewDir, float probesSpacing, float3 worldNormal, float bias) { #if LIGHTING_NO_DIRECTIONAL @@ -327,67 +412,99 @@ float sdRoundBox(float3 p, float3 b, float r) return length(max(q, 0.0f)) + min(max(q.x, max(q.y, q.z)), 0.0f) - r; } -// Samples DDGI probes volume at the given world-space position and returns the irradiance. -// bias - scales the bias vector to the initial sample point to reduce self-shading artifacts -// dither - randomized per-pixel value in range 0-1, used to smooth dithering for cascades blending -float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesData, Texture2D probesDistance, Texture2D probesIrradiance, float3 worldPosition, float3 worldNormal, float bias = DDGI_DEFAULT_BIAS, float dither = 0.0f) +DDGICascadeSampling GetDDGICascade(DDGIData data, float3 worldPosition, float3 worldNormal, float bias, float dither) { // Select the highest cascade that contains the sample location - float probesSpacing = 0, cascadeWeight = 0; - float3 probesOrigin = (float3)0, probesExtent = (float3)0, biasedWorldPosition = (float3)0; + DDGICascadeSampling cascade; + cascade.ProbesOrigin = float3(0, 0, 0); + cascade.ProbesExtent = float3(0, 0, 0); + cascade.BiasedWorldPosition = float3(0, 0, 0); + cascade.ProbesSpacing = 0; + cascade.CascadeWeight = 0; float3 viewDir = normalize(data.ViewPos - worldPosition); #if DDGI_CASCADE_BLEND_SMOOTH dither = 0.0f; #endif #ifdef DDGI_DEBUG_CASCADE - uint cascadeIndex = DDGI_DEBUG_CASCADE; + cascade.CascadeIndex = DDGI_DEBUG_CASCADE; #else - uint cascadeIndex = 0; - if (data.CascadesCount == 0) - return float3(0, 0, 0); - for (; cascadeIndex < data.CascadesCount; cascadeIndex++) + cascade.CascadeIndex = 0; + for (; cascade.CascadeIndex < data.CascadesCount; cascade.CascadeIndex++) { // Get cascade data - probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; - probesOrigin = data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing + data.ProbesOriginAndSpacing[cascadeIndex].xyz; - probesExtent = (data.ProbesCounts - 1) * (probesSpacing * 0.5f); - biasedWorldPosition = worldPosition + GetDDGISurfaceBias(viewDir, probesSpacing, worldNormal, bias); + cascade.ProbesSpacing = data.ProbesOriginAndSpacing[cascade.CascadeIndex].w; + cascade.ProbesOrigin = data.ProbesScrollOffsets[cascade.CascadeIndex].xyz * cascade.ProbesSpacing + data.ProbesOriginAndSpacing[cascade.CascadeIndex].xyz; + cascade.ProbesExtent = (data.ProbesCounts - 1) * (cascade.ProbesSpacing * 0.5f); + cascade.BiasedWorldPosition = worldPosition + GetDDGISurfaceBias(viewDir, cascade.ProbesSpacing, worldNormal, bias); // Calculate cascade blending weight (use input bias to smooth transition) - float fadeDistance = probesSpacing * DDGI_CASCADE_BLEND_SIZE; - float3 blendPos = worldPosition - data.BlendOrigin[cascadeIndex].xyz; - cascadeWeight = sdRoundBox(blendPos, probesExtent - probesSpacing, probesSpacing * 2) + fadeDistance; - cascadeWeight = 1 - saturate(cascadeWeight / fadeDistance); - if (cascadeWeight > dither) + float fadeDistance = cascade.ProbesSpacing * DDGI_CASCADE_BLEND_SIZE; + float3 blendPos = worldPosition - data.BlendOrigin[cascade.CascadeIndex].xyz; + cascade.CascadeWeight = sdRoundBox(blendPos, cascade.ProbesExtent - cascade.ProbesSpacing, cascade.ProbesSpacing * 2) + fadeDistance; + cascade.CascadeWeight = 1 - saturate(cascade.CascadeWeight / fadeDistance); + if (cascade.CascadeWeight > dither) break; } #endif + return cascade; +} + +// Samples DDGI probes volume at the given world-space position and returns the irradiance. +// bias - scales the bias vector to the initial sample point to reduce self-shading artifacts +// dither - randomized per-pixel value in range 0-1, used to smooth dithering for cascades blending +float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesData, Texture2D probesDistance, Texture2D probesIrradiance, float3 worldPosition, float3 worldNormal, float bias = DDGI_DEFAULT_BIAS, float dither = 0.0f) +{ + if (data.CascadesCount == 0) + return float3(0, 0, 0); + + // Select the cascade + DDGICascadeSampling cascade = GetDDGICascade(data, worldPosition, worldNormal, bias, dither); // Sample cascade - float3 result = SampleDDGIIrradianceCascade(data, probesData, probesDistance, probesIrradiance, worldPosition, worldNormal, cascadeIndex, probesOrigin, probesExtent, probesSpacing, biasedWorldPosition); + float3 result = SampleDDGIIrradianceCascade(data, probesData, probesDistance, probesIrradiance, worldPosition, worldNormal, cascade); - // Blend with the next cascade (or fallback irradiance outside the volume) #if DDGI_CASCADE_BLEND_SMOOTH && !defined(DDGI_DEBUG_CASCADE) - cascadeIndex++; - if (cascadeIndex <= data.CascadesCount && cascadeWeight < 0.99f) + // Blend with the next cascade + cascade.CascadeIndex++; + if (cascade.CascadeIndex <= data.CascadesCount && cascade.CascadeWeight < 0.99f) { - uint cascadeIndexTmp = cascadeIndex; - cascadeIndex = min(cascadeIndex, data.CascadesCount - 1); - probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; - probesOrigin = data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing + data.ProbesOriginAndSpacing[cascadeIndex].xyz; - probesExtent = (data.ProbesCounts - 1) * (probesSpacing * 0.5f); - biasedWorldPosition = worldPosition + GetDDGISurfaceBias(viewDir, probesSpacing, worldNormal, bias); - float3 resultNext = SampleDDGIIrradianceCascade(data, probesData, probesDistance, probesIrradiance, worldPosition, worldNormal, cascadeIndexTmp, probesOrigin, probesExtent, probesSpacing, biasedWorldPosition); - result *= cascadeWeight; - result += resultNext * (1 - cascadeWeight); + uint cascadeIndexTmp = cascade.CascadeIndex; + cascade.CascadeIndex = min(cascade.CascadeIndex, data.CascadesCount - 1); + cascade.ProbesSpacing = data.ProbesOriginAndSpacing[cascade.CascadeIndex].w; + cascade.ProbesOrigin = data.ProbesScrollOffsets[cascade.CascadeIndex].xyz * cascade.ProbesSpacing + data.ProbesOriginAndSpacing[cascade.CascadeIndex].xyz; + cascade.ProbesExtent = (data.ProbesCounts - 1) * (cascade.ProbesSpacing * 0.5f); + float3 viewDir = normalize(data.ViewPos - worldPosition); + cascade.BiasedWorldPosition = worldPosition + GetDDGISurfaceBias(viewDir, cascade.ProbesSpacing, worldNormal, bias); + float3 resultNext = SampleDDGIIrradianceCascade(data, probesData, probesDistance, probesIrradiance, worldPosition, worldNormal, cascade); + result *= cascade.CascadeWeight; + result += resultNext * (1 - cascade.CascadeWeight); } #endif - if (cascadeIndex >= data.CascadesCount) + + // Blend between the last cascade and the fallback irradiance + if (cascade.CascadeIndex >= data.CascadesCount) { - // Blend between the last cascade and the fallback irradiance - float fallbackWeight = (1 - cascadeWeight) * data.FallbackIrradiance.a; + float fallbackWeight = (1 - cascade.CascadeWeight) * data.FallbackIrradiance.a; result = lerp(result, data.FallbackIrradiance.rgb, fallbackWeight); } return result; } + +// Samples DDGI probes volume at the given world-space position and returns the specular reflections. +// bias - scales the bias vector to the initial sample point to reduce self-shading artifacts +// dither - randomized per-pixel value in range 0-1, used to smooth dithering for cascades blending +float3 SampleDDGISpecular(DDGIData data, Texture2D probesData, Texture2D probesDistance, Texture2D probesRadiance, float3 worldPosition, float3 worldNormal, float roughness, float bias = DDGI_DEFAULT_BIAS, float dither = 0.0f) +{ + if (data.CascadesCount == 0) + return float3(0, 0, 0); + + // Select the cascade + DDGICascadeSampling cascade = GetDDGICascade(data, worldPosition, worldNormal, bias, dither); + + // Sample cascade + float3 reflection = reflect(normalize(worldPosition - data.ViewPos), worldNormal); + float3 specular = SampleDDGISpecularCascade(data, probesData, probesDistance, probesRadiance, worldPosition, worldNormal, reflection, cascade); + + return specular; +} diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index f44f988b9..98f54339b 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -482,7 +482,7 @@ RWByteAddressBuffer RWStats : register(u1); Texture3D GlobalSDFTex : register(t0); Texture3D GlobalSDFMip : register(t1); ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t2); -ByteAddressBuffer RWGlobalSurfaceAtlasCulledObjects : register(t3); +ByteAddressBuffer GlobalSurfaceAtlasCulledObjects : register(t3); Buffer GlobalSurfaceAtlasObjects : register(t4); Texture2D GlobalSurfaceAtlasDepth : register(t5); Texture2D GlobalSurfaceAtlasTex : register(t6); @@ -545,7 +545,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(GlobalSDF, hit); - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, RWGlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hitPosition, -probeRayDirection, surfaceThreshold); + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasObjects, 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 @@ -586,6 +586,10 @@ groupshared float OutputInstability[DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTIO // Update distance #define DDGI_PROBE_RESOLUTION DDGI_PROBE_RESOLUTION_DISTANCE groupshared float CachedProbesTraceDistance[DDGI_TRACE_RAYS_LIMIT]; +#elif DDGI_PROBE_UPDATE_RADIANCE +// Update radiance +#define DDGI_PROBE_RESOLUTION DDGI_PROBE_RESOLUTION_RADIANCE +groupshared float4 CachedProbesTraceRadiance[DDGI_TRACE_RAYS_LIMIT]; #endif // Source: https://github.com/turanszkij/WickedEngine @@ -699,14 +703,10 @@ static const uint4 BorderOffsets[BorderOffsetsSize] = { groupshared float3 CachedProbesTraceDirection[DDGI_TRACE_RAYS_LIMIT]; RWTexture2D RWOutput : register(u0); -#if DDGI_PROBE_UPDATE_IRRADIANCE -RWTexture2D RWProbesData : register(u1); -#if DDGI_DEBUG_INSTABILITY +RWTexture2D ProbesData : register(u1); +#if DDGI_PROBE_UPDATE_IRRADIANCE && DDGI_DEBUG_INSTABILITY RWTexture2D RWOutputInstability : register(u2); #endif -#elif DDGI_PROBE_UPDATE_DEPTH -Texture2D ProbesData : register(t0); -#endif Texture2D ProbesTrace : register(t1); ByteAddressBuffer ActiveProbes : register(t2); @@ -714,6 +714,7 @@ ByteAddressBuffer ActiveProbes : register(t2); META_CS(true, FEATURE_LEVEL_SM5) META_PERMUTATION_1(DDGI_PROBE_UPDATE_IRRADIANCE=1) META_PERMUTATION_1(DDGI_PROBE_UPDATE_DEPTH=1) +META_PERMUTATION_1(DDGI_PROBE_UPDATE_RADIANCE=1) [numthreads(DDGI_PROBE_RESOLUTION, DDGI_PROBE_RESOLUTION, 1)] void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) { @@ -725,12 +726,8 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ probeIndex = GetDDGIScrollingProbeIndex(DDGI, CascadeIndex, probeCoords); // Load probe data -#if DDGI_PROBE_UPDATE_IRRADIANCE int2 probeDataCoords = GetDDGIProbeTexelCoords(DDGI, CascadeIndex, probeIndex); - float4 probeData = RWProbesData[probeDataCoords]; -#elif DDGI_PROBE_UPDATE_DEPTH - float4 probeData = LoadDDGIProbeData(DDGI, ProbesData, CascadeIndex, probeIndex); -#endif + float4 probeData = ProbesData[probeDataCoords]; float probeAttention = DecodeDDGIProbeAttention(probeData); uint probeState = DecodeDDGIProbeState(probeData); uint probeRaysCount = GetProbeRaysCount(DDGI, probeAttention); @@ -750,7 +747,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ for (uint i = 0; i < raysCount; i++) { uint rayIndex = raysStart + i; -#if DDGI_PROBE_UPDATE_IRRADIANCE +#if DDGI_PROBE_UPDATE_IRRADIANCE || DDGI_PROBE_UPDATE_RADIANCE CachedProbesTraceRadiance[rayIndex] = ProbesTrace[uint2(rayIndex, GroupId.x)]; #elif DDGI_PROBE_UPDATE_DEPTH float rayDistance = ProbesTrace[uint2(rayIndex, GroupId.x)].w; @@ -800,6 +797,14 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ // Add distance (R), distance^2 (G) and weight (A) float rayDistance = CachedProbesTraceDistance[rayIndex]; result += float4(rayDistance, rayDistance * rayDistance, 0.0f, 1.0f) * rayWeight; +#elif DDGI_PROBE_UPDATE_RADIANCE + // Clamped cosine filtering + // ["Ray tracing the world of Assassin's Creed Shadows", SIGGRAPH 2025] + rayWeight = pow(saturate((rayWeight - 0.75) / (1 - 0.75)), 4); + + // Add radiance (RGB) and weight (A) + float4 rayRadiance = CachedProbesTraceRadiance[rayIndex]; + result += float4(rayRadiance.rgb * rayWeight, rayWeight); #endif } @@ -807,7 +812,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ float epsilon = (float)probeRaysCount * 1e-9f; #if DDGI_PROBE_UPDATE_IRRADIANCE result.rgb *= 1.0f / (2.0f * max(result.a, epsilon)); -#elif DDGI_PROBE_UPDATE_DEPTH +#elif DDGI_PROBE_UPDATE_DEPTH || DDGI_PROBE_UPDATE_RADIANCE result.rgb *= 1.0f / max(result.a, epsilon); #endif @@ -829,7 +834,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ instability *= 2.0f; // Make it stronger on scene changes //instability = saturate(instability); OutputInstability[GroupIndex] = instability; -#if DDGI_DEBUG_INSTABILITY +#if DDGI_PROBE_UPDATE_IRRADIANCE && DDGI_DEBUG_INSTABILITY RWOutputInstability[outputCoords] = instability; //RWOutputInstability[outputCoords] = probeAttention; // Debug test probe attention visualization #endif @@ -870,12 +875,18 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ //result.rgb = previous + (irradianceDelta * 0.25f); } result = float4(lerp(result.rgb, previous.rgb, historyWeight), 1.0f); +#elif DDGI_PROBE_UPDATE_RADIANCE + historyWeightSlow = 0.98f; + historyWeight = lerp(historyWeightSlow, min(historyWeightFast * 1.1f, historyWeightSlow), probeAttention * probeAttention); + result.rgb = lerp(result.rgb, previous, historyWeight); +#elif DDGI_PROBE_UPDATE_DEPTH + result = float4(lerp(result.rg, previous.rg, historyWeight), 0.0f, 1.0f); +#endif +#if DDGI_PROBE_UPDATE_IRRADIANCE || DDGI_PROBE_UPDATE_RADIANCE // Apply quantization error to reduce yellowish artifacts due to R11G11B10 format float noise = InterleavedGradientNoise(octahedralCoords * 10, FrameIndexMod8); result.rgb = QuantizeColor(result.rgb, noise, QuantizationError); -#elif DDGI_PROBE_UPDATE_DEPTH - result = float4(lerp(result.rg, previous.rg, historyWeight), 0.0f, 1.0f); #endif RWOutput[outputCoords] = result; @@ -909,7 +920,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ // Update probe data for the next frame probeState = DDGI_PROBE_STATE_ACTIVE; - RWProbesData[probeDataCoords] = EncodeDDGIProbeData(probeData.xyz, probeState, probeAttention); + ProbesData[probeDataCoords] = EncodeDDGIProbeData(probeData.xyz, probeState, probeAttention); } #if DDGI_DEBUG_INSTABILITY && defined(BorderOffsetsSize) @@ -955,7 +966,7 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ #endif -#ifdef _PS_IndirectLighting +#if defined(_PS_IndirectLighting) || defined(_PS_SpecularLighting) #include "./Flax/GBuffer.hlsl" #include "./Flax/Random.hlsl" @@ -963,6 +974,15 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_ Texture2D ProbesData : register(t4); Texture2D ProbesDistance : register(t5); + +// Shared code for both irradiance and specular sampling +#define DDGI_GET_DITHER RandN2(input.TexCoord + TemporalTime).x +#define DDGI_GET_SAMPLE_POS gBuffer.WorldPos + gBuffer.Normal * (dither * 0.1f + 0.1f) + +#endif + +#ifdef _PS_IndirectLighting + Texture2D ProbesIrradiance : register(t6); // Pixel shader for drawing indirect lighting in fullscreen @@ -973,15 +993,13 @@ float4 PS_IndirectLighting(Quad_VS2PS input) : SV_Target0 { // Sample GBuffer GBufferSample gBuffer = SampleGBuffer(GBuffer, input.TexCoord); - - // Check if cannot shadow pixel BRANCH if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) return float4(0, 0, 0, 0); // Sample irradiance - float dither = RandN2(input.TexCoord + TemporalTime).x; - float3 samplePos = gBuffer.WorldPos + gBuffer.Normal * (dither * 0.1f + 0.1f); + float dither = DDGI_GET_DITHER; + float3 samplePos = DDGI_GET_SAMPLE_POS; float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesData, ProbesDistance, ProbesIrradiance, samplePos, gBuffer.Normal, DDGI_DEFAULT_BIAS, dither); // Calculate lighting @@ -991,3 +1009,27 @@ float4 PS_IndirectLighting(Quad_VS2PS input) : SV_Target0 } #endif + +#ifdef _PS_SpecularLighting + +Texture2D ProbesRadiance : register(t6); + +// Pixel shader for drawing specular lighting in fullscreen +META_PS(true, FEATURE_LEVEL_SM5) +float4 PS_SpecularLighting(Quad_VS2PS input) : SV_Target0 +{ + // Sample GBuffer + GBufferSample gBuffer = SampleGBuffer(GBuffer, input.TexCoord); + BRANCH + if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) + return float4(0, 0, 0, 0); + + // Sample specular reflection + float dither = DDGI_GET_DITHER; + float3 samplePos = DDGI_GET_SAMPLE_POS; + float3 specular = SampleDDGISpecular(DDGI, ProbesData, ProbesDistance, ProbesRadiance, samplePos, gBuffer.Normal, gBuffer.Roughness, DDGI_DEFAULT_BIAS, dither); + + return float4(specular, 1); +} + +#endif