Add roughness-based filtering when sampling DDGI probes

This commit is contained in:
2026-07-21 11:29:24 +02:00
parent 078a45c4b7
commit 8a5ff58b9e
4 changed files with 50 additions and 30 deletions
Binary file not shown.
@@ -75,9 +75,8 @@ GPU_CB_STRUCT(Data0 {
float TestValue;
Float3 QuantizationError;
int32 FrameIndexMod8;
Float2 ResolveDitherScale;
Float2 Padding0;
float ResolveDitherScaleIndirect;
float ResolveDitherScaleSpecular;
});
GPU_CB_STRUCT(Data1 {
@@ -185,16 +184,18 @@ public:
}
};
void InitData0Shared(const RenderContext& renderContext, const DDGICustomBuffer& ddgiData, Data0& data)
void InitData0(const RenderContext& renderContext, const DDGICustomBuffer& ddgiData, Data0& data)
{
Platform::MemoryClear((byte*)&data + sizeof(data.DDGI), sizeof(Data0) - sizeof(data.DDGI));
data.DDGI = ddgiData.Result.Constants;
data.ProbesCount = data.DDGI.ProbesCounts[0] * data.DDGI.ProbesCounts[1] * data.DDGI.ProbesCounts[2];
data.TestValue = Graphics::TestValue;
data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f;
data.FrameIndexMod8 = (int32)(Engine::FrameCount % 8);
auto& settings = renderContext.List->Settings.GlobalIllumination;
constexpr float DitherScaleHalfRes = 0.1f; // Hardcoded to reduce noise at half-res due to TAA not being able to filter this out (in future BilateralUpscale could try smooth it)
data.ResolveDitherScaleIndirect = settings.IndirectResolution == ResolutionMode::Full ? 1.0f : DitherScaleHalfRes;
data.ResolveDitherScaleSpecular = settings.ReflectionsResolution == ResolutionMode::Full ? 1.0f : DitherScaleHalfRes;
data.ResolveDitherScale.X = settings.IndirectResolution == ResolutionMode::Full ? 1.0f : DitherScaleHalfRes;
data.ResolveDitherScale.Y = settings.ReflectionsResolution == ResolutionMode::Full ? 1.0f : DitherScaleHalfRes;
GBufferPass::SetInputs(renderContext.View, data.GBuffer);
}
@@ -301,6 +302,8 @@ bool DynamicDiffuseGlobalIlluminationPass::setupResources()
// Initialize resources
const auto shader = _shader->GPU;
CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data0);
CHECK_INVALID_SHADER_PASS_CB_SIZE(shader, 1, Data1);
_cb0 = shader->GetCB(0);
_cb1 = shader->GetCB(1);
if (!_cb0 || !_cb1)
@@ -658,6 +661,7 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont
ddgiData.Result.ProbesRadiance = ddgiData.ProbesRadiance ? ddgiData.ProbesRadiance->View() : nullptr;
Data0 data;
InitData0(renderContext, ddgiData, data);
// Compute random rotation matrix for probe rays orientation (randomized every frame)
Matrix3x3 raysRotationMatrix;
@@ -668,7 +672,6 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont
data.GlobalSDF = bindingDataSDF.Constants;
data.GlobalSurfaceAtlas = bindingDataSurfaceAtlas.Constants;
data.ProbesCount = data.DDGI.ProbesCounts[0] * data.DDGI.ProbesCounts[1] * data.DDGI.ProbesCounts[2];
data.ResetBlend = clear ? 1.0f : 0.0f;
for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++)
{
@@ -678,8 +681,6 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont
data.ViewDir = renderContext.View.Direction;
data.SkyboxIntensity = renderContext.List->Sky ? renderContext.List->Sky->GetIndirectLightingIntensity() : 1.0f;
data.QuantizationError = RenderTools::GetColorQuantizationError(ddgiData.ProbesIrradiance->Format());
InitData0Shared(renderContext, ddgiData, data);
GBufferPass::SetInputs(renderContext.View, data.GBuffer);
context->UpdateCB(_cb0, &data);
context->BindCB(0, _cb0);
}
@@ -911,8 +912,9 @@ bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext,
#endif
if (!render)
{
// Bind constants if not set to draw DDGI
Data0 data;
InitData0Shared(renderContext, *ddgiData, data);
InitData0(renderContext, *ddgiData, data);
context->UpdateCB(_cb0, &data);
context->BindCB(0, _cb0);
}
@@ -1060,8 +1062,9 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderReflections(RenderContext& rend
PROFILE_GPU_CPU_NAMED("Specular Lighting");
if (!render)
{
// Bind constants if not set to draw DDGI
Data0 data;
InitData0Shared(renderContext, *ddgiData, data);
InitData0(renderContext, *ddgiData, data);
context->UpdateCB(_cb0, &data);
context->BindCB(0, _cb0);
}
+32 -14
View File
@@ -160,7 +160,7 @@ float2 GetDDGIProbeUV(DDGIData data, uint cascadeIndex, uint probeIndex, float2
float probeTexelSize = resolution + 2.0f;
float2 textureSize = float2(data.ProbesCounts.x * data.ProbesCounts.y, data.ProbesCounts.z * data.CascadesCount) * probeTexelSize;
float2 uv = float2(coords.x * probeTexelSize, coords.y * probeTexelSize) + (probeTexelSize * 0.5f);
uv += octahedralCoords.xy * (resolution * 0.5f);
uv += octahedralCoords * (resolution * 0.5f);
uv /= textureSize;
return uv;
}
@@ -275,11 +275,11 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D<snorm float4> probes
{
bool invalidCascade = cascade.CascadeIndex >= data.CascadesCount;
cascade.CascadeIndex = min(cascade.CascadeIndex, data.CascadesCount - 1);
float2 octahedralCoords = GetOctahedralCoords(worldNormal);
#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, cascade.CascadeIndex, 0, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE);
float3 probeIrradiance = probesIrradiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb;
#if DDGI_SRGB_BLENDING == 1
@@ -301,7 +301,6 @@ float3 SampleDDGIIrradianceCascade(DDGIData data, Texture2D<snorm float4> probes
DDGIProbeSample probe = SampleDDGIProbe(data, probesData, probesDistance, worldPosition, worldNormal, cascade, base, i, fallbacks);
// Sample irradiance texture
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
@@ -357,7 +356,33 @@ float3 DDGIHash3D(float3 p)
return frac(frac(p.xxy * p.yzz) * 2.0 - 1.0);
}
float3 SampleDDGISpecularCascade(DDGIData data, Texture2D<snorm float4> probesData, Texture2D<float4> probesDistance, Texture2D<float4> probesRadiance, float3 worldPosition, float3 worldNormal, float3 reflection, DDGICascadeSampling cascade)
float3 FilterProbeRadiance(DDGIData data, Texture2D<float4> probesRadiance, float roughness, uint cascadeIndex, uint probeIndex, float3 direction)
{
uint2 coords = GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex);
const float probeTexelSize = DDGI_PROBE_RESOLUTION_RADIANCE + 2.0f;
float2 uv = float2(coords.x * probeTexelSize, coords.y * probeTexelSize) + (probeTexelSize * 0.5f);
uv += GetOctahedralCoords(direction) * (DDGI_PROBE_RESOLUTION_RADIANCE * 0.5f);
float2 textureSize = float2(data.ProbesCounts.x * data.ProbesCounts.y, data.ProbesCounts.z * data.CascadesCount) * probeTexelSize;
uv /= textureSize;
float3 radiance = probesRadiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb;
// Box-blur filter for roughness (not physically correct, but cheap)
float2 uvBase = float2(coords.x * probeTexelSize, coords.y * probeTexelSize) + 1; // The first texel of the probe (skip 1px padding)
float2 uvMin = uvBase / textureSize;
float2 uvMax = (uvBase + float2(DDGI_PROBE_RESOLUTION_RADIANCE, DDGI_PROBE_RESOLUTION_RADIANCE)) / textureSize;
float2 filterSize = (float2)1.0f / textureSize;
float3 radiance00 = probesRadiance.SampleLevel(SamplerLinearClamp, clamp(uv - filterSize, uvMin, uvMax), 0).rgb;
float3 radiance10 = probesRadiance.SampleLevel(SamplerLinearClamp, clamp(uv + float2(filterSize.x, -filterSize.y), uvMin, uvMax), 0).rgb;
float3 radiance01 = probesRadiance.SampleLevel(SamplerLinearClamp, clamp(uv + float2(-filterSize.x, filterSize.y), uvMin, uvMax), 0).rgb;
float3 radiance11 = probesRadiance.SampleLevel(SamplerLinearClamp, clamp(uv + filterSize, uvMin, uvMax), 0).rgb;
float3 radianceRough = (radiance00 + radiance10 + radiance01 + radiance11) * 0.25f;
float alpha = (roughness - 0.4f) / 0.6f; // Remap [0.4; 1] to [0; 1]
radiance = lerp(radiance, radianceRough, saturate(alpha));
return radiance;
}
float3 SampleDDGISpecularCascade(DDGIData data, Texture2D<snorm float4> probesData, Texture2D<float4> probesDistance, Texture2D<float4> probesRadiance, float3 worldPosition, float3 worldNormal, float roughness, float3 reflection, DDGICascadeSampling cascade)
{
bool invalidCascade = cascade.CascadeIndex >= data.CascadesCount;
cascade.CascadeIndex = min(cascade.CascadeIndex, data.CascadesCount - 1);
@@ -365,10 +390,7 @@ float3 SampleDDGISpecularCascade(DDGIData data, Texture2D<snorm float4> probesDa
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;
return FilterProbeRadiance(data, probesRadiance, roughness, cascade.CascadeIndex, 0, reflection);
}
#endif
@@ -390,11 +412,7 @@ float3 SampleDDGISpecularCascade(DDGIData data, Texture2D<snorm float4> probesDa
sampleVector += worldNoise;
// 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?
float3 probeRadiance = FilterProbeRadiance(data, probesRadiance, roughness, cascade.CascadeIndex, probe.ProbeIndex, sampleVector);
// Accumulate weighted radiance
totalRadiance += float4(probeRadiance * probe.Weights.x, probe.Weights.x);
@@ -516,7 +534,7 @@ float3 SampleDDGISpecular(DDGIData data, Texture2D<snorm float4> probesData, Tex
// Sample cascade
float3 reflection = reflect(normalize(worldPosition - data.ViewPos), worldNormal);
float3 specular = SampleDDGISpecularCascade(data, probesData, probesDistance, probesRadiance, worldPosition, worldNormal, reflection, cascade);
float3 specular = SampleDDGISpecularCascade(data, probesData, probesDistance, probesRadiance, worldPosition, worldNormal, roughness, reflection, cascade);
return specular;
}
+3 -4
View File
@@ -52,9 +52,8 @@ float3 ViewDir;
float TestValue;
float3 QuantizationError;
uint FrameIndexMod8;
float2 ResolveDitherScale;
float2 Padding0;
float ResolveDitherScaleIndirect;
float ResolveDitherScaleSpecular;
META_CB_END
META_CB_BEGIN(1, Data1)
@@ -994,7 +993,7 @@ float4 PS_IndirectLighting(Quad_VS2PS input) : SV_Target0
return float4(0, 0, 0, 0);
// Sample irradiance
float dither = DDGI_GET_DITHER * ResolveDitherScaleIndirect;
float dither = DDGI_GET_DITHER * ResolveDitherScale.x;
float3 samplePos = DDGI_GET_SAMPLE_POS;
float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesData, ProbesDistance, ProbesIrradiance, samplePos, gBuffer.Normal, DDGI_DEFAULT_BIAS, dither);
@@ -1021,7 +1020,7 @@ float4 PS_SpecularLighting(Quad_VS2PS input) : SV_Target0
return float4(0, 0, 0, 0);
// Sample specular reflection
float dither = DDGI_GET_DITHER * ResolveDitherScaleSpecular;
float dither = DDGI_GET_DITHER * ResolveDitherScale.y;
float3 samplePos = DDGI_GET_SAMPLE_POS;
float3 specular = SampleDDGISpecular(DDGI, ProbesData, ProbesDistance, ProbesRadiance, samplePos, gBuffer.Normal, gBuffer.Roughness, DDGI_DEFAULT_BIAS, dither);