Skip to main content
GameDev.net gamedev.net
🔒 Locked

Depth buffer value equation problem

Started by nbertoa Dec 31, 2016 at 11:13 PM 2 replies 2.5k views
Original Post
nbertoa
nbertoa
Hi, community.

I was reading an old post from Steve Baker about Learning to Love your Z-Buffer (https://www.sjbaker.org/steve/omniv/love_your_z_buffer.html) where he explains that z buffer integer value is calculated in the following way:

z_buffer_value = (1 << N) * ( a + b / z)

where
N = number of bits of Z precision
a = zFar / (zFar - zNear)
b = zFar * zNear / ( zNear - zFar )
z = distance from the eye to the object
and z_buffer_value js an integer value.

I have 2 questions:
(1) What are the steps to get that equation?
(2) If you create your depth buffer with a floating point format. Is Z distance from the eye to the camera stored directly instead?
MJP
MJP

The value stored in the depth buffer is post-projection Z divided by post-projection W. So to get the formula for computing that value you should look at how your projection matrix is constructed. A typical, symmetrical perspective projection constructed for D3D conventions will look like this:


xScale     0          0               0
0        yScale       0               0
0          0       zf/(zf-zn)         1
0          0       -zn*zf/(zf-zn)     0

So, let's assume you start with a position in view-space that looks like this: (Xv, Yv, Zv, 1). You'll then transform by your projection matrix to get (Xp, Yp, Zp, Wp). This gives us the following:


Zp = (Zv * zf)/(zf - zn) + 1.0 * -zn * zf/(zf - zn)
Wp = Zv

If we say that a = zf/(zf - zn) and b = -zn*zf/(zf-zn), then Zp / Wp = ((Zv * a) + b) / Zv = a + b / Zv. So really a and b are just the last two values from the third column of your projection matrix. Note that for D3D conventions a and b are different than what you get from that article, which accounts for the differences in clip space between D3D and OpenGL. It's also different if you use an orthographic projection instead of a perspective projection.

To answer your second question, a floating point depth buffer stores the same Zp / Zw value as an integer depth buffer. It just doesn't do the "multiply by (1 << N)" part, because it stores the value directly as a float instead of converting to a normalized integer format.

fais
fais
To answer your question about the steps leading to the equation let's look at the row in our projection matrix which will effect the zbuffer (column in the case of directx).

We know that the row of the matrix (again, column in directx) will operate on the vector to yield a value. This value will be divided by the whatever is in w after the matrix operation. The projection matrix will always have [0, 0, 1, 0] for the row operation on the w component, so when dotted with will return 0x + 0y + 1z + 0, or z.

Back to the row that will operate on the z component, we have

new_z = Ax + By + Cz + D

Well, we don't want the x and y value to effect our z buffer, so A and B will be set to 0, leaving the following options for us

w = old_z
new_z = C*old_z + D*1

Zbuffer value = new_z / w.

Okay so now what? Well we know our zbuffer only goes from 0 to 1, but we want to cram our znear (z_n) and zfar (z_f) in it such that when old_z = z_n, zbuffer value is 0, and when the old_z = z_f, zbuffer value is 1.

So now, we have two unknowns (C and D) and 2 results. Guess what? Time to solve a 2 by 2 matrix.

If old_z = z_n, new_z/old_z = 0
(C * z_n + D)/z_near = 0, or
C*z_n + D = 0

If old_z = z_far, new_z/old_z = 1
(C * z_far + D)/z_far = 1, or
C*z_f + D = z_f

We have two equations, two constants (z_f and z_n) and two unknowns C and D. When you solve for C and D, you should have the values for a and b
nbertoa
nbertoa
Thanks for your detailed explanation. I was doing the opposite in the shaders to get view position from depth buffer value :P

Happy New Year!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.