Fix Volumetric Fog sampling to use the same code path for depth slices conversion

This commit is contained in:
Wojtek Figat
2026-01-27 23:24:47 +01:00
parent f9b784a42a
commit a9bddfa784
15 changed files with 84 additions and 78 deletions

View File

@@ -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);
}

View File

@@ -22,8 +22,7 @@ float Dummy0;
float VolumetricFogMaxDistance;
int ParticleStride;
int ParticleIndex;
float3 GridSliceParameters;
float Dummy1;
float4 GridSliceParameters;
@1META_CB_END
// Particles attributes buffer

View File

@@ -29,26 +29,8 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
const bool canUseShadow = view.Pass != DrawPass::Depth;
// Set fog input
GPUTextureView* volumetricFogTexture = nullptr;
if (cache->Fog)
{
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())

View File

@@ -25,8 +25,7 @@ PACK_STRUCT(struct VolumeParticleMaterialShaderData {
float VolumetricFogMaxDistance;
int32 ParticleStride;
int32 ParticleIndex;
Float3 GridSliceParameters;
float Dummy1;
Float4 GridSliceParameters;
});
DrawPass VolumeParticleMaterialShader::GetDrawModes() const

View File

@@ -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

View File

@@ -42,6 +42,8 @@ GPU_CB_STRUCT(ShaderExponentialHeightFogData {
float VolumetricFogMaxDistance;
float DirectionalInscatteringStartDistance;
float StartDistance;
Float4 VolumetricFogGrid;
});
/// <summary>

View File

@@ -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();

View File

@@ -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);
};
/// <summary>
/// The draw calls list types.
/// </summary>
@@ -409,9 +420,9 @@ public:
IAtmosphericFogRenderer* AtmosphericFog;
/// <summary>
/// Fog renderer proxy to use (only one per frame)
/// Fog rendering data.
/// </summary>
IFogRenderer* Fog;
RenderFogData Fog;
/// <summary>
/// Post effects to render.

View File

@@ -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();
}

View File

@@ -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<uint16, InlinedAllocation<64, RendererAllocation>> pointLights;
Array<uint16, InlinedAllocation<64, RendererAllocation>> 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);

View File

@@ -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;
/// <summary>
/// Fog options(from renderer).
/// </summary>
VolumetricFogOptions Options;
/// <summary>
/// The cached per-frame data for the constant buffer.
/// </summary>

View File

@@ -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)

View File

@@ -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

View File

@@ -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);
}

View File

@@ -52,8 +52,7 @@ float VolumetricFogMaxDistance;
float InverseSquaredLightDistanceBiasScale;
float4 FogParameters;
float3 GridSliceParameters;
float Dummy1;
float4 GridSliceParameters;
float4x4 PrevWorldToClip;