12 Nov 2010

## Real depth in OpenGL / GLSL

So, many places will give you clues how to get linear depth from the OpenGL depth buffer, or visualise it, or other things. This, however, is what I believe to be the definitive answer:

This link http://www.songho.ca/opengl/gl_projectionmatrix.html gives a good run-down of the projection matrix, and the link between eye-space Z (`z_e` below) and normalised device coordinates (NDC) Z (`z_n` below). From there, we have

```A   = -(zFar + zNear) / (zFar - zNear);
B   = -2*zFar*zNear / (zFar - zNear);
z_n = -(A*z_e + B) / z_e; // z_n in [-1, 1]```

Note that the value stored in the depth buffer is actually in the range [0, 1], so the depth buffer value `z_b` is:

`z_b = 0.5*z_n + 0.5; // z_b in [0, 1]`

If we have rendered this depth buffer to a texture and wish to access the real depth in a later shader, we must undo the non-linear mapping above:

`z_e = 2*zFar*zNear / (zFar + zNear - (zFar - zNear)*(2*z_b -1));`

This is similar to the example given here: http://www.geeks3d.com/20091216/geexlab-how-to-visualize-the-depth-buffer-in-..., except that their example is divided through by `zFar`, which obviously gives a better value for visualisation. Their example also uses the depth buffer value `z_b` directly as `z_n`, without shifting back to [-1, 1], which, as you can see above is wrong.

If you want to verify the equations for `z_n` and `z_b`, you can try this in your shader on the render-to-texture (RTT) pass for your scene:

```// == RTT vert shader ====================================================
varying float depth;
void main(void)
{
depth = -(gl_ModelViewMatrix * gl_Vertex).z;
}

// == RTT frag shader ====================================================
varying float depth;
void main(void)
{
float A = gl_ProjectionMatrix[2].z;
float B = gl_ProjectionMatrix[3].z;
float zNear = - B / (1.0 - A);
float zFar  =   B / (1.0 + A);
//    float depthFF = 0.5*(-A*depth + B) / depth + 0.5;
//    float depthFF = gl_FragCoord.z;
//    gl_FragDepth  = depthFF;
}```

If you play with uncommenting the lines in the frag shader, you can try writing to `gl_FragDepth` manually, using either the value `gl_FragCoord.z`, which is supposed to be identical to the fixed-functionality, or alternatively using the value calculated using `depth`, `A`, and `B`. In all cases the results should be identical.

If you want to access the depth buffer in a later shader and recover the true depths, you then need to pass the `zNear` and `zFar` values from the first camera’s projection matrix as uniforms into the shader:

```// == 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));
}```

Alternatively, you can write your own depth value to a colour texture. If, like me, you don’t have floating point textures at your disposition, you can pack the depth value into 24 bits of a regular `RGBA` / `UNSIGNED_BYTE` colour texture as follows:

```// == RTT vert shader ====================================================
varying float depth;
void main(void)
{
depth = -(gl_ModelViewMatrix * gl_Vertex).z;
}

// == RTT frag shader ====================================================
varying float depth;
void main(void)
{
const vec3 bitShift3 = vec3(65536.0, 256.0, 1.0);
const vec3 bitMask3  = vec3(0.0, 1.0/256.0, 1.0/256.0);
float A = gl_ProjectionMatrix[2].z;
float B = gl_ProjectionMatrix[3].z;
float zNear = - B / (1.0 - A);
float zFar  =   B / (1.0 + A);
float depthN = (depth - zNear)/(zFar - zNear);  // scale to a value in [0, 1]
vec3 depthNPack3 = fract(depthN*bitShift3);
// gl_FragData[0] = colour rendering of your scene
gl_FragData[1] = vec4(depthNPack3, 1.0); // alpha should equal 1.0 if GL_BLEND is enabled
}

// == Post-process frag shader ===========================================
uniform sampler2D myDepthTex;   // My packed depth texture
uniform sampler2D depthBuffTex; // Texture storing OpenGL depth buffer
uniform float zNear;
uniform float zFar;
varying vec2 vTexCoord;
void main(void)
{
const vec3 bitUnshift3 = vec3(1.0/65536.0, 1.0/256.0, 1.0);
float z_e_mine = dot(texture2D(myDepthTex, vTexCoord).xyz, bitUnshift3);
z_e_mine = mix(zNear, zFar, z_e_mine); // scale from [0, 1] to [zNear, zFar]
float z_e_ffun = texture2D(depthBuffTex, vTexCoord).x;
z_e_ffun = 2.0 * z_e_ffun - 1.0;
z_e_ffun = 2.0 * zNear * zFar / (zFar + zNear - z_e_ffun * (zFar - zNear));
gl_FragColor = vec4(vec3(z_e_mine/zFar), 1.0); // divide by zFar to visualize
// gl_FragColor = vec4(vec3(z_e_ffun/zFar), 1.0); // divide by zFar to visualize
}```