Add improvements to Volumetric Fog quality and performance

This commit is contained in:
2026-01-27 22:20:48 +01:00
parent d47bd5d6e7
commit f9b784a42a
16 changed files with 249 additions and 191 deletions
-5
View File
@@ -92,9 +92,4 @@ float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, fl
return GetExponentialHeightFog(exponentialHeightFog, posWS, camWS, skipDistance, distance(posWS, camWS));
}
float4 CombineVolumetricFog(float4 fog, float4 volumetricFog)
{
return float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a);
}
#endif
+3 -19
View File
@@ -5,6 +5,7 @@
#include "./Flax/Common.hlsl"
#include "./Flax/GBufferCommon.hlsl"
#include "./Flax/GBuffer.hlsl"
#include "./Flax/VolumetricFog.hlsl"
#include "./Flax/ExponentialHeightFog.hlsl"
// Disable Volumetric Fog if is not supported
@@ -21,7 +22,7 @@ DECLARE_GBUFFERDATA_ACCESS(GBuffer)
Texture2D Depth : register(t0);
#if VOLUMETRIC_FOG
Texture3D IntegratedLightScattering : register(t1);
Texture3D VolumetricFogTexture : register(t1);
#endif
META_PS(true, FEATURE_LEVEL_ES2)
@@ -34,23 +35,6 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0
GBufferData gBufferData = GetGBufferData();
float3 viewPos = GetViewPos(gBufferData, input.TexCoord, rawDepth);
float3 worldPos = mul(float4(viewPos, 1), gBufferData.InvViewMatrix).xyz;
float3 viewVector = worldPos - GBuffer.ViewPos;
float sceneDepth = length(viewVector);
// Calculate volumetric fog coordinates
float depthSlice = sceneDepth / ExponentialHeightFog.VolumetricFogMaxDistance;
float3 volumeUV = float3(input.TexCoord, depthSlice);
// Debug code
#if VOLUMETRIC_FOG && 0
volumeUV = worldPos / 1000;
if (!all(volumeUV >= 0 && volumeUV <= 1))
return 0;
return float4(IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0).rgb, 1);
//return float4(volumeUV, 1);
//return float4(worldPos / 100, 1);
#endif
float skipDistance = 0;
#if VOLUMETRIC_FOG
@@ -62,7 +46,7 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0
#if VOLUMETRIC_FOG
// Sample volumetric fog and mix it in
float4 volumetricFog = IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0);
float4 volumetricFog = SampleVolumetricFog(VolumetricFogTexture, worldPos - GBuffer.ViewPos, ExponentialHeightFog.VolumetricFogMaxDistance, input.TexCoord);
fog = CombineVolumetricFog(fog, volumetricFog);
#endif
+5
View File
@@ -256,4 +256,9 @@ float3 RotateAboutAxis(float4 normalizedRotationAxisAndAngle, float3 positionOnA
return pointOnAxis + rotation - position;
}
float Remap(float value, float fromMin, float fromMax, float toMin, float toMax)
{
return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin;
}
#endif
+40
View File
@@ -0,0 +1,40 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#ifndef __VOLUMETRIC_FOG__
#define __VOLUMETRIC_FOG__
#define VOLUMETRIC_FOG_GRID_Z_LINEAR 1
float GetDepthFromSlice(float3 gridSliceParameters, float zSlice)
{
#if VOLUMETRIC_FOG_GRID_Z_LINEAR
return zSlice * gridSliceParameters.x;
#else
return (exp2(zSlice / gridSliceParameters.z) - gridSliceParameters.y) / gridSliceParameters.x;
#endif
}
float GetSliceFromDepth(float3 gridSliceParameters, float sceneDepth)
{
#if VOLUMETRIC_FOG_GRID_Z_LINEAR
return sceneDepth * gridSliceParameters.y;
#else
return (log2(sceneDepth * gridSliceParameters.x + gridSliceParameters.y) * gridSliceParameters.z);
#endif
}
float4 SampleVolumetricFog(Texture3D volumetricFogTexture, float3 viewVector, float maxDistance, float2 uv)
{
float sceneDepth = length(viewVector);
float zSlice = sceneDepth / maxDistance;
// TODO: use GetSliceFromDepth instead to handle non-linear depth distributions
float3 volumeUV = float3(uv, zSlice);
return volumetricFogTexture.SampleLevel(SamplerLinearClamp, volumeUV, 0);
}
float4 CombineVolumetricFog(float4 fog, float4 volumetricFog)
{
return float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a);
}
#endif
+37 -44
View File
@@ -17,9 +17,11 @@
#define DEBUG_VOXELS 0
#include "./Flax/Common.hlsl"
#include "./Flax/Math.hlsl"
#include "./Flax/LightingCommon.hlsl"
#include "./Flax/ShadowsSampling.hlsl"
#include "./Flax/GBuffer.hlsl"
#include "./Flax/VolumetricFog.hlsl"
#include "./Flax/GI/DDGI.hlsl"
struct SkyLightData
@@ -50,6 +52,8 @@ float VolumetricFogMaxDistance;
float InverseSquaredLightDistanceBiasScale;
float4 FogParameters;
float3 GridSliceParameters;
float Dummy1;
float4x4 PrevWorldToClip;
@@ -83,23 +87,18 @@ float GetPhase(float g, float cosTheta)
return HenyeyGreensteinPhase(g, cosTheta);
}
float GetSliceDepth(float zSlice)
{
return (zSlice / GridSize.z) * VolumetricFogMaxDistance;
}
float3 GetCellPositionWS(uint3 gridCoordinate, float3 cellOffset, out float sceneDepth)
{
float2 volumeUV = (gridCoordinate.xy + cellOffset.xy) / GridSize.xy;
sceneDepth = GetSliceDepth(gridCoordinate.z + cellOffset.z) / GBuffer.ViewFar;
sceneDepth = GetDepthFromSlice(GridSliceParameters, gridCoordinate.z + cellOffset.z) / GBuffer.ViewFar;
float deviceDepth = LinearZ2DeviceDepth(GBuffer, sceneDepth);
return GetWorldPos(GBuffer, volumeUV, deviceDepth);
}
float3 GetCellPositionWS(uint3 gridCoordinate, float3 cellOffset)
{
float temp;
return GetCellPositionWS(gridCoordinate, cellOffset, temp);
float sceneDepth;
return GetCellPositionWS(gridCoordinate, cellOffset, sceneDepth);
}
float3 GetVolumeUV(float3 worldPosition, float4x4 worldToClip)
@@ -164,17 +163,16 @@ META_PERMUTATION_1(USE_SHADOW=1)
float4 PS_InjectLight(Quad_GS2PS input) : SV_Target0
{
uint3 gridCoordinate = uint3(input.Vertex.Position.xy, input.LayerIndex);
// Prevent from shading locations outside the volume
if (!all(gridCoordinate < GridSizeInt))
if (any(gridCoordinate >= GridSizeInt))
return 0;
// Supersample if history buffer is outside the view
float3 historyUV = GetVolumeUV(GetCellPositionWS(gridCoordinate, 0.5f), PrevWorldToClip);
float historyAlpha = HistoryWeight;
FLATTEN
if (any(historyUV < 0) || any(historyUV > 1))
historyAlpha = 0;
uint samplesCount = historyAlpha < 0.001f ? MissedHistorySamplesCount : 1;
uint samplesCount = historyAlpha < 0.01f ? MissedHistorySamplesCount : 1;
float NoL = 0;
bool isSpotLight = LocalLight.SpotAngles.x > -2.0f;
@@ -182,8 +180,6 @@ float4 PS_InjectLight(Quad_GS2PS input) : SV_Target0
for (uint sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++)
{
float3 cellOffset = FrameJitterOffsets[sampleIndex].xyz;
//float cellOffset = 0.5f;
float3 positionWS = GetCellPositionWS(gridCoordinate, cellOffset);
float3 cameraVector = normalize(positionWS - GBuffer.ViewPos);
float cellRadius = length(positionWS - GetCellPositionWS(gridCoordinate + uint3(1, 1, 1), cellOffset));
@@ -202,13 +198,9 @@ float4 PS_InjectLight(Quad_GS2PS input) : SV_Target0
if (attenuation > 0)
{
if (isSpotLight)
{
shadow = SampleSpotLightShadow(LocalLight, ShadowsBuffer, ShadowMap, positionWS).SurfaceShadow;
}
else
{
shadow = SamplePointLightShadow(LocalLight, ShadowsBuffer, ShadowMap, positionWS).SurfaceShadow;
}
}
#endif
@@ -226,12 +218,10 @@ RWTexture3D<float4> RWVBufferB : register(u1);
META_CS(true, FEATURE_LEVEL_SM5)
[numthreads(4, 4, 4)]
void CS_Initialize(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID)
void CS_Initialize(uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint3 gridCoordinate = DispatchThreadId;
float voxelOffset = 0.5f;
float3 positionWS = GetCellPositionWS(gridCoordinate, voxelOffset);
float3 positionWS = GetCellPositionWS(gridCoordinate, 0.5f);
// Unpack the fog parameters (packing done in C++ ExponentialHeightFog::GetVolumetricFogOptions)
float fogDensity = FogParameters.x;
@@ -274,26 +264,25 @@ META_CS(true, FEATURE_LEVEL_SM5)
META_PERMUTATION_1(USE_DDGI=0)
META_PERMUTATION_1(USE_DDGI=1)
[numthreads(4, 4, 4)]
void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID)
void CS_LightScattering(uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint3 gridCoordinate = DispatchThreadId;
float3 lightScattering = 0;
uint samplesCount = 1;
if (any(gridCoordinate >= GridSizeInt))
return;
// Supersample if history buffer is outside the view
float3 historyUV = GetVolumeUV(GetCellPositionWS(gridCoordinate, 0.5f), PrevWorldToClip);
float historyAlpha = HistoryWeight;
FLATTEN
if (any(historyUV < 0) || any(historyUV > 1))
historyAlpha = 0;
samplesCount = historyAlpha < 0.001f && all(gridCoordinate < GridSizeInt) ? MissedHistorySamplesCount : 1;
uint samplesCount = historyAlpha < 0.01f ? MissedHistorySamplesCount : 1;
float3 lightScattering = 0;
for (uint sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++)
{
float3 cellOffset = FrameJitterOffsets[sampleIndex].xyz;
//float3 cellOffset = 0.5f;
float sceneDepth;
float3 positionWS = GetCellPositionWS(gridCoordinate, cellOffset, sceneDepth);
float3 positionWS = GetCellPositionWS(gridCoordinate, cellOffset);
float3 cameraVector = positionWS - GBuffer.ViewPos;
float cameraVectorLength = length(cameraVector);
float3 cameraVectorNormalized = cameraVector / cameraVectorLength;
@@ -306,8 +295,7 @@ void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_
#if USE_DDGI
// Dynamic Diffuse Global Illumination
float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesData, ProbesDistance, ProbesIrradiance, positionWS, cameraVectorNormalized, 0.0f, cellOffset.x);
lightScattering += float4(irradiance, 1);
lightScattering += SampleDDGIIrradiance(DDGI, ProbesData, ProbesDistance, ProbesIrradiance, positionWS, cameraVectorNormalized, 0.0f, cellOffset.x);
#else
// Sky light
if (SkyLight.VolumetricScatteringIntensity > 0)
@@ -334,12 +322,9 @@ void CS_LightScattering(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_
float4 historyScatteringAndExtinction = LightScatteringHistory.SampleLevel(SamplerLinearClamp, historyUV, 0);
scatteringAndExtinction = lerp(scatteringAndExtinction, historyScatteringAndExtinction, historyAlpha);
}
if (all(gridCoordinate < GridSizeInt))
{
scatteringAndExtinction = select(or(isnan(scatteringAndExtinction), isinf(scatteringAndExtinction)), 0, scatteringAndExtinction);
RWLightScattering[gridCoordinate] = max(scatteringAndExtinction, 0);
}
scatteringAndExtinction = select(or(isnan(scatteringAndExtinction), isinf(scatteringAndExtinction)), 0, scatteringAndExtinction);
RWLightScattering[gridCoordinate] = max(scatteringAndExtinction, 0);
}
#elif defined(_CS_FinalIntegration)
@@ -350,9 +335,11 @@ Texture3D<float4> LightScattering : register(t0);
META_CS(true, FEATURE_LEVEL_SM5)
[numthreads(8, 8, 1)]
void CS_FinalIntegration(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint3 GroupThreadId : SV_GroupThreadID)
void CS_FinalIntegration(uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint3 gridCoordinate = DispatchThreadId;
if (any(gridCoordinate.xy >= GridSizeInt.xy))
return;
float4 acc = float4(0, 0, 0, 1);
float3 prevPositionWS = GBuffer.ViewPos;
@@ -363,11 +350,17 @@ void CS_FinalIntegration(uint3 GroupId : SV_GroupID, uint3 DispatchThreadId : SV
float3 positionWS = GetCellPositionWS(coords, 0.5f);
// Ref: "Physically Based and Unified Volumetric Rendering in Frostbite"
float transmittance = exp(-scatteringExtinction.w * length(positionWS - prevPositionWS));
float stepDistance = length(positionWS - prevPositionWS);
float transmittance = exp(-scatteringExtinction.w * stepDistance);
float3 scatteringIntegratedOverSlice = (scatteringExtinction.rgb - scatteringExtinction.rgb * transmittance) / max(scatteringExtinction.w, 0.00001f);
// Apply distance fade
float distanceFade = Remap(layerIndex, GridSizeInt.z * 0.8f, GridSizeInt.z - 1, 1, 0);
scatteringIntegratedOverSlice *= distanceFade;
// Accumulate
acc.rgb += scatteringIntegratedOverSlice * acc.a;
acc.a *= transmittance;
#if DEBUG_VOXELS
RWIntegratedLightScattering[coords] = float4(scatteringExtinction.rgb, 1.0f);
#elif DEBUG_VOXEL_WS_POS