Converting a clip-space point to world-space?

I am editing the Unity built-in-renderer fog macros to apply a height based fog to every shader that utilises built-in fog. Because I am hooking into UNITY_TRANSFER_FOG to get the vertex position, it is unfortunately in clip-space.

Is there way to convert the vertex position back to world-space so I can compare the Y value to a known world-space height?

Alternatively, does anyone have any clever suggestions on how to do the comparison in clip-space instead? Other height fog implementations (deferred etc.) are off the table. It needs to be compiled into every shader.

Yes, it’s possible. The harder part you’re going to run into is Unity only needs a single float value for fog, and there are lots of places where Unity packs that single value into another vector when transferring it to the fragment.

What you need to go from clip space to world space is an inverse projection matrix and inverse view matrix. Unity provides an inverse project matrix, but it doesn’t exactly match the “real” view matrix, so some tweaks need to be made to solve that.

float3 ClipToWorldPos(float4 clipPos)
{
#ifdef UNITY_REVERSED_Z
    // unity_CameraInvProjection always in OpenGL matrix form
    // that doesn't match the current view matrix used to calculate the clip space

    // transform clip space into normalized device coordinates
    float3 ndc = clipPos.xyz / clipPos.w;

    // convert ndc's depth from 1.0 near to 0.0 far to OpenGL style -1.0 near to 1.0 far 
    ndc = float3(ndc.x, ndc.y * _ProjectionParams.x, (1.0 - ndc.z) * 2.0 - 1.0);

    // transform back into clip space and apply inverse projection matrix
    float3 viewPos =  mul(unity_CameraInvProjection, float4(ndc * clipPos.w, clipPos.w));
#else
    // using OpenGL, unity_CameraInvProjection matches view matrix
    float3 viewPos = mul(unity_CameraInvProjection, clipPos);
#endif

    // transform from view to world space
    return mul(unity_MatrixInvV, float4(viewPos, 1.0)).xyz;
}

Thanks so much for your help bgolus! That’s perfect!

I’m modifying UNITY_FOG_COORDS to add second float (y position in clip space). I ran some tests and couldn’t seem to find anywhere in our game where UNITY_TRANSFER_FOG_COMBINED_WITH_TSPACE, UNITY_TRANSFER_FOG_COMBINED_WITH_WORLD_POS, or UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC are used. Do you know in what situations these defines are used? Is there somewhere we’re gonna be caught out?

Unity’s Standard shader uses UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC

Good to know. We aren’t using the standard shader anywhere so we might be alright. I’ll do some more tests to be sure.

Thanks for your help!