NDC depth to pre perspective divide depth?

Given a NDC depth value (sampled from a depth texture) and the near and far values used in the projection matrix how do I go from non linear NDC depth to linear viewspace depth? Figuring this out saves me having to use MRTs storing viewspace depth in it’s own texture, I can just sample a depth texture directly (useful for things like volumefog and softparticles). The only good examples I have found are for D3D10 and I am having trouble doing the coordinate system change.

Common question. You can figure all this out yourself just by looking at the appropriate OpenGL projection matrix (see Appendix F in the Red Book, or the bottom of this page) you used for computing the depth texture values.

And note that typically you don’t store Z_ndc NDC-space depth (-1…1) in a depth texture. You usually store Z_viewport – that is viewport-space depth (0…1, or whatever you set glDepthRange to). But undoing that mapping to get to Z_ndc is easy.

Referring to the projection matrix, for a perspective projection you have:

z_ndc = z_clip / w_clip
z_ndc = [ z_eye*gl_ProjectionMatrix[2].z + gl_ProjectionMatrix[3].z ] / -z_eye

The 2nd step presumes w_eye = 1. Solve the above z_eye, and you get:

float z_eye = gl_ProjectionMatrix[3].z/(-z_ndc - gl_ProjectionMatrix[2].z);

Typically your glDepthRange is 0…1, so z_ndc = z_viewport * 2 - 1, so plugging that in…

float z_eye = gl_ProjectionMatrix[3].z/(z_viewport * -2.0 + 1.0 - gl_ProjectionMatrix[2].z);

That’ll get you from viewport-space Z to eye-space Z, for a perspective projection.

For a parallel projection, you have the difference that w_clip = 1 instead of -z_eye (again, assuming w_eye = 1), so…:

z_ndc = z_clip / w_clip
z_ndc = [ z_eye*gl_ProjectionMatrix[2].z + gl_ProjectionMatrix[3].z ] / 1

Solving for z_eye:

z_eye = ( z_ndc - gl_ProjectionMatrix[3].z ) / gl_ProjectionMatrix[2].z

and plugging in for z_ndc, assuming depth range is 0…1:

z_eye = ( z_viewport * 2.0 - 1.0 - gl_ProjectionMatrix[3].z ) / gl_ProjectionMatrix[2].z

That did it. This works especially well because it gives you the correct value regardless of the ndc z mapping (-1 to 1, 0 - 1). I was trying to solve a much harder problem than needed, I was basically trying to solve the same equation for z_eye but using the full equations for gl_ProjectionMatrix[2] + gl_ProjectionMatrix[3] using only the far and near values. Thanks!