From a9bddfa7841a13a3f699299bdaf6599a994224c1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 27 Jan 2026 23:24:47 +0100 Subject: [PATCH] Fix Volumetric Fog sampling to use the same code path for depth slices conversion --- .../Features/ForwardShading.hlsl | 2 +- .../MaterialTemplates/VolumeParticle.shader | 3 +- .../Materials/MaterialShaderFeatures.cpp | 22 +------- .../VolumeParticleMaterialShader.cpp | 3 +- .../Level/Actors/ExponentialHeightFog.cpp | 10 ++-- Source/Engine/Renderer/Config.h | 2 + Source/Engine/Renderer/RenderList.cpp | 23 +++++++- Source/Engine/Renderer/RenderList.h | 15 +++++- Source/Engine/Renderer/Renderer.cpp | 4 +- Source/Engine/Renderer/VolumetricFogPass.cpp | 52 +++++++++---------- Source/Engine/Renderer/VolumetricFogPass.h | 10 +--- Source/Shaders/ExponentialHeightFog.hlsl | 2 + Source/Shaders/Fog.shader | 2 +- Source/Shaders/VolumetricFog.hlsl | 9 ++-- Source/Shaders/VolumetricFog.shader | 3 +- 15 files changed, 84 insertions(+), 78 deletions(-) diff --git a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl index 7c11eb0ac..fe8963e71 100644 --- a/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl +++ b/Content/Editor/MaterialTemplates/Features/ForwardShading.hlsl @@ -164,7 +164,7 @@ void PS_Forward( { // Sample volumetric fog and mix it in float2 screenUV = materialInput.SvPosition.xy * ScreenSize.zw; - float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, materialInput.WorldPosition - ViewPos, ExponentialHeightFog.VolumetricFogMaxDistance, screenUV); + float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, ExponentialHeightFog.VolumetricFogGrid, materialInput.WorldPosition - ViewPos, screenUV); fog = CombineVolumetricFog(fog, volumetricFog); } diff --git a/Content/Editor/MaterialTemplates/VolumeParticle.shader b/Content/Editor/MaterialTemplates/VolumeParticle.shader index 59d964101..c415228ee 100644 --- a/Content/Editor/MaterialTemplates/VolumeParticle.shader +++ b/Content/Editor/MaterialTemplates/VolumeParticle.shader @@ -22,8 +22,7 @@ float Dummy0; float VolumetricFogMaxDistance; int ParticleStride; int ParticleIndex; -float3 GridSliceParameters; -float Dummy1; +float4 GridSliceParameters; @1META_CB_END // Particles attributes buffer diff --git a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp index 19f2042f4..59f0aca79 100644 --- a/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp +++ b/Source/Engine/Graphics/Materials/MaterialShaderFeatures.cpp @@ -29,26 +29,8 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, SpanFog) - { - cache->Fog->GetExponentialHeightFogData(view, data.ExponentialHeightFog); - VolumetricFogOptions volumetricFog; - cache->Fog->GetVolumetricFogOptions(volumetricFog); - if (volumetricFog.UseVolumetricFog() && params.RenderContext.Buffers->VolumetricFog) - volumetricFogTexture = params.RenderContext.Buffers->VolumetricFog->ViewVolume(); - else - data.ExponentialHeightFog.VolumetricFogMaxDistance = -1.0f; - } - else - { - data.ExponentialHeightFog.FogMinOpacity = 1.0f; - data.ExponentialHeightFog.FogDensity = 0.0f; - data.ExponentialHeightFog.FogCutoffDistance = 0.1f; - data.ExponentialHeightFog.StartDistance = 0.0f; - data.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f; - } - params.GPUContext->BindSR(volumetricFogTextureRegisterIndex, volumetricFogTexture); + data.ExponentialHeightFog = cache->Fog.ExponentialHeightFog; + params.GPUContext->BindSR(volumetricFogTextureRegisterIndex, cache->Fog.VolumetricFogTexture); // Set directional light input if (cache->DirectionalLights.HasItems()) diff --git a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp index 22d29c0be..7caf456fb 100644 --- a/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/VolumeParticleMaterialShader.cpp @@ -25,8 +25,7 @@ PACK_STRUCT(struct VolumeParticleMaterialShaderData { float VolumetricFogMaxDistance; int32 ParticleStride; int32 ParticleIndex; - Float3 GridSliceParameters; - float Dummy1; + Float4 GridSliceParameters; }); DrawPass VolumeParticleMaterialShader::GetDrawModes() const diff --git a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp index 01fc77058..181bf6aa7 100644 --- a/Source/Engine/Level/Actors/ExponentialHeightFog.cpp +++ b/Source/Engine/Level/Actors/ExponentialHeightFog.cpp @@ -63,7 +63,7 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext) } // Register for Fog Pass - renderContext.List->Fog = this; + renderContext.List->Fog.Init(renderContext.View, this); } } @@ -187,19 +187,19 @@ GPU_CB_STRUCT(Data { void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) { PROFILE_GPU_CPU("Exponential Height Fog"); - auto volumetricFogTexture = renderContext.Buffers->VolumetricFog; + auto volumetricFogTexture = renderContext.List->Fog.VolumetricFogTexture; bool useVolumetricFog = volumetricFogTexture != nullptr; // Setup shader inputs Data data; GBufferPass::SetInputs(renderContext.View, data.GBuffer); - GetExponentialHeightFogData(renderContext.View, data.ExponentialHeightFog); + data.ExponentialHeightFog = renderContext.List->Fog.ExponentialHeightFog; auto cb = _shader->GetShader()->GetCB(0); - ASSERT(cb->GetSize() == sizeof(Data)); + ASSERT_LOW_LAYER(cb->GetSize() == sizeof(Data)); context->UpdateCB(cb, &data); context->BindCB(0, cb); context->BindSR(0, renderContext.Buffers->DepthBuffer); - context->BindSR(1, volumetricFogTexture ? volumetricFogTexture->ViewVolume() : nullptr); + context->BindSR(1, volumetricFogTexture); // TODO: instead of rendering fullscreen triangle, draw quad transformed at the fog start distance (also it could use early depth discard) // TODO: or use DepthBounds to limit the fog rendering to the distance range diff --git a/Source/Engine/Renderer/Config.h b/Source/Engine/Renderer/Config.h index 302817948..a8a5529f6 100644 --- a/Source/Engine/Renderer/Config.h +++ b/Source/Engine/Renderer/Config.h @@ -42,6 +42,8 @@ GPU_CB_STRUCT(ShaderExponentialHeightFogData { float VolumetricFogMaxDistance; float DirectionalInscatteringStartDistance; float StartDistance; + + Float4 VolumetricFogGrid; }); /// diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index c7800d4ee..6f332ddce 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -179,6 +179,26 @@ void RenderEnvironmentProbeData::SetShaderData(ShaderEnvProbeData& data) const } } +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; +} + +void RenderFogData::Init(const RenderView& view, IFogRenderer* renderer) +{ + Renderer = renderer; + renderer->GetExponentialHeightFogData(view, ExponentialHeightFog); + renderer->GetVolumetricFogOptions(VolumetricFog); +} + void* RendererAllocation::Allocate(uintptr size) { PROFILE_CPU(); @@ -501,7 +521,6 @@ RenderList::RenderList(const SpawnParams& params) , Decals(64) , Sky(nullptr) , AtmosphericFog(nullptr) - , Fog(nullptr) , Blendable(32) , ObjectBuffer(0, PixelFormat::R32G32B32A32_Float, false, TEXT("Object Buffer")) , TempObjectBuffer(0, PixelFormat::R32G32B32A32_Float, false, TEXT("Object Buffer")) @@ -534,7 +553,7 @@ void RenderList::Clear() VolumetricFogParticles.Clear(); Sky = nullptr; AtmosphericFog = nullptr; - Fog = nullptr; + Fog = RenderFogData(); PostFx.Clear(); Settings = PostProcessSettings(); Blendable.Clear(); diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index b7d10dac2..ea6b2b2ac 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -175,6 +175,17 @@ struct RenderDecalData uint32 RenderLayersMask; }; +struct RenderFogData +{ + IFogRenderer* Renderer; + GPUTextureView* VolumetricFogTexture; + ShaderExponentialHeightFogData ExponentialHeightFog; + VolumetricFogOptions VolumetricFog; + + RenderFogData(); + void Init(const RenderView& view, IFogRenderer* renderer); +}; + /// /// The draw calls list types. /// @@ -409,9 +420,9 @@ public: IAtmosphericFogRenderer* AtmosphericFog; /// - /// Fog renderer proxy to use (only one per frame) + /// Fog rendering data. /// - IFogRenderer* Fog; + RenderFogData Fog; /// /// Post effects to render. diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index fd7d43c8b..fdf309d9e 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -670,12 +670,12 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont renderContext.List->AtmosphericFog->DrawFog(context, renderContext, *lightBuffer); context->ResetSR(); } - if (renderContext.List->Fog) + if (renderContext.List->Fog.Renderer) { VolumetricFogPass::Instance()->Render(renderContext); PROFILE_GPU_CPU("Fog"); - renderContext.List->Fog->DrawFog(context, renderContext, *lightBuffer); + renderContext.List->Fog.Renderer->DrawFog(context, renderContext, *lightBuffer); context->ResetSR(); } diff --git a/Source/Engine/Renderer/VolumetricFogPass.cpp b/Source/Engine/Renderer/VolumetricFogPass.cpp index 2b00470be..bcd220b51 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.cpp +++ b/Source/Engine/Renderer/VolumetricFogPass.cpp @@ -91,22 +91,23 @@ void VolumetricFogPass::Dispose() _shader = nullptr; } -Float3 GetGridSliceParameters(float fogStart, float fogEnd, int32 gridSizeZ) +Float4 GetGridSliceParameters(float fogStart, float fogEnd, int32 gridSizeZ) { + float sliceToUV = 1.0f / (float)gridSizeZ; #if VOLUMETRIC_FOG_GRID_Z_LINEAR float sliceToDepth = fogEnd / (float)gridSizeZ; - return Float3(sliceToDepth, 1.0f / sliceToDepth, 0.0f); + return Float4(sliceToDepth, 1.0f / sliceToDepth, 0.0f, sliceToUV); #else // Use logarithmic distribution for Z slices to have more resolution for close distances and less for far ones (less aliasing near camera) const float distribution = 220.0f; // Manually adjusted to give a good distribution across the range fogStart += UNITS_TO_METERS(10); // Bias start a bit for some more quality float y = (fogEnd - fogStart * Math::Exp2((float)(gridSizeZ - 1) / distribution)) / (fogEnd - fogStart); float x = (1.0f - y) / fogStart; - return Float3(x, y, distribution); + return Float4(x, y, distribution, sliceToUV); #endif } -float GetDepthFromSlice(float slice, const Float3& gridSliceParameters) +float GetDepthFromSlice(float slice, const Float4& gridSliceParameters) { #if VOLUMETRIC_FOG_GRID_Z_LINEAR return slice * gridSliceParameters.X; @@ -115,7 +116,7 @@ float GetDepthFromSlice(float slice, const Float3& gridSliceParameters) #endif } -float GetSliceFromDepth(float sceneDepth, const Float3& gridSliceParameters) +float GetSliceFromDepth(float sceneDepth, const Float4& gridSliceParameters) { #if VOLUMETRIC_FOG_GRID_Z_LINEAR return sceneDepth * gridSliceParameters.Y; @@ -135,33 +136,21 @@ struct alignas(Float4) RasterizeSphere bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context) { - const auto fog = renderContext.List->Fog; - auto& options = _cache.Options; - - // Check if already prepared for this frame + const auto& fog = renderContext.List->Fog; if (renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount) - { - if (fog) - fog->GetVolumetricFogOptions(options); return false; - } - - // Check if skip rendering - if (fog == nullptr || !renderContext.List->Setup.UseVolumetricFog || !_isSupported || checkIfSkipPass()) - { - RenderTargetPool::Release(renderContext.Buffers->VolumetricFog); - renderContext.Buffers->VolumetricFog = nullptr; - renderContext.Buffers->LastFrameVolumetricFog = 0; - return true; - } - fog->GetVolumetricFogOptions(options); - if (!options.UseVolumetricFog()) + if (fog.Renderer == nullptr || + !renderContext.List->Setup.UseVolumetricFog || + !_isSupported || + !fog.VolumetricFog.UseVolumetricFog() || + checkIfSkipPass()) { RenderTargetPool::Release(renderContext.Buffers->VolumetricFog); renderContext.Buffers->VolumetricFog = nullptr; renderContext.Buffers->LastFrameVolumetricFog = 0; return true; } + auto& options = fog.VolumetricFog; // Setup configuration _cache.FogJitter = true; @@ -220,6 +209,13 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context) _cache.Data.HistoryWeight = _cache.HistoryWeight; _cache.Data.FogParameters = options.FogParameters; _cache.Data.GridSliceParameters = GetGridSliceParameters(renderContext.View.Near, options.Distance, _cache.GridSizeZ); + /*static bool log = true; + if (log) + { + log = false; + for (int slice = 0; slice < _cache.GridSizeZ; slice++) + LOG(Error, "Slice {} -> {}", slice, GetDepthFromSlice((float)slice, _cache.Data.GridSliceParameters)); + }*/ _cache.Data.InverseSquaredLightDistanceBiasScale = _cache.InverseSquaredLightDistanceBiasScale; _cache.Data.PhaseG = options.ScatteringDistribution; _cache.Data.VolumetricFogMaxDistance = options.Distance; @@ -287,7 +283,7 @@ bool VolumetricFogPass::InitSphereRasterize(RasterizeSphere& sphere, RenderView& sphere.VolumeZBoundsMax = (uint16)Math::Clamp(furthestSliceIndex, 0.0f, _cache.GridSize.Z - 1.0f); // Cull - if ((view.Position - sphere.Center).LengthSquared() >= Math::Square(_cache.Options.Distance + sphere.Radius) || + if ((view.Position - sphere.Center).LengthSquared() >= Math::Square(_cache.Data.VolumetricFogMaxDistance + sphere.Radius) || sphere.VolumeZBoundsMin > sphere.VolumeZBoundsMax) { return true; @@ -508,7 +504,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext) // Get lights to render Array> pointLights; Array> spotLights; - float distance = _cache.Options.Distance; + float distance = cache.Data.VolumetricFogMaxDistance; for (int32 i = 0; i < renderContext.List->PointLights.Count(); i++) { const auto& light = renderContext.List->PointLights.Get()[i]; @@ -617,6 +613,10 @@ void VolumetricFogPass::Render(RenderContext& renderContext) } renderContext.Buffers->LastFrameVolumetricFog = Engine::FrameCount; + // Update fog to be used by other passes + renderContext.List->Fog.VolumetricFogTexture = integratedLightScattering->ViewVolume(); + renderContext.List->Fog.ExponentialHeightFog.VolumetricFogGrid = cache.Data.GridSliceParameters; + groupCountX = Math::DivideAndRoundUp((int32)cache.GridSize.X, VolumetricFogIntegrationGroupSize); groupCountY = Math::DivideAndRoundUp((int32)cache.GridSize.Y, VolumetricFogIntegrationGroupSize); diff --git a/Source/Engine/Renderer/VolumetricFogPass.h b/Source/Engine/Renderer/VolumetricFogPass.h index 7205d165d..e070b8de2 100644 --- a/Source/Engine/Renderer/VolumetricFogPass.h +++ b/Source/Engine/Renderer/VolumetricFogPass.h @@ -23,7 +23,7 @@ public: GPUShader* Shader; Float3 GridSize; float VolumetricFogMaxDistance; - Float3 GridSliceParameters; + Float4 GridSliceParameters; int32 ParticleIndex; }; @@ -58,8 +58,7 @@ private: float InverseSquaredLightDistanceBiasScale; Float4 FogParameters; - Float3 GridSliceParameters; - float Dummy1; + Float4 GridSliceParameters; Matrix PrevWorldToClip; @@ -133,11 +132,6 @@ private: float SphereRasterizeRadiusBias; - /// - /// Fog options(from renderer). - /// - VolumetricFogOptions Options; - /// /// The cached per-frame data for the constant buffer. /// diff --git a/Source/Shaders/ExponentialHeightFog.hlsl b/Source/Shaders/ExponentialHeightFog.hlsl index f6fb918f5..689adfb86 100644 --- a/Source/Shaders/ExponentialHeightFog.hlsl +++ b/Source/Shaders/ExponentialHeightFog.hlsl @@ -27,6 +27,8 @@ 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 20d2a8e65..1cb49042f 100644 --- a/Source/Shaders/Fog.shader +++ b/Source/Shaders/Fog.shader @@ -46,7 +46,7 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0 #if VOLUMETRIC_FOG // Sample volumetric fog and mix it in - float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, worldPos - GBuffer.ViewPos, ExponentialHeightFog.VolumetricFogMaxDistance, input.TexCoord); + float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, ExponentialHeightFog.VolumetricFogGrid, worldPos - GBuffer.ViewPos, input.TexCoord); fog = CombineVolumetricFog(fog, volumetricFog); #endif diff --git a/Source/Shaders/VolumetricFog.hlsl b/Source/Shaders/VolumetricFog.hlsl index db5895b94..81043e1ed 100644 --- a/Source/Shaders/VolumetricFog.hlsl +++ b/Source/Shaders/VolumetricFog.hlsl @@ -5,7 +5,7 @@ #define VOLUMETRIC_FOG_GRID_Z_LINEAR 1 -float GetDepthFromSlice(float3 gridSliceParameters, float zSlice) +float GetDepthFromSlice(float4 gridSliceParameters, float zSlice) { #if VOLUMETRIC_FOG_GRID_Z_LINEAR return zSlice * gridSliceParameters.x; @@ -14,7 +14,7 @@ float GetDepthFromSlice(float3 gridSliceParameters, float zSlice) #endif } -float GetSliceFromDepth(float3 gridSliceParameters, float sceneDepth) +float GetSliceFromDepth(float4 gridSliceParameters, float sceneDepth) { #if VOLUMETRIC_FOG_GRID_Z_LINEAR return sceneDepth * gridSliceParameters.y; @@ -23,11 +23,10 @@ float GetSliceFromDepth(float3 gridSliceParameters, float sceneDepth) #endif } -float4 SampleVolumetricFog(Texture3D volumetricFogTexture, float3 viewVector, float maxDistance, float2 uv) +float4 SampleVolumetricFog(Texture3D volumetricFogTexture, float4 gridSliceParameters, float3 viewVector, float2 uv) { float sceneDepth = length(viewVector); - float zSlice = sceneDepth / maxDistance; - // TODO: use GetSliceFromDepth instead to handle non-linear depth distributions + float zSlice = GetSliceFromDepth(gridSliceParameters, sceneDepth) * gridSliceParameters.w; float3 volumeUV = float3(uv, zSlice); return volumetricFogTexture.SampleLevel(SamplerLinearClamp, volumeUV, 0); } diff --git a/Source/Shaders/VolumetricFog.shader b/Source/Shaders/VolumetricFog.shader index ae117a7d0..49c198c64 100644 --- a/Source/Shaders/VolumetricFog.shader +++ b/Source/Shaders/VolumetricFog.shader @@ -52,8 +52,7 @@ float VolumetricFogMaxDistance; float InverseSquaredLightDistanceBiasScale; float4 FogParameters; -float3 GridSliceParameters; -float Dummy1; +float4 GridSliceParameters; float4x4 PrevWorldToClip;