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

This commit is contained in:
2026-01-27 23:24:47 +01:00
parent f9b784a42a
commit a9bddfa784
15 changed files with 84 additions and 78 deletions
@@ -164,7 +164,7 @@ void PS_Forward(
{ {
// Sample volumetric fog and mix it in // Sample volumetric fog and mix it in
float2 screenUV = materialInput.SvPosition.xy * ScreenSize.zw; 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); fog = CombineVolumetricFog(fog, volumetricFog);
} }
@@ -22,8 +22,7 @@ float Dummy0;
float VolumetricFogMaxDistance; float VolumetricFogMaxDistance;
int ParticleStride; int ParticleStride;
int ParticleIndex; int ParticleIndex;
float3 GridSliceParameters; float4 GridSliceParameters;
float Dummy1;
@1META_CB_END @1META_CB_END
// Particles attributes buffer // Particles attributes buffer
@@ -29,26 +29,8 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
const bool canUseShadow = view.Pass != DrawPass::Depth; const bool canUseShadow = view.Pass != DrawPass::Depth;
// Set fog input // Set fog input
GPUTextureView* volumetricFogTexture = nullptr; data.ExponentialHeightFog = cache->Fog.ExponentialHeightFog;
if (cache->Fog) params.GPUContext->BindSR(volumetricFogTextureRegisterIndex, cache->Fog.VolumetricFogTexture);
{
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);
// Set directional light input // Set directional light input
if (cache->DirectionalLights.HasItems()) if (cache->DirectionalLights.HasItems())
@@ -25,8 +25,7 @@ PACK_STRUCT(struct VolumeParticleMaterialShaderData {
float VolumetricFogMaxDistance; float VolumetricFogMaxDistance;
int32 ParticleStride; int32 ParticleStride;
int32 ParticleIndex; int32 ParticleIndex;
Float3 GridSliceParameters; Float4 GridSliceParameters;
float Dummy1;
}); });
DrawPass VolumeParticleMaterialShader::GetDrawModes() const DrawPass VolumeParticleMaterialShader::GetDrawModes() const
@@ -63,7 +63,7 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext)
} }
// Register for Fog Pass // 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) void ExponentialHeightFog::DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output)
{ {
PROFILE_GPU_CPU("Exponential Height Fog"); PROFILE_GPU_CPU("Exponential Height Fog");
auto volumetricFogTexture = renderContext.Buffers->VolumetricFog; auto volumetricFogTexture = renderContext.List->Fog.VolumetricFogTexture;
bool useVolumetricFog = volumetricFogTexture != nullptr; bool useVolumetricFog = volumetricFogTexture != nullptr;
// Setup shader inputs // Setup shader inputs
Data data; Data data;
GBufferPass::SetInputs(renderContext.View, data.GBuffer); GBufferPass::SetInputs(renderContext.View, data.GBuffer);
GetExponentialHeightFogData(renderContext.View, data.ExponentialHeightFog); data.ExponentialHeightFog = renderContext.List->Fog.ExponentialHeightFog;
auto cb = _shader->GetShader()->GetCB(0); auto cb = _shader->GetShader()->GetCB(0);
ASSERT(cb->GetSize() == sizeof(Data)); ASSERT_LOW_LAYER(cb->GetSize() == sizeof(Data));
context->UpdateCB(cb, &data); context->UpdateCB(cb, &data);
context->BindCB(0, cb); context->BindCB(0, cb);
context->BindSR(0, renderContext.Buffers->DepthBuffer); 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: 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 // TODO: or use DepthBounds to limit the fog rendering to the distance range
+2
View File
@@ -42,6 +42,8 @@ GPU_CB_STRUCT(ShaderExponentialHeightFogData {
float VolumetricFogMaxDistance; float VolumetricFogMaxDistance;
float DirectionalInscatteringStartDistance; float DirectionalInscatteringStartDistance;
float StartDistance; float StartDistance;
Float4 VolumetricFogGrid;
}); });
/// <summary> /// <summary>
+21 -2
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) void* RendererAllocation::Allocate(uintptr size)
{ {
PROFILE_CPU(); PROFILE_CPU();
@@ -501,7 +521,6 @@ RenderList::RenderList(const SpawnParams& params)
, Decals(64) , Decals(64)
, Sky(nullptr) , Sky(nullptr)
, AtmosphericFog(nullptr) , AtmosphericFog(nullptr)
, Fog(nullptr)
, Blendable(32) , Blendable(32)
, ObjectBuffer(0, PixelFormat::R32G32B32A32_Float, false, TEXT("Object Buffer")) , ObjectBuffer(0, PixelFormat::R32G32B32A32_Float, false, TEXT("Object Buffer"))
, TempObjectBuffer(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(); VolumetricFogParticles.Clear();
Sky = nullptr; Sky = nullptr;
AtmosphericFog = nullptr; AtmosphericFog = nullptr;
Fog = nullptr; Fog = RenderFogData();
PostFx.Clear(); PostFx.Clear();
Settings = PostProcessSettings(); Settings = PostProcessSettings();
Blendable.Clear(); Blendable.Clear();
+13 -2
View File
@@ -175,6 +175,17 @@ struct RenderDecalData
uint32 RenderLayersMask; uint32 RenderLayersMask;
}; };
struct RenderFogData
{
IFogRenderer* Renderer;
GPUTextureView* VolumetricFogTexture;
ShaderExponentialHeightFogData ExponentialHeightFog;
VolumetricFogOptions VolumetricFog;
RenderFogData();
void Init(const RenderView& view, IFogRenderer* renderer);
};
/// <summary> /// <summary>
/// The draw calls list types. /// The draw calls list types.
/// </summary> /// </summary>
@@ -409,9 +420,9 @@ public:
IAtmosphericFogRenderer* AtmosphericFog; IAtmosphericFogRenderer* AtmosphericFog;
/// <summary> /// <summary>
/// Fog renderer proxy to use (only one per frame) /// Fog rendering data.
/// </summary> /// </summary>
IFogRenderer* Fog; RenderFogData Fog;
/// <summary> /// <summary>
/// Post effects to render. /// Post effects to render.
+2 -2
View File
@@ -670,12 +670,12 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
renderContext.List->AtmosphericFog->DrawFog(context, renderContext, *lightBuffer); renderContext.List->AtmosphericFog->DrawFog(context, renderContext, *lightBuffer);
context->ResetSR(); context->ResetSR();
} }
if (renderContext.List->Fog) if (renderContext.List->Fog.Renderer)
{ {
VolumetricFogPass::Instance()->Render(renderContext); VolumetricFogPass::Instance()->Render(renderContext);
PROFILE_GPU_CPU("Fog"); PROFILE_GPU_CPU("Fog");
renderContext.List->Fog->DrawFog(context, renderContext, *lightBuffer); renderContext.List->Fog.Renderer->DrawFog(context, renderContext, *lightBuffer);
context->ResetSR(); context->ResetSR();
} }
+26 -26
View File
@@ -91,22 +91,23 @@ void VolumetricFogPass::Dispose()
_shader = nullptr; _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 #if VOLUMETRIC_FOG_GRID_Z_LINEAR
float sliceToDepth = fogEnd / (float)gridSizeZ; float sliceToDepth = fogEnd / (float)gridSizeZ;
return Float3(sliceToDepth, 1.0f / sliceToDepth, 0.0f); return Float4(sliceToDepth, 1.0f / sliceToDepth, 0.0f, sliceToUV);
#else #else
// Use logarithmic distribution for Z slices to have more resolution for close distances and less for far ones (less aliasing near camera) // 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 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 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 y = (fogEnd - fogStart * Math::Exp2((float)(gridSizeZ - 1) / distribution)) / (fogEnd - fogStart);
float x = (1.0f - y) / fogStart; float x = (1.0f - y) / fogStart;
return Float3(x, y, distribution); return Float4(x, y, distribution, sliceToUV);
#endif #endif
} }
float GetDepthFromSlice(float slice, const Float3& gridSliceParameters) float GetDepthFromSlice(float slice, const Float4& gridSliceParameters)
{ {
#if VOLUMETRIC_FOG_GRID_Z_LINEAR #if VOLUMETRIC_FOG_GRID_Z_LINEAR
return slice * gridSliceParameters.X; return slice * gridSliceParameters.X;
@@ -115,7 +116,7 @@ float GetDepthFromSlice(float slice, const Float3& gridSliceParameters)
#endif #endif
} }
float GetSliceFromDepth(float sceneDepth, const Float3& gridSliceParameters) float GetSliceFromDepth(float sceneDepth, const Float4& gridSliceParameters)
{ {
#if VOLUMETRIC_FOG_GRID_Z_LINEAR #if VOLUMETRIC_FOG_GRID_Z_LINEAR
return sceneDepth * gridSliceParameters.Y; return sceneDepth * gridSliceParameters.Y;
@@ -135,33 +136,21 @@ struct alignas(Float4) RasterizeSphere
bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context) bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context)
{ {
const auto fog = renderContext.List->Fog; const auto& fog = renderContext.List->Fog;
auto& options = _cache.Options;
// Check if already prepared for this frame
if (renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount) if (renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount)
{
if (fog)
fog->GetVolumetricFogOptions(options);
return false; return false;
} if (fog.Renderer == nullptr ||
!renderContext.List->Setup.UseVolumetricFog ||
// Check if skip rendering !_isSupported ||
if (fog == nullptr || !renderContext.List->Setup.UseVolumetricFog || !_isSupported || checkIfSkipPass()) !fog.VolumetricFog.UseVolumetricFog() ||
{ checkIfSkipPass())
RenderTargetPool::Release(renderContext.Buffers->VolumetricFog);
renderContext.Buffers->VolumetricFog = nullptr;
renderContext.Buffers->LastFrameVolumetricFog = 0;
return true;
}
fog->GetVolumetricFogOptions(options);
if (!options.UseVolumetricFog())
{ {
RenderTargetPool::Release(renderContext.Buffers->VolumetricFog); RenderTargetPool::Release(renderContext.Buffers->VolumetricFog);
renderContext.Buffers->VolumetricFog = nullptr; renderContext.Buffers->VolumetricFog = nullptr;
renderContext.Buffers->LastFrameVolumetricFog = 0; renderContext.Buffers->LastFrameVolumetricFog = 0;
return true; return true;
} }
auto& options = fog.VolumetricFog;
// Setup configuration // Setup configuration
_cache.FogJitter = true; _cache.FogJitter = true;
@@ -220,6 +209,13 @@ bool VolumetricFogPass::Init(RenderContext& renderContext, GPUContext* context)
_cache.Data.HistoryWeight = _cache.HistoryWeight; _cache.Data.HistoryWeight = _cache.HistoryWeight;
_cache.Data.FogParameters = options.FogParameters; _cache.Data.FogParameters = options.FogParameters;
_cache.Data.GridSliceParameters = GetGridSliceParameters(renderContext.View.Near, options.Distance, _cache.GridSizeZ); _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.InverseSquaredLightDistanceBiasScale = _cache.InverseSquaredLightDistanceBiasScale;
_cache.Data.PhaseG = options.ScatteringDistribution; _cache.Data.PhaseG = options.ScatteringDistribution;
_cache.Data.VolumetricFogMaxDistance = options.Distance; _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); sphere.VolumeZBoundsMax = (uint16)Math::Clamp(furthestSliceIndex, 0.0f, _cache.GridSize.Z - 1.0f);
// Cull // 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) sphere.VolumeZBoundsMin > sphere.VolumeZBoundsMax)
{ {
return true; return true;
@@ -508,7 +504,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
// Get lights to render // Get lights to render
Array<uint16, InlinedAllocation<64, RendererAllocation>> pointLights; Array<uint16, InlinedAllocation<64, RendererAllocation>> pointLights;
Array<uint16, InlinedAllocation<64, RendererAllocation>> spotLights; 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++) for (int32 i = 0; i < renderContext.List->PointLights.Count(); i++)
{ {
const auto& light = renderContext.List->PointLights.Get()[i]; const auto& light = renderContext.List->PointLights.Get()[i];
@@ -617,6 +613,10 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
} }
renderContext.Buffers->LastFrameVolumetricFog = Engine::FrameCount; 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); groupCountX = Math::DivideAndRoundUp((int32)cache.GridSize.X, VolumetricFogIntegrationGroupSize);
groupCountY = Math::DivideAndRoundUp((int32)cache.GridSize.Y, VolumetricFogIntegrationGroupSize); groupCountY = Math::DivideAndRoundUp((int32)cache.GridSize.Y, VolumetricFogIntegrationGroupSize);
+2 -8
View File
@@ -23,7 +23,7 @@ public:
GPUShader* Shader; GPUShader* Shader;
Float3 GridSize; Float3 GridSize;
float VolumetricFogMaxDistance; float VolumetricFogMaxDistance;
Float3 GridSliceParameters; Float4 GridSliceParameters;
int32 ParticleIndex; int32 ParticleIndex;
}; };
@@ -58,8 +58,7 @@ private:
float InverseSquaredLightDistanceBiasScale; float InverseSquaredLightDistanceBiasScale;
Float4 FogParameters; Float4 FogParameters;
Float3 GridSliceParameters; Float4 GridSliceParameters;
float Dummy1;
Matrix PrevWorldToClip; Matrix PrevWorldToClip;
@@ -133,11 +132,6 @@ private:
float SphereRasterizeRadiusBias; float SphereRasterizeRadiusBias;
/// <summary>
/// Fog options(from renderer).
/// </summary>
VolumetricFogOptions Options;
/// <summary> /// <summary>
/// The cached per-frame data for the constant buffer. /// The cached per-frame data for the constant buffer.
/// </summary> /// </summary>
+2
View File
@@ -27,6 +27,8 @@ struct ExponentialHeightFogData
float VolumetricFogMaxDistance; float VolumetricFogMaxDistance;
float DirectionalInscatteringStartDistance; float DirectionalInscatteringStartDistance;
float StartDistance; float StartDistance;
float4 VolumetricFogGrid;
}; };
float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance, float sceneDistance) float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance, float sceneDistance)
+1 -1
View File
@@ -46,7 +46,7 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0
#if VOLUMETRIC_FOG #if VOLUMETRIC_FOG
// Sample volumetric fog and mix it in // 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); fog = CombineVolumetricFog(fog, volumetricFog);
#endif #endif
+4 -5
View File
@@ -5,7 +5,7 @@
#define VOLUMETRIC_FOG_GRID_Z_LINEAR 1 #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 #if VOLUMETRIC_FOG_GRID_Z_LINEAR
return zSlice * gridSliceParameters.x; return zSlice * gridSliceParameters.x;
@@ -14,7 +14,7 @@ float GetDepthFromSlice(float3 gridSliceParameters, float zSlice)
#endif #endif
} }
float GetSliceFromDepth(float3 gridSliceParameters, float sceneDepth) float GetSliceFromDepth(float4 gridSliceParameters, float sceneDepth)
{ {
#if VOLUMETRIC_FOG_GRID_Z_LINEAR #if VOLUMETRIC_FOG_GRID_Z_LINEAR
return sceneDepth * gridSliceParameters.y; return sceneDepth * gridSliceParameters.y;
@@ -23,11 +23,10 @@ float GetSliceFromDepth(float3 gridSliceParameters, float sceneDepth)
#endif #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 sceneDepth = length(viewVector);
float zSlice = sceneDepth / maxDistance; float zSlice = GetSliceFromDepth(gridSliceParameters, sceneDepth) * gridSliceParameters.w;
// TODO: use GetSliceFromDepth instead to handle non-linear depth distributions
float3 volumeUV = float3(uv, zSlice); float3 volumeUV = float3(uv, zSlice);
return volumetricFogTexture.SampleLevel(SamplerLinearClamp, volumeUV, 0); return volumetricFogTexture.SampleLevel(SamplerLinearClamp, volumeUV, 0);
} }
+1 -2
View File
@@ -52,8 +52,7 @@ float VolumetricFogMaxDistance;
float InverseSquaredLightDistanceBiasScale; float InverseSquaredLightDistanceBiasScale;
float4 FogParameters; float4 FogParameters;
float3 GridSliceParameters; float4 GridSliceParameters;
float Dummy1;
float4x4 PrevWorldToClip; float4x4 PrevWorldToClip;