Get Clip Space Z value from depth sample [Applying fog using only a scene depth sample (URP)]

Hello, I’m trying to get a renderer feature to mix fog, which means I can only rely on the scene depth value that I have. It should be doable, but I’m not finding a clear path to get there.

My setup is that the render pass happens before rendering transparents. This is my own sort of flat transparent layer that gets blitted on top of the opaque objects. Only a screen quad is rendered in this pass, so the fog node won’t work here. I’m using shader graph and custom function nodes with .hlsl.

By including the URP Core hlsl file: #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
I want to take advantage of MixFog() which is already producing results, but MixFog requires a “fog factor” which you get from ComputeFogFactor(). This requires the clip space Z value that I don’t have in this render pass, or in my depth texture.

I can see the inner workings here and it doesn’t seem complicated at all: https://xibanya.github.io/URPShaderViewer/Library/URP/ShaderLibrary/ShaderVariablesFunctions.html

Is it possible to reverse the math and apply the same fog effect with only a depth node? I’m using exponential squared fog. I think passing a Scene Depth node result and emulating the fog math can produce the same result, but I’d need help with that

Thanks for your time :slight_smile:

For anyone who has this same problem (needing to get clip space Z value only from depth), I managed to stumble across a random github repo that had the solution:
https://github.com/h33p/Unity-Graphics-Demo/blob/master/Assets/Standard Assets/Effects/ImageEffects/Shaders/GlobalFog.shader
https://github.com/h33p/Unity-Graphics-Demo/blob/master/Assets/Standard Assets/Effects/ImageEffects/Scripts/GlobalFog.cs

These are old, but the math is the same for the most part. The part that was needed was from that shader; “ComputeDistance()”

I ported the relevant pieces over, including the camera frustum corners, but it turns out they weren’t needed for URP fog. (You can do that part in shader graph by copying the relevant C# portion to set the material matrix4x4, then in Shader Graph, use a custom function node and put it into a custom interpolator on the vertex output.) Those were needed for radial distance, but to my eye, the non-radial distance calculation matches the URP fog perfectly, even in extreme scenarios.

URP - NearPlane: 0.1, FarPlane: 100, Fog: exp2 0.03


Shader graph ComputeFogValue_float custom function output:

I removed all of the frustum corner code and now my custom HLSL is very clean and concise:

EDIT: I added a max(0,x) here since URP does it in shadergraph_LWFog (ShaderGraphFunctions.hlsl)

float GetClipZ_01(float linearDepth, float nearPlane, float farPlane)
{
   return max(0, (linearDepth * farPlane) - nearPlane); // Non-radial distance
}

You can get the nearPlane and farPlane value from the Camera Node, and the linearDepth from the Scene Depth Node. The output shader graph simply uses this:

void ComputeFogValue_float(float linearDepth, float nearPlane, float farPlane, out float fogFactor)
{
   float clipZ_01 = GetClipZ_01(linearDepth, nearPlane, farPlane);
   fogFactor = ComputeFogIntensity(ComputeFogFactorZ0ToFar(clipZ_01));
}
void ComputeFogColor_float(float3 mixColor, float linearDepth, float nearPlane, float farPlane, out float3 fogFactor)
{
   float clipZ_01 = GetClipZ_01(linearDepth, nearPlane, farPlane);
   fogFactor = MixFog(mixColor, ComputeFogFactorZ0ToFar(clipZ_01));
}

You can use either of those two functions. MixFog calls ComputeFogIntensity() for you. Custom Function nodes also include the relevant URP .hlsl files so you don’t have to (I mentioned in the first post that I was, but this caused duplicate include warnings, and it turns out I didn’t need them). The first of these two functions will just show the fog in black/white if you output that to your shader graph. That’s how I was able to see that the GetClipZ_01() was working perfectly, even in extreme cases, like when nearPlane is 16.5 etc.

URP - NearPlane: 16.5, FarPlane 100, Fog: exp2 0.19


Shader graph ComputeFogValue_float custom function output:

Even if they don’t match 100%, nobody would notice a difference in-game. I’m using this solution so I wanted to help anyone who may have the same situation. It’s better than being being forced to use multiple render-targets like I was before. Now I can just use the depth from the previous renders instead of outputting the fog density to a separate texture :slight_smile: