66

Sampling from a depth buffer in a shader returns values between 0 and 1, as expected. Given the near- and far- clip planes of the camera, how do I calculate the true z value at this point, i.e. the distance from the camera?

Hannesh
  • 7,256
  • 7
  • 46
  • 80
  • 10
    The depth buffer does not contain distance values to the camera. It contains perpendicular distance values to the _plane_ of the camera. – Nicol Bolas Jul 12 '11 at 02:21
  • 10
    I understand that, but I thought it was clear what I meant. All I need is a linearization of the depth values. – Hannesh Jul 12 '11 at 06:53

3 Answers3

68

From http://web.archive.org/web/20130416194336/http://olivers.posterous.com/linear-depth-in-glsl-for-real

// == Post-process frag shader ===========================================
uniform sampler2D depthBuffTex;
uniform float zNear;
uniform float zFar;
varying vec2 vTexCoord;
void main(void)
{
    float z_b = texture2D(depthBuffTex, vTexCoord).x;
    float z_n = 2.0 * z_b - 1.0;
    float z_e = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
}

[edit] So here's the explanation (with 2 mistakes, see Christian's comment below) :

An OpenGL perspective matrix looks like this : from songho.ca

When you multiply this matrix by an homogeneous point [x,y,z,1], it gives you: [don't care, don't care, Az+B, -z] (with A and B the 2 big components in the matrix).

OpenGl next does the perspective division: it divides this vector by its w component. This operation is not done in shaders (except special cases like shadowmapping) but in hardware; you can't control it. w = -z, so the Z value becomes -A/z -B.

We are now in Normalized Device Coordinates. The Z value is between 0 and 1. For some stupid reason, OpenGL requires that it should be moved to the [-1,1] range (just like x and y). A scaling and offset is applied.

This final value is then stored in the buffer.

The above code does the exact opposite :

  • z_b is the raw value stored in the buffer
  • z_n linearly transforms z_b from [-1,1] to [0,1]
  • z_e is the same formula as z_n=-A/z_e -B, but solved for z_e instead. It's equivalent to z_e = -A / (z_n+B). A and B should be computed on the CPU and sent as uniforms, btw.

The opposite function is :

varying float depth; // Linear depth, in world units
void main(void)
{
    float A = gl_ProjectionMatrix[2].z;
    float B = gl_ProjectionMatrix[3].z;
    gl_FragDepth  = 0.5*(-A*depth + B) / depth + 0.5;
}
Justicle
  • 14,761
  • 17
  • 70
  • 94
Calvin1602
  • 9,413
  • 2
  • 44
  • 55
  • 13
    While generally a good explanation, I think you have some things wrong. First, after dividing `Az+B` by `-z` you get `-A-B/z` rather than `-A/z-B`. And then it is after the perspective divide that the value is in [-1,1] and needs to be scale-biases to [0,1] before writing to the depth buffer, and not the other way around (though your code does it right, it's just the explanation that's wrong). – Christian Rau Jul 16 '13 at 08:16
  • @wil Thanks ! I added the opposition function, just in case. Christian: Oops yes, but I don't have the time to correct, so I refer to your comment instead :/ – Calvin1602 Sep 30 '13 at 11:11
  • A clarifying note: in the equation above, zNear and zFar are understood to be _negative_ values (which is proper, as they are z-coordinates in front of the camera). In contrast, the _n_ and _f_ values in the provided matrix are _positive_ values, following glOrtho's convention of using absolute values for near and far. – Kyle Simek Aug 11 '14 at 20:10
  • 1
    I think there is still a small mistake in your explanation. I guess you wanted to say "z_n linearly transforms z_b from [0,1] to [-1,1]" instead of the opposite way. At least, this is what your code does. – DanceIgel Oct 28 '15 at 08:41
  • So would it be that for directx it's `z_n = z_b` ? – v.oddou Dec 22 '16 at 08:01
  • @v.oddou Not sure but yeah, it should. – Calvin1602 Dec 22 '16 at 09:16
  • @Calvin1602, what if I dont know zNear and zFar? I mean, I am trying to get depth map using Direct3D library and I have no knowledge about these variables. – Muaz Usmani Oct 16 '17 at 15:54
16

I know this is an old, old question, but I've found myself back here more than once on various occasions, so I thought I'd share my code that does the forward and reverse conversions.

This is based on @Calvin1602's answer. These work in GLSL or plain old C code.

uniform float zNear = 0.1;
uniform float zFar = 500.0;

// depthSample from depthTexture.r, for instance
float linearDepth(float depthSample)
{
    depthSample = 2.0 * depthSample - 1.0;
    float zLinear = 2.0 * zNear * zFar / (zFar + zNear - depthSample * (zFar - zNear));
    return zLinear;
}

// result suitable for assigning to gl_FragDepth
float depthSample(float linearDepth)
{
    float nonLinearDepth = (zFar + zNear - 2.0 * zNear * zFar / linearDepth) / (zFar - zNear);
    nonLinearDepth = (nonLinearDepth + 1.0) / 2.0;
    return nonLinearDepth;
}
david van brink
  • 3,604
  • 1
  • 22
  • 17
  • 3
    The linear->non-linear seems to reduce down to: 4*far*(1-near/linear)/(far-near). https://www.desmos.com/calculator/oonxyoo3to – Wivlaro Aug 22 '16 at 05:40
  • 5
    Also the non-linear->linear reduces to: zFar*zNear / (zFar + depthSample * (zNear - zFar)) – Wivlaro Aug 24 '16 at 19:15
2

I ended up here trying to solve a similar problem when Nicol Bolas's comment on this page made me realize what I was doing wrong. If you want the distance to the camera and not the distance to the camera plane, you can compute it as follows (in GLSL):

float GetDistanceFromCamera(float depth, 
                            vec2 screen_pixel, 
                            vec2 resolution) {
  float fov = ... 
  float near = ...
  float far = ...
  float distance_to_plane = near / (far - depth * (far - near)) * far;


  vec2 center = resolution / 2.0f - 0.5;
  float focal_length = (resolution.y / 2.0f) / tan(fov / 2.0f);
  float diagonal = length(vec3(screen_pixel.x - center.x,
                               screen_pixel.y - center.y,
                               focal_length));

  return distance_to_plane * (diagonal / focal_length);
}

(source) Thanks to github user cassfalg: https://github.com/carla-simulator/carla/issues/2287

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Chet
  • 1,209
  • 1
  • 11
  • 29