From fa428e343b931fc1bfdc685d556b728cc72254b1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 28 Jan 2026 08:39:56 +0100 Subject: [PATCH] Add dithering to Volumetric Fog to reduce aliasing --- .../Features/ForwardShading.hlsl | 4 +++- .../Materials/MaterialShaderFeatures.cpp | 3 ++- .../Materials/MaterialShaderFeatures.h | 1 + .../Level/Actors/ExponentialHeightFog.cpp | 6 ++++- Source/Engine/Renderer/Config.h | 9 +++++++- Source/Engine/Renderer/RenderList.cpp | 17 +++++++------- Source/Engine/Renderer/RenderList.h | 3 ++- Source/Engine/Renderer/VolumetricFogPass.cpp | 5 ++++- Source/Shaders/ExponentialHeightFog.hlsl | 2 -- Source/Shaders/Fog.shader | 4 +++- Source/Shaders/VolumetricFog.hlsl | 22 +++++++++++++++++-- 11 files changed, 57 insertions(+), 19 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index fe8963e71..a10a573ff 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -20,6 +20,7 @@ LightData DirectionalLight; LightData SkyLight; EnvProbeData EnvironmentProbe; ExponentialHeightFogData ExponentialHeightFog; +VolumetricFogData VolumetricFog; float3 Dummy2; uint LocalLightsCount; LightData LocalLights[MAX_LOCAL_LIGHTS]; @@ -35,6 +36,7 @@ LightData GetDirectionalLight() { return DirectionalLight; } LightData GetSkyLight() { return SkyLight; } EnvProbeData GetEnvironmentProbe() { return EnvironmentProbe; } ExponentialHeightFogData GetExponentialHeightFog() { return ExponentialHeightFog; } +VolumetricFogData GetVolumetricFog() { return VolumetricFog; } uint GetLocalLightsCount() { return LocalLightsCount; } LightData GetLocalLight(uint i) { return LocalLights[i]; } @5// Forward Shading: Shaders @@ -164,7 +166,7 @@ void PS_Forward( { // Sample volumetric fog and mix it in float2 screenUV = materialInput.SvPosition.xy * ScreenSize.zw; - float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, ExponentialHeightFog.VolumetricFogGrid, materialInput.WorldPosition - ViewPos, screenUV); + float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, VolumetricFog, materialInput.WorldPosition - ViewPos, screenUV, TemporalAAJitter); fog = CombineVolumetricFog(fog, volumetricFog); } diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 59f0aca79..fd3a8b20f 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -29,7 +29,8 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanFog.ExponentialHeightFog; + data.ExponentialHeightFog = cache->Fog.ExponentialHeightFogData; + data.VolumetricFogData = cache->Fog.VolumetricFogData; params.GPUContext->BindSR(volumetricFogTextureRegisterIndex, cache->Fog.VolumetricFogTexture); // Set directional light input diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h index 54b91af23..a7d1a5f85 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.h @@ -33,6 +33,7 @@ struct ForwardShadingFeature : MaterialShaderFeature ShaderLightData SkyLight; ShaderEnvProbeData EnvironmentProbe; ShaderExponentialHeightFogData ExponentialHeightFog; + ShaderVolumetricFogData VolumetricFogData; Float3 Dummy2; uint32 LocalLightsCount; ShaderLightData LocalLights[MaxLocalLights]; diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 181bf6aa7..2a89771c9 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -182,6 +182,8 @@ void ExponentialHeightFog::GetExponentialHeightFogData(const RenderView& view, S GPU_CB_STRUCT(Data { ShaderGBufferData GBuffer; ShaderExponentialHeightFogData ExponentialHeightFog; + ShaderVolumetricFogData VolumetricFog; + Float4 TemporalAAJitter; }); void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) @@ -193,7 +195,9 @@ void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderCon // Setup shader inputs Data data; GBufferPass::SetInputs(renderContext.View, data.GBuffer); - data.ExponentialHeightFog = renderContext.List->Fog.ExponentialHeightFog; + data.ExponentialHeightFog = renderContext.List->Fog.ExponentialHeightFogData; + data.VolumetricFog = renderContext.List->Fog.VolumetricFogData; + data.TemporalAAJitter = renderContext.View.TemporalAAJitter; auto cb = _shader->GetShader()->GetCB(0); ASSERT_LOW_LAYER(cb->GetSize() == sizeof(Data)); context->UpdateCB(cb, &data); diff --git a/Source/Engine/Renderer/Config.h b/Source/Engine/Renderer/Config.h index a8a5529f6..f4885e366 100644 --- a/Source/Engine/Renderer/Config.h +++ b/Source/Engine/Renderer/Config.h @@ -42,8 +42,15 @@ GPU_CB_STRUCT(ShaderExponentialHeightFogData { float VolumetricFogMaxDistance; float DirectionalInscatteringStartDistance; float StartDistance; + }); - Float4 VolumetricFogGrid; +/// +/// Structure that contains information about volumetric fog +/// +GPU_CB_STRUCT(ShaderVolumetricFogData { + Float4 GridSliceParameters; + Float2 ScreenSize; + Float2 VolumeTexelSize; // Scaled for dithering }); /// diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 6f332ddce..919bd8eae 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -183,19 +183,20 @@ RenderFogData::RenderFogData() { Renderer = nullptr; VolumetricFogTexture = nullptr; - ExponentialHeightFog.FogMinOpacity = 1.0f; - ExponentialHeightFog.FogDensity = 0.0f; - ExponentialHeightFog.FogCutoffDistance = 0.1f; - ExponentialHeightFog.StartDistance = 0.0f; - ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; - ExponentialHeightFog.VolumetricFogMaxDistance = -1.0f; - ExponentialHeightFog.VolumetricFogGrid = Float4::One; + ExponentialHeightFogData.FogMinOpacity = 1.0f; + ExponentialHeightFogData.FogDensity = 0.0f; + ExponentialHeightFogData.FogCutoffDistance = 0.1f; + ExponentialHeightFogData.StartDistance = 0.0f; + ExponentialHeightFogData.ApplyDirectionalInscattering = 0.0f; + ExponentialHeightFogData.VolumetricFogMaxDistance = -1.0f; + VolumetricFogData.GridSliceParameters = Float4::One; + VolumetricFogData.ScreenSize = VolumetricFogData.VolumeTexelSize = Float2::Zero; } void RenderFogData::Init(const RenderView& view, IFogRenderer* renderer) { Renderer = renderer; - renderer->GetExponentialHeightFogData(view, ExponentialHeightFog); + renderer->GetExponentialHeightFogData(view, ExponentialHeightFogData); renderer->GetVolumetricFogOptions(VolumetricFog); } diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index ea6b2b2ac..5af620e93 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -179,7 +179,8 @@ struct RenderFogData { IFogRenderer* Renderer; GPUTextureView* VolumetricFogTexture; - ShaderExponentialHeightFogData ExponentialHeightFog; + ShaderExponentialHeightFogData ExponentialHeightFogData; + ShaderVolumetricFogData VolumetricFogData; VolumetricFogOptions VolumetricFog; RenderFogData(); diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 74a029618..69d1abd3e 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -679,8 +679,11 @@ void VolumetricFogPass::Render(RenderContext& renderContext) renderContext.Buffers->LastFrameVolumetricFog = Engine::FrameCount; // Update fog to be used by other passes + const float ditherNoiseScale = 0.2f; // Scales noise in SampleVolumetricFog renderContext.List->Fog.VolumetricFogTexture = integratedLightScattering->ViewVolume(); - renderContext.List->Fog.ExponentialHeightFog.VolumetricFogGrid = cache.Data.GridSliceParameters; + renderContext.List->Fog.VolumetricFogData.GridSliceParameters = cache.Data.GridSliceParameters; + renderContext.List->Fog.VolumetricFogData.ScreenSize = renderContext.Buffers->GetSize(); + renderContext.List->Fog.VolumetricFogData.VolumeTexelSize = Float2(1.0f / cache.GridSize.X, 1.0f / cache.GridSize.Y) * ditherNoiseScale; groupCountX = Math::DivideAndRoundUp((int32)cache.GridSize.X, VolumetricFogIntegrationGroupSize); groupCountY = Math::DivideAndRoundUp((int32)cache.GridSize.Y, VolumetricFogIntegrationGroupSize); diff --git a/Source/Shaders/ExponentialHeightFog.hlsl b/Source/Shaders/ExponentialHeightFog.hlsl index 689adfb86..f6fb918f5 100644 --- a/Source/Shaders/ExponentialHeightFog.hlsl +++ b/Source/Shaders/ExponentialHeightFog.hlsl @@ -27,8 +27,6 @@ struct ExponentialHeightFogData float VolumetricFogMaxDistance; float DirectionalInscatteringStartDistance; float StartDistance; - - float4 VolumetricFogGrid; }; float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance, float sceneDistance) diff --git a/Source/Shaders/Fog.shader b/Source/Shaders/Fog.shader index 1cb49042f..1eae65d72 100644 --- a/Source/Shaders/Fog.shader +++ b/Source/Shaders/Fog.shader @@ -16,6 +16,8 @@ META_CB_BEGIN(0, Data) GBufferData GBuffer; ExponentialHeightFogData ExponentialHeightFog; +VolumetricFogData VolumetricFog; +float4 TemporalAAJitter; META_CB_END DECLARE_GBUFFERDATA_ACCESS(GBuffer) @@ -46,7 +48,7 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0 #if VOLUMETRIC_FOG // Sample volumetric fog and mix it in - float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, ExponentialHeightFog.VolumetricFogGrid, worldPos - GBuffer.ViewPos, input.TexCoord); + float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, VolumetricFog, worldPos - GBuffer.ViewPos, input.TexCoord, TemporalAAJitter); fog = CombineVolumetricFog(fog, volumetricFog); #endif diff --git a/Source/Shaders/VolumetricFog.hlsl b/Source/Shaders/VolumetricFog.hlsl index 81043e1ed..22bb71f3c 100644 --- a/Source/Shaders/VolumetricFog.hlsl +++ b/Source/Shaders/VolumetricFog.hlsl @@ -3,8 +3,18 @@ #ifndef __VOLUMETRIC_FOG__ #define __VOLUMETRIC_FOG__ +#include "./Flax/Noise.hlsl" + #define VOLUMETRIC_FOG_GRID_Z_LINEAR 1 +// Structure that contains information about volumetric fog +struct VolumetricFogData +{ + float4 GridSliceParameters; + float2 ScreenSize; + float2 VolumeTexelSize; // Scaled for dithering +}; + float GetDepthFromSlice(float4 gridSliceParameters, float zSlice) { #if VOLUMETRIC_FOG_GRID_Z_LINEAR @@ -23,11 +33,19 @@ float GetSliceFromDepth(float4 gridSliceParameters, float sceneDepth) #endif } -float4 SampleVolumetricFog(Texture3D volumetricFogTexture, float4 gridSliceParameters, float3 viewVector, float2 uv) +float4 SampleVolumetricFog(Texture3D volumetricFogTexture, VolumetricFogData volumetricFogData, float3 viewVector, float2 uv, float4 temporalAAJitter = 0) { + // Project view vector to get 3D frustum UVW coordinates float sceneDepth = length(viewVector); - float zSlice = GetSliceFromDepth(gridSliceParameters, sceneDepth) * gridSliceParameters.w; + float zSlice = GetSliceFromDepth(volumetricFogData.GridSliceParameters, sceneDepth) * volumetricFogData.GridSliceParameters.w; float3 volumeUV = float3(uv, zSlice); + + // Dither to reduce banding artifacts + float2 noiseUV = volumeUV.xy + temporalAAJitter.xy; + float2 noise = rand2dTo2d(noiseUV * volumetricFogData.ScreenSize) * 2.0f - 1.0f; + volumeUV.xy += noise * volumetricFogData.VolumeTexelSize; + + // Sample 3D texture return volumetricFogTexture.SampleLevel(SamplerLinearClamp, volumeUV, 0); }