Add Start Distance to volumetric fog to match Exponential Fog more

This commit is contained in:
2026-04-07 11:36:51 +02:00
parent 1676f41744
commit e501d10ac1
4 changed files with 39 additions and 25 deletions
Binary file not shown.
+30 -17
View File
@@ -47,8 +47,8 @@ GPU_CB_STRUCT(Data {
uint32 GridSizeIntZ; uint32 GridSizeIntZ;
float PhaseG; float PhaseG;
Float2 Dummy0; Float2 VolumetricFogRange;
float VolumetricFogMaxDistance; float Dummy0;
float InverseSquaredLightDistanceBiasScale; float InverseSquaredLightDistanceBiasScale;
Float4 FogParameters; Float4 FogParameters;
@@ -161,8 +161,9 @@ Float4 GetGridSliceParameters(float fogStart, float fogEnd, int32 gridSizeZ)
{ {
float sliceToUV = 1.0f / (float)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 - fogStart) / (float)(gridSizeZ - 1);
return Float4(sliceToDepth, 1.0f / sliceToDepth, 0.0f, sliceToUV); float sliceToDepthInv = 1.0f / sliceToDepth;
return Float4(sliceToDepth, 1.0f / sliceToDepth, -fogStart * sliceToDepthInv, 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
@@ -176,7 +177,7 @@ Float4 GetGridSliceParameters(float fogStart, float fogEnd, int32 gridSizeZ)
float GetDepthFromSlice(float slice, const Float4& 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.Z) * gridSliceParameters.X;
#else #else
return (Math::Exp2(slice / gridSliceParameters.Z) - gridSliceParameters.Y) / gridSliceParameters.X; return (Math::Exp2(slice / gridSliceParameters.Z) - gridSliceParameters.Y) / gridSliceParameters.X;
#endif #endif
@@ -185,7 +186,7 @@ float GetDepthFromSlice(float slice, const Float4& gridSliceParameters)
float GetSliceFromDepth(float sceneDepth, const Float4& 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 + gridSliceParameters.Z;
#else #else
return Math::Log2(sceneDepth * gridSliceParameters.X + gridSliceParameters.Y) * gridSliceParameters.Z; return Math::Log2(sceneDepth * gridSliceParameters.X + gridSliceParameters.Y) * gridSliceParameters.Z;
#endif #endif
@@ -205,10 +206,12 @@ bool VolumetricFogPass::Init(FrameCache& cache, RenderContext& renderContext, GP
const auto& fog = renderContext.List->Fog; const auto& fog = renderContext.List->Fog;
if (renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount) if (renderContext.Buffers->LastFrameVolumetricFog == Engine::FrameCount)
return false; return false;
float fogStart = Math::Max(renderContext.View.Near, fog.ExponentialHeightFogData.StartDistance);
if (fog.Renderer == nullptr || if (fog.Renderer == nullptr ||
!renderContext.List->Setup.UseVolumetricFog || !renderContext.List->Setup.UseVolumetricFog ||
!_isSupported || !_isSupported ||
!fog.VolumetricFog.UseVolumetricFog() || !fog.VolumetricFog.UseVolumetricFog() ||
fogStart + METERS_TO_UNITS(1) >= fog.VolumetricFog.Distance ||
checkIfSkipPass()) checkIfSkipPass())
{ {
RenderTargetPool::Release(renderContext.Buffers->VolumetricFog); RenderTargetPool::Release(renderContext.Buffers->VolumetricFog);
@@ -245,6 +248,13 @@ bool VolumetricFogPass::Init(FrameCache& cache, RenderContext& renderContext, GP
cache.MissedHistorySamplesCount = 8; cache.MissedHistorySamplesCount = 8;
break; break;
} }
auto& fogData = renderContext.Buffers->VolumetricFogData;
fogData.MaxDistance = options.Distance;
// Reduce Z resolution when fog range is smaller than reference
float referenceRangeScaleZ = METERS_TO_UNITS(60);
float fogRange = fogData.MaxDistance - fogStart;
cache.GridSizeZ = Math::CeilToInt(Math::Clamp(fogRange / referenceRangeScaleZ, 0.3f, 1.5f) * cache.GridSizeZ);
// Calculate volumetric fog size // Calculate volumetric fog size
const int32 width = renderContext.Buffers->GetWidth(); const int32 width = renderContext.Buffers->GetWidth();
@@ -265,8 +275,6 @@ bool VolumetricFogPass::Init(FrameCache& cache, RenderContext& renderContext, GP
(float)Math::DivideAndRoundUp(width, cache.GridPixelSize), (float)Math::DivideAndRoundUp(width, cache.GridPixelSize),
(float)Math::DivideAndRoundUp(height, cache.GridPixelSize), (float)Math::DivideAndRoundUp(height, cache.GridPixelSize),
(float)cache.GridSizeZ); (float)cache.GridSizeZ);
auto& fogData = renderContext.Buffers->VolumetricFogData;
fogData.MaxDistance = options.Distance;
if (renderContext.Task->IsCameraCut || if (renderContext.Task->IsCameraCut ||
renderContext.View.IsOriginTeleport() || renderContext.View.IsOriginTeleport() ||
(renderContext.Buffers->VolumetricFog && renderContext.Buffers->VolumetricFog->Size3() != cache.GridSize) || (renderContext.Buffers->VolumetricFog && renderContext.Buffers->VolumetricFog->Size3() != cache.GridSize) ||
@@ -287,17 +295,22 @@ bool VolumetricFogPass::Init(FrameCache& cache, RenderContext& renderContext, GP
cache.Data.GridSizeIntZ = (uint32)cache.GridSize.Z; cache.Data.GridSizeIntZ = (uint32)cache.GridSize.Z;
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(fogStart, fogData.MaxDistance, cache.GridSizeZ);
/*static bool log = true; /*static bool log = true;
if (log) if (log)
{ {
log = false; log = false;
LOG(Info, "Fog range: {} - {}", fogStart, fogData.MaxDistance);
for (int slice = 0; slice < cache.GridSizeZ; slice++) for (int slice = 0; slice < cache.GridSizeZ; slice++)
LOG(Error, "Slice {} -> {}", slice, GetDepthFromSlice((float)slice, cache.Data.GridSliceParameters)); {
float depth = GetDepthFromSlice((float)slice, cache.Data.GridSliceParameters);
LOG(Info, "Slice {} -> {}", slice, depth);
ASSERT((int32)GetSliceFromDepth(depth, cache.Data.GridSliceParameters) == slice);
}
}*/ }*/
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.VolumetricFogRange = Float2(fogStart, options.Distance);
cache.Data.MissedHistorySamplesCount = Math::Clamp(cache.MissedHistorySamplesCount, 1, (int32)ARRAY_COUNT(cache.Data.FrameJitterOffsets)); cache.Data.MissedHistorySamplesCount = Math::Clamp(cache.MissedHistorySamplesCount, 1, (int32)ARRAY_COUNT(cache.Data.FrameJitterOffsets));
Matrix::Transpose(renderContext.View.PrevViewProjection, cache.Data.PrevWorldToClip); Matrix::Transpose(renderContext.View.PrevViewProjection, cache.Data.PrevWorldToClip);
cache.Data.SkyLight.VolumetricScatteringIntensity = 0; cache.Data.SkyLight.VolumetricScatteringIntensity = 0;
@@ -362,7 +375,7 @@ bool VolumetricFogPass::InitSphereRasterize(FrameCache& cache, RasterizeSphere&
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.Data.VolumetricFogMaxDistance + sphere.Radius) || if ((view.Position - sphere.Center).LengthSquared() >= Math::Square(cache.Data.VolumetricFogRange.Y + sphere.Radius) ||
sphere.VolumeZBoundsMin > sphere.VolumeZBoundsMax) sphere.VolumeZBoundsMin > sphere.VolumeZBoundsMax)
{ {
return true; return true;
@@ -395,7 +408,7 @@ void VolumetricFogPass::RenderRadialLight(FrameCache& cache, RenderContext& rend
// Setup data // Setup data
perLight.SliceToDepth.X = cache.Data.GridSize.Z; perLight.SliceToDepth.X = cache.Data.GridSize.Z;
perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance; perLight.SliceToDepth.Y = cache.Data.VolumetricFogRange.Y;
perLight.MinZ = sphere.VolumeZBoundsMin; perLight.MinZ = sphere.VolumeZBoundsMin;
perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity; perLight.LocalLightScatteringIntensity = light.VolumetricScatteringIntensity;
perLight.ViewSpaceBoundingSphere = Float4(sphere.ViewSpaceCenter, sphere.Radius); perLight.ViewSpaceBoundingSphere = Float4(sphere.ViewSpaceCenter, sphere.Radius);
@@ -532,7 +545,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
CustomData customData; CustomData customData;
customData.Shader = _shader->GetShader(); customData.Shader = _shader->GetShader();
customData.GridSize = cache.GridSize; customData.GridSize = cache.GridSize;
customData.VolumetricFogMaxDistance = cache.Data.VolumetricFogMaxDistance; customData.VolumetricFogMaxDistance = cache.Data.VolumetricFogRange.Y;
customData.GridSliceParameters = cache.Data.GridSliceParameters; customData.GridSliceParameters = cache.Data.GridSliceParameters;
bindParams.CustomData = &customData; bindParams.CustomData = &customData;
bindParams.BindViewData(); bindParams.BindViewData();
@@ -554,7 +567,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
PerLight perLight; PerLight perLight;
auto cb2 = _shader->GetShader()->GetCB(2); auto cb2 = _shader->GetShader()->GetCB(2);
perLight.SliceToDepth.X = cache.Data.GridSize.Z; perLight.SliceToDepth.X = cache.Data.GridSize.Z;
perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance; perLight.SliceToDepth.Y = cache.Data.VolumetricFogRange.Y;
perLight.MinZ = sphere.VolumeZBoundsMin; perLight.MinZ = sphere.VolumeZBoundsMin;
perLight.ViewSpaceBoundingSphere = Float4(sphere.ViewSpaceCenter, sphere.Radius); perLight.ViewSpaceBoundingSphere = Float4(sphere.ViewSpaceCenter, sphere.Radius);
Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip); Matrix::Transpose(renderContext.View.Projection, perLight.ViewToVolumeClip);
@@ -582,7 +595,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
Array<uint16, InlinedAllocation<64, RendererAllocation>> pointLights; Array<uint16, InlinedAllocation<64, RendererAllocation>> pointLights;
Array<uint16, InlinedAllocation<64, RendererAllocation>> spotLights; Array<uint16, InlinedAllocation<64, RendererAllocation>> spotLights;
Float3 viewPosition = renderContext.View.Position; Float3 viewPosition = renderContext.View.Position;
float distance = cache.Data.VolumetricFogMaxDistance; float distance = cache.Data.VolumetricFogRange.Y;
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];
@@ -609,7 +622,7 @@ void VolumetricFogPass::Render(RenderContext& renderContext)
// Prepare // Prepare
PerLight perLight; PerLight perLight;
perLight.SliceToDepth.X = cache.Data.GridSize.Z; perLight.SliceToDepth.X = cache.Data.GridSize.Z;
perLight.SliceToDepth.Y = cache.Data.VolumetricFogMaxDistance; perLight.SliceToDepth.Y = cache.Data.VolumetricFogRange.Y;
auto cb2 = _shader->GetShader()->GetCB(2); auto cb2 = _shader->GetShader()->GetCB(2);
// Bind the output // Bind the output
+2 -2
View File
@@ -18,7 +18,7 @@ struct VolumetricFogData
float GetDepthFromSlice(float4 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.z) * gridSliceParameters.x;
#else #else
return (exp2(zSlice / gridSliceParameters.z) - gridSliceParameters.y) / gridSliceParameters.x; return (exp2(zSlice / gridSliceParameters.z) - gridSliceParameters.y) / gridSliceParameters.x;
#endif #endif
@@ -27,7 +27,7 @@ float GetDepthFromSlice(float4 gridSliceParameters, float zSlice)
float GetSliceFromDepth(float4 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 + gridSliceParameters.z;
#else #else
return (log2(sceneDepth * gridSliceParameters.x + gridSliceParameters.y) * gridSliceParameters.z); return (log2(sceneDepth * gridSliceParameters.x + gridSliceParameters.y) * gridSliceParameters.z);
#endif #endif
+5 -4
View File
@@ -47,8 +47,8 @@ uint MissedHistorySamplesCount;
uint3 GridSizeInt; uint3 GridSizeInt;
float PhaseG; float PhaseG;
float2 Dummy0; float2 VolumetricFogRange;
float VolumetricFogMaxDistance; float Dummy0;
float InverseSquaredLightDistanceBiasScale; float InverseSquaredLightDistanceBiasScale;
float4 FogParameters; float4 FogParameters;
@@ -104,7 +104,8 @@ float3 GetVolumeUV(float3 worldPosition, float4x4 worldToClip)
{ {
float4 ndcPosition = mul(float4(worldPosition, 1), worldToClip); float4 ndcPosition = mul(float4(worldPosition, 1), worldToClip);
ndcPosition.xy /= ndcPosition.w; ndcPosition.xy /= ndcPosition.w;
return float3(ndcPosition.xy * float2(0.5f, -0.5f) + 0.5f, ndcPosition.w / VolumetricFogMaxDistance); ndcPosition.w = (ndcPosition.w - VolumetricFogRange.x) / VolumetricFogRange.y; // TODO: convert into MAD
return float3(ndcPosition.xy * float2(0.5f, -0.5f) + 0.5f, ndcPosition.w);
} }
// Vertex shader that writes to a range of slices of a volume texture // Vertex shader that writes to a range of slices of a volume texture
@@ -340,7 +341,7 @@ void CS_FinalIntegration(uint3 DispatchThreadId : SV_DispatchThreadID)
if (any(gridCoordinate.xy >= GridSizeInt.xy)) if (any(gridCoordinate.xy >= GridSizeInt.xy))
return; return;
float4 acc = float4(0, 0, 0, 1); float4 acc = float4(0, 0, 0, 1);
float3 prevPositionWS = GBuffer.ViewPos; float3 prevPositionWS = GetCellPositionWS(uint3(gridCoordinate.xy, 0), 0.5f);
for (uint layerIndex = 0; layerIndex < GridSizeInt.z; layerIndex++) for (uint layerIndex = 0; layerIndex < GridSizeInt.z; layerIndex++)
{ {