Fix DDGI iradiance to use debanding by applying quantization error to reduce yellowish artifacts due to R11G11B10 format

This commit is contained in:
Wojtek Figat
2025-12-16 23:56:45 +01:00
parent 8bf51512ac
commit 0e76585709
5 changed files with 67 additions and 0 deletions

View File

@@ -620,6 +620,40 @@ void RenderTools::ComputeSphereModelDrawMatrix(const RenderView& view, const Flo
resultIsViewInside = Float3::DistanceSquared(view.Position, position) < Math::Square(radius * 1.1f); // Manually tweaked bias
}
Float3 RenderTools::GetColorQuantizationError(PixelFormat format)
{
Float3 mantissaBits;
switch (format)
{
case PixelFormat::R11G11B10_Float:
mantissaBits = Float3(6, 6, 5);
break;
case PixelFormat::R10G10B10A2_UNorm:
mantissaBits = Float3(10, 10, 10);
break;
case PixelFormat::R16G16B16A16_Float:
mantissaBits = Float3(16, 16, 16);
break;
case PixelFormat::R32G32B32A32_Float:
mantissaBits = Float3(23, 23, 23);
break;
case PixelFormat::R9G9B9E5_SharedExp:
mantissaBits = Float3(5, 6, 5);
break;
case PixelFormat::R8G8B8A8_UNorm:
case PixelFormat::B8G8R8A8_UNorm:
mantissaBits = Float3(8, 8, 8);
break;
default:
return Float3::Zero;
}
return {
Math::Pow(0.5f, mantissaBits.X),
Math::Pow(0.5f, mantissaBits.Y),
Math::Pow(0.5f, mantissaBits.Z)
};
}
int32 MipLevelsCount(int32 width)
{
int32 result = 1;

View File

@@ -140,6 +140,9 @@ public:
static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent);
static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside);
// Calculates error for a given render target format to reduce floating-point precision artifacts via QuantizeColor (from Noise.hlsl).
static Float3 GetColorQuantizationError(PixelFormat format);
};
// Calculates mip levels count for a texture 1D.

View File

@@ -68,6 +68,8 @@ GPU_CB_STRUCT(Data0 {
Int4 ProbeScrollClears[4];
Float3 ViewDir;
float Padding1;
Float3 QuantizationError;
int32 FrameIndexMod8;
});
GPU_CB_STRUCT(Data1 {
@@ -535,6 +537,8 @@ bool DynamicDiffuseGlobalIlluminationPass::RenderInner(RenderContext& renderCont
data.TemporalTime = renderContext.List->Setup.UseTemporalAAJitter ? RenderTools::ComputeTemporalTime() : 0.0f;
data.ViewDir = renderContext.View.Direction;
data.SkyboxIntensity = renderContext.List->Sky ? renderContext.List->Sky->GetIndirectLightingIntensity() : 1.0f;
data.QuantizationError = RenderTools::GetColorQuantizationError(ddgiData.ProbesIrradiance->Format());
data.FrameIndexMod8 = (int32)(Engine::FrameCount % 8);
GBufferPass::SetInputs(renderContext.View, data.GBuffer);
context->UpdateCB(_cb0, &data);
context->BindCB(0, _cb0);

View File

@@ -13,6 +13,7 @@
#include "./Flax/Math.hlsl"
#include "./Flax/Noise.hlsl"
#include "./Flax/Quaternion.hlsl"
#include "./Flax/MonteCarlo.hlsl"
#include "./Flax/GlobalSignDistanceField.hlsl"
#include "./Flax/GI/GlobalSurfaceAtlas.hlsl"
#include "./Flax/GI/DDGI.hlsl"
@@ -42,6 +43,8 @@ float TemporalTime;
int4 ProbeScrollClears[4];
float3 ViewDir;
float Padding1;
float3 QuantizationError;
uint FrameIndexMod8;
META_CB_END
META_CB_BEGIN(1, Data1)
@@ -704,6 +707,9 @@ void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_
result = float4(lerp(result.rg, previous.rg, historyWeight), 0.0f, 1.0f);
#endif
// Write output irradiance (apply quantization error to reduce yellowish artifacts due to R11G11B10 format)
float noise = InterleavedGradientNoise(octahedralCoords, FrameIndexMod8);
result.rgb = QuantizeColor(result.rgb, noise, QuantizationError);
RWOutput[outputCoords] = result;
GroupMemoryBarrierWithGroupSync();

View File

@@ -54,6 +54,26 @@ float2 PerlinNoiseFade(float2 t)
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
}
// "Next Generation Post Processing in Call of Duty: Advanced Warfare"
// http://advances.realtimerendering.com/s2014/index.html
float InterleavedGradientNoise(float2 uv, uint frameCount)
{
const float2 magicFrameScale = float2(47, 17) * 0.695;
uv += frameCount * magicFrameScale;
const float3 magic = float3(0.06711056, 0.00583715, 52.9829189);
return frac(magic.z * frac(dot(uv, magic.xy)));
}
// Removes error from the color to properly store it in lower precision formats (error = 2^(-mantissaBits))
float3 QuantizeColor(float3 color, float noise, float3 error)
{
float3 delta = color * error;
delta.x = asfloat(asuint(delta.x) & ~0x007fffff);
delta.y = asfloat(asuint(delta.y) & ~0x007fffff);
delta.z = asfloat(asuint(delta.z) & ~0x007fffff);
return color + delta * noise;
}
float rand2dTo1d(float2 value, float2 dotDir = float2(12.9898, 78.233))
{
// https://www.ronja-tutorials.com/post/024-white-noise/