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