Add new DDGI Reflections rendering mode for fully dynamic indirect specular

This commit is contained in:
2026-07-14 23:09:34 +02:00
parent 7d387a11c7
commit 401bfbd52d
10 changed files with 507 additions and 205 deletions
Binary file not shown.
Binary file not shown.
@@ -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)
+29 -2
View File
@@ -23,7 +23,7 @@ API_ENUM() enum class GlobalIlluminationMode
None = 0,
/// <summary>
/// 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.
/// </summary>
DDGI = 1,
@@ -161,6 +161,22 @@ API_ENUM() enum class ResolutionMode : int32
Half = 2,
};
/// <summary>
/// The reflections modes.
/// </summary>
API_ENUM() enum class ReflectionsMode : int32
{
/// <summary>
/// Reflections from Environment Probes placed on the scene.
/// </summary>
EnvironmentProbes = 0,
/// <summary>
/// Reflections from Dynamic Diffuse Global Illumination algorithm with scrolling probes volume (and cascades). Uses software raytracing - requires Global SDF and Global Surface Atlas.
/// </summary>
DDGI = 1,
};
/// <summary>
/// The screen space reflections modes.
/// </summary>
@@ -344,10 +360,15 @@ API_ENUM(Attributes="Flags") enum class GlobalIlluminationSettingsOverride : int
/// </summary>
IndirectResolution = 1 << 7,
/// <summary>
/// Overrides <see cref="GlobalIlluminationSettings.Reflections"/> property.
/// </summary>
Reflections = 1 << 8,
/// <summary>
/// All properties.
/// </summary>
All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance | BounceIntensity | IndirectShadowsStrength | IndirectResolution,
All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance | BounceIntensity | IndirectShadowsStrength | IndirectResolution | Reflections,
};
/// <summary>
@@ -413,6 +434,12 @@ API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable
API_FIELD(Attributes="EditorOrder(50), PostProcessSetting((int)GlobalIlluminationSettingsOverride.IndirectResolution)")
ResolutionMode IndirectResolution = ResolutionMode::Full;
/// <summary>
/// The indirect specular reflections rendering mode to use.
/// </summary>
API_FIELD(Attributes = "EditorOrder(100), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Reflections)")
ReflectionsMode Reflections = ReflectionsMode::EnvironmentProbes;
public:
/// <summary>
/// Blends the settings using given weight.
@@ -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<DDGICustomBuffer>(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<BoundingBox> regions)
{
#if !DDGI_DEBUG_FORCE_UPDATE
@@ -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<Model> _debugModel;
AssetReference<MaterialBase> _debugMaterial;
@@ -73,6 +76,15 @@ public:
/// <returns>True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false.</returns>
bool Render(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer);
/// <summary>
/// Renders the DDGI Reflections.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="context">The GPU context.</param>
/// <param name="reflectionsBuffer">The reflections buffer (output).</param>
/// <returns>True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false.</returns>
bool RenderReflections(RenderContext& renderContext, GPUContext* context, GPUTextureView* reflectionsBuffer);
/// <summary>
/// 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.
/// </summary>
@@ -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:
+37 -33
View File
@@ -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
+1 -1
View File
@@ -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:
+233 -116
View File
@@ -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<snorm float4> probesData, Texture2D<float4> probesDistance, Texture2D<float4> 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<snorm float4> probesData, Texture2D<float4> 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<snorm float4> probesData, Texture2D<float4> probesDistance, Texture2D<float4> 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<snorm float4> 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<snorm float4> 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<snorm float4> 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<snorm float4> probes
return totalIrradiance.rgb;
}
float3 SampleDDGISpecularCascade(DDGIData data, Texture2D<snorm float4> probesData, Texture2D<float4> probesDistance, Texture2D<float4> 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<snorm float4> probesData, Texture2D<float4> probesDistance, Texture2D<float4> 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<snorm float4> probesData, Texture2D<float4> probesDistance, Texture2D<float4> 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<snorm float4> probesData, Texture2D<float4> probesDistance, Texture2D<float4> 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;
}
+66 -24
View File
@@ -482,7 +482,7 @@ RWByteAddressBuffer RWStats : register(u1);
Texture3D<snorm float> GlobalSDFTex : register(t0);
Texture3D<snorm float> GlobalSDFMip : register(t1);
ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t2);
ByteAddressBuffer RWGlobalSurfaceAtlasCulledObjects : register(t3);
ByteAddressBuffer GlobalSurfaceAtlasCulledObjects : register(t3);
Buffer<float4> 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<float4> RWOutput : register(u0);
#if DDGI_PROBE_UPDATE_IRRADIANCE
RWTexture2D<snorm float4> RWProbesData : register(u1);
#if DDGI_DEBUG_INSTABILITY
RWTexture2D<snorm float4> ProbesData : register(u1);
#if DDGI_PROBE_UPDATE_IRRADIANCE && DDGI_DEBUG_INSTABILITY
RWTexture2D<float> RWOutputInstability : register(u2);
#endif
#elif DDGI_PROBE_UPDATE_DEPTH
Texture2D<snorm float4> ProbesData : register(t0);
#endif
Texture2D<float4> 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<snorm float4> ProbesData : register(t4);
Texture2D<float4> 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<float4> 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<float4> 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