How do projection matrices work in Unity?

I’m working on a screen-space reflection shader for Unity but I’m running into some incorrect values when transforming from clip space to camera space.

To get a better grasp on how this transformation works I’ve ran some tests:

Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(targetCamera.projectionMatrix, false);

Vector4 clipSpace = projectionMatrix * new Vector4(0.0f, 0.0f, -targetCamera.nearClipPlane, 1.0f);
float depth = clipSpace.z / clipSpace.w;

Result: depth = 0.0.

Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(targetCamera.projectionMatrix, false);

Vector4 clipSpace = projectionMatrix * new Vector4(0.0f, 0.0f, -targetCamera.farClipPlane, 1.0f);
float depth = clipSpace.z / clipSpace.w;

Result: depth = 1.0.

So far, this makes sense. In clip space the Z component represents linear depth from the near clip plane to the far clip plane.

But then I ran this test, expecting the result to be about 0.5f:

Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(targetCamera.projectionMatrix, false);

Vector4 clipSpace = projectionMatrix * new Vector4(0.0f, 0.0f, -targetCamera.farClipPlane * 0.5f, 1.0f);
float depth = clipSpace.z / clipSpace.w;

Result: depth = 0.995.

Apparently my assumption was not correct. It seems the Z value in clip space follows some sort of curve where it rapidly increases to values above 0.9 and then slowly increases to 1.0 at the far plane. What am I missing here? Is my test case incorrect?

Note: In my test setup I used a perspective camera with the near plane at 0.1 and the far plane at 20

What you’re expecting would be linear depth mapping. That hasn’t been used for a while because it does cause clipping issues, as Owen mentioned. Instead, the calculation is based on the reciprocal. Read this article from NVIDIA for a pretty good description of why it works that way, including equations for how it’s calculated.