Fix fog to draw Fog Cutoff Distance via a plane, not sphere test

Add support for negative Fog Cutoff Distance on fog to draw it in front of the camera Far Plane, no matter the setup.
Fix hot-reloading Fog shader in Editor.
This commit is contained in:
Wojtek Figat
2025-06-29 20:02:24 +02:00
parent dbd8297612
commit 4e4d380267
7 changed files with 39 additions and 47 deletions

View File

@@ -145,7 +145,7 @@ void PS_Forward(
#if USE_FOG
// Calculate exponential height fog
float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0);
float4 fog = GetExponentialHeightFog(ExponentialHeightFog, materialInput.WorldPosition, ViewPos, 0, gBuffer.ViewPos.z);
// Apply fog to the output color
#if MATERIAL_BLEND == MATERIAL_BLEND_OPAQUE

Binary file not shown.

BIN
Content/Shaders/Fog.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -41,11 +41,10 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext)
&& _shader->IsLoaded()
&& renderContext.View.IsPerspectiveProjection())
{
// Prepare
if (_psFog.States[0] == nullptr)
{
// Create pipeline states
_psFog.CreatePipelineStates();
if (!_psFog.States[0]->IsValid())
{
GPUPipelineState::Description psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle;
psDesc.DepthWriteEnable = false;
psDesc.BlendMode.BlendEnable = true;
@@ -59,6 +58,7 @@ void ExponentialHeightFog::Draw(RenderContext& renderContext)
if (_psFog.Create(psDesc, _shader->GetShader(), "PS_Fog"))
{
LOG(Warning, "Cannot create graphics pipeline state object for '{0}'.", ToString());
return;
}
}
@@ -160,7 +160,7 @@ void ExponentialHeightFog::GetExponentialHeightFogData(const RenderView& view, S
result.FogAtViewPosition = density * Math::Pow(2.0f, Math::Clamp(-heightFalloff * (viewHeight - height), -125.f, 126.f));
result.StartDistance = StartDistance;
result.FogMinOpacity = 1.0f - FogMaxOpacity;
result.FogCutoffDistance = FogCutoffDistance;
result.FogCutoffDistance = FogCutoffDistance >= 0 ? FogCutoffDistance : view.Far + FogCutoffDistance;
if (useDirectionalLightInscattering)
{
result.InscatteringLightDirection = -DirectionalInscatteringLight->GetDirection();

View File

@@ -55,9 +55,9 @@ public:
float StartDistance = 0.0f;
/// <summary>
/// Scene elements past this distance will not have fog applied. This is useful for excluding skyboxes which already have fog baked in. Setting this value to 0 disables it.
/// Scene elements past this distance will not have fog applied. This is useful for excluding skyboxes which already have fog baked in. Setting this value to 0 disables it. Negative value sets the cutoff distance relative to the far plane of the camera.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), DefaultValue(0.0f), Limit(0), EditorDisplay(\"Exponential Height Fog\")")
API_FIELD(Attributes="EditorOrder(60), DefaultValue(0.0f), EditorDisplay(\"Exponential Height Fog\")")
float FogCutoffDistance = 0.0f;
public:

View File

@@ -29,7 +29,7 @@ struct ExponentialHeightFogData
float StartDistance;
};
float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance)
float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance, float sceneDistance)
{
float3 cameraToPos = posWS - camWS;
float cameraToPosSqr = dot(cameraToPos, cameraToPos);
@@ -78,7 +78,7 @@ float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, fl
// Disable fog after a certain distance
FLATTEN
if (exponentialHeightFog.FogCutoffDistance > 0 && cameraToPosLen > exponentialHeightFog.FogCutoffDistance)
if (exponentialHeightFog.FogCutoffDistance > 0 && sceneDistance > exponentialHeightFog.FogCutoffDistance)
{
expFogFactor = 1;
directionalInscattering = 0;
@@ -87,4 +87,9 @@ float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, fl
return float4(inscatteringColor * (1.0f - expFogFactor) + directionalInscattering, expFogFactor);
}
float4 GetExponentialHeightFog(ExponentialHeightFogData exponentialHeightFog, float3 posWS, float3 camWS, float skipDistance)
{
return GetExponentialHeightFog(exponentialHeightFog, posWS, camWS, skipDistance, distance(posWS, camWS));
}
#endif

View File

@@ -24,41 +24,17 @@ Texture2D Depth : register(t0);
Texture3D IntegratedLightScattering : register(t1);
#endif
// Get world space position at given pixel coordinate
float3 GetWorldPos(float2 uv)
{
float depth = SAMPLE_RT(Depth, uv).r;
GBufferData gBufferData = GetGBufferData();
float3 viewPos = GetViewPos(gBufferData, uv, depth);
return mul(float4(viewPos, 1), gBufferData.InvViewMatrix).xyz;
}
float4 CalculateCombinedFog(float3 posWS, float sceneDepth, float3 volumeUV)
{
float skipDistance = 0;
#if VOLUMETRIC_FOG
skipDistance = max(ExponentialHeightFog.VolumetricFogMaxDistance - 100, 0);
#endif
float4 fog = GetExponentialHeightFog(ExponentialHeightFog, posWS, GBuffer.ViewPos, skipDistance);
#if VOLUMETRIC_FOG
float4 volumetricFog = IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0);
fog = float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a);
#endif
return fog;
}
META_PS(true, FEATURE_LEVEL_ES2)
META_PERMUTATION_1(VOLUMETRIC_FOG=0)
META_PERMUTATION_1(VOLUMETRIC_FOG=1)
float4 PS_Fog(Quad_VS2PS input) : SV_Target0
{
// Calculate pixel world space position
float3 posWS = GetWorldPos(input.TexCoord);
float3 viewVector = posWS - GBuffer.ViewPos;
// Get world space position at given pixel coordinate
float rawDepth = SAMPLE_RT(Depth, input.TexCoord).r;
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
@@ -67,17 +43,28 @@ float4 PS_Fog(Quad_VS2PS input) : SV_Target0
// Debug code
#if VOLUMETRIC_FOG && 0
volumeUV = posWS / 1000;
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(posWS / 100, 1);
//return float4(worldPos / 100, 1);
#endif
// Calculate fog color
float4 fog = CalculateCombinedFog(posWS, sceneDepth, volumeUV);
float skipDistance = 0;
#if VOLUMETRIC_FOG
skipDistance = max(ExponentialHeightFog.VolumetricFogMaxDistance - 100, 0);
#endif
// Calculate exponential fog color
float4 fog = GetExponentialHeightFog(ExponentialHeightFog, worldPos, GBuffer.ViewPos, skipDistance, viewPos.z);
#if VOLUMETRIC_FOG
// Sample volumetric fog and mix it in
float4 volumetricFog = IntegratedLightScattering.SampleLevel(SamplerLinearClamp, volumeUV, 0);
fog = float4(volumetricFog.rgb + fog.rgb * volumetricFog.a, volumetricFog.a * fog.a);
#endif
return fog;
}