diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index b0d587c8d..effbe6e1b 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -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; diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h index 18357a13a..5f0dc23dc 100644 --- a/Source/Engine/Graphics/RenderTools.h +++ b/Source/Engine/Graphics/RenderTools.h @@ -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. diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp index a954cf31f..95bcb18e9 100644 --- a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -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); diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader index daad2018d..3dbfd1ed5 100644 --- a/Source/Shaders/GI/DDGI.shader +++ b/Source/Shaders/GI/DDGI.shader @@ -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(); diff --git a/Source/Shaders/Noise.hlsl b/Source/Shaders/Noise.hlsl index dc35f1efc..df5a041fa 100644 --- a/Source/Shaders/Noise.hlsl +++ b/Source/Shaders/Noise.hlsl @@ -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/