Convert value from Z buffer to Z coordinate

I started messing with the NeHe lesson 06, which draws a spinning cube.

I wrote some code to print the value from Z buffer in the middle of viewport:


	/* This is C, NOT C++! */

	int viewport[4];
	GLfloat depth = 0.0f;
	
	/* Query window size */
	glGetIntegerv( GL_VIEWPORT, viewport );

	/* Read 1x1 area from z buffer at centre of viewport */
	glReadPixels( viewport[2] / 2, viewport[3] / 2, 1, 1,
		GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
	
	printf( "%f
", depth );

The value printed is not the “real” distance to the spinning cube, it is something between 0.96 and 0.98. The cube is about 4.5 units away.

The problem is: How to get the distance to the cube from Z buffer in world coordinates?

I mean a Z coordinate similar to the value (winZ) gluProject returns:


GLint gluProject( ..., GLdouble* winZ );

Am I clear?

Right. This is window-space Z.

The problem is: How to get the distance to the cube from Z buffer in world coordinates?

Presumably you mean eye coordinates, since you just care about the distance from the world to the eye.

One way is to run this X,Y,Z point through gluUnProject, or do the same thing in your code.

Another is to just look at the PROJECTION transform (orthographic or perspective – see the end of this page), write an equation of window-space Z in terms of eye-space Z, and solve for the latter.

If you do that for PERSPECTIVE projection, assuming eye_coord.w == 1, and assuming glDepthRange (0,1), then you get:

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

For ORTHOGRAPHIC with glDepthRange(0,1), you get:

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

Keep in mind these are eye-space Zs, so to get a positive Z distance, negate them.

Note that this gives you Z-distance only, not radial distance. For radial, use the UnProject route (or similar) to back-transform window X,Y,Z to a 3D eye-space point, then take the magnitude of the resultant point.

You don’t even need to go through the depth buffer if all that you want is the distance to the cube. You know what position the camera is at and you know what position the cube is at, so just use a standard 3D distance calculation to get the result.

gluUnProject only works with orthographic projection, it involves slow matrix multiplications, and I don’t need x/y coordinates.

This code doesn’t work properly:


static float test()
{
	GLdouble projection[16];
	GLfloat depth;
	float distance;
	
	glGetDoublev( GL_PROJECTION_MATRIX, projection );
	glReadPixels( 320, 240, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
	
	distance = projection[11] / ( depth * -2.0 + 1.0 - projection[10] ) * -1;
	printf( "Distance: %f
", distance );

	return distance;
}

It returns the distance, but the smallest value was 16 and largest 49,000. My znear is 0.1 and zfar 100.0, so the return value should be in that range.

I want to get a single scalar value, which I can use to quickly convert all values in Z buffer to eye Z coordinates.

But Z buffer is not linear, and I don’t know the math behind it. Help!

Not true. It works for perspective as well. You just have to provide the correct matrices.

There may be other bugs in your translation of the formula I gave to the code above, but here are the ones immediately obvious to me:

  1. [li] projection[11] != gl_ProjectionMatrix[3].z (3*4+2=?)

You lucked out on the other one because both the row and column indices were 2 :wink: