NV_depth_buffer_float and FBOs

Hi all,

I would love to know if anybody was able to use a
depth texture, of the type supported by NV_depth_buffer_float object, as depth attachement of FBOs. I really need the extra precision, but I would have no idea how to go about this.
The spec appear to suggest that this should work great.

If you have any example, or if you know for sure it just does not work (regardless of what the spec says), I would really appreciate if you’ll let me know.



What do you want to do? Do you just want to render the depth to a texture? Or are you looking to render depth and color to textures at the same time?

I want to render depth to a texture to use for shadow mapping. Plus I want to compare this with integer SM datatypes.
So the best way would be to enable floating point depth texture (instead of DEPTH_COMPONENT24) in
the FBO creation time.
I would have expected to see some change, but I do not especially since I have huge near plane problem for my particular application.



The problem you are most likely encountering is that floating point depth buffers do not significantly help distant precision if you use them exactly like integer depth buffers. The problem and solution(s) are as follows:

With a normal perspective projection and viewport mapping, the values placed into the depth buffer vary from 0 at the near plane to 1 at the far plane, but the mapping is not linear. This means that points 2 * near in front of the eye, end up with a depth value of 0.5, leaving only 23 bits of integer precision for stuff beyond 2 * near when you use a 24 bit integer depth buffer. This naturally leads to depth fighting in far objects, when you have a small near plane.

While floating point representations of the depth buffer provide additional bits, they do not provide a favorable distribution of those bits if you continue to use the depth buffer in the same way. IEEE style 32 bit floating point numbers have 24 bits of precision between every power of two within the exponent range. This means that they have 24 bits of precsion from 0.5 (2 ^ -1) and 1.0 (2 ^ 0). As you can see, this is only one bit better than a 24 bit integer buffer for far objects. What they do provide are extra range (-MAX_FLOAT - MAX_FLOAT), and extra precision close to zero ( 24 bits from 0.25 - 0.5, 24 bits from 0.125 - 0.25, etc).

To maximize the extra precision from the floating point depth buffer one must utilize the extra properties supplied by floats. The simple solution of making the far value map to a much larger number than 1 does not work. This is because it will still be bitten by the power of two rule. If the depth buffer is set to run from 0.0 to 16.0, the values from 2 * near to far will be mapped into the 8.0 - 16.0 range. This range still has only 24 bits of precision, due to the nature of floating point numbers. What will work, is to invert the depth values, such that the near plane is at 1.0 and the far plane is at 0.0. Now, the values from the near plane to 2 * near will map into the 0.5 to 1.0 range and receive 24 bits of depth precision (typically enough for this small range). The values beyond 2 * near will be mapped to 0.0 - 0.5 and receive the 24 bits worth of mantissa and more than 6 bits worth of precision from the negative exponent values, providing around 30 bits worth of precision. It can be pushed further, by mapping the near value to a number greater than 0.0, but the gains are going to be significantly diminished.

Finally, the precision cannot be improved in a meaningful way by using both positive and negative numbers in the depth buffer. Since precision concentrates around 0.0, this would result in all the precision being concentrated near 2 * near, which is typically not desirable.

To get the depth inverted, you can either change the values sent to depth range to be the reverse of what you would normally use, or you can bake a scale of (1,1,-1) at the end of the projection calculation. With either one, you need to reverse your depth test function and polygon offset values. With the scaling trick, you need to change the front-face direction. (CW versus CCW) The projection matrix trick might provide slightly better results, because it does the computations pre-clip and pre-projection, but I have not personally been able to find a difference. Also, remember that if you plan to try to use a depth range value outside [0,1], you need to use glDepthRangeNV.

As for shadow mapping and depth fighting, be careful searching for more precision. When you are trying to compare values like this, additional precision means that it is harder to get equality. If the code generating the depth values is not doing things right, adding more precision will often make things worse instead of better.



What can you say about making depth’s distribution linear? I mean, modifying projection matrix in such a way, that normalized depth values got the same 0 and 1 bounds, but the distribution is not hyperbolic, but linear.
This might give lots of precision at far and constant discretization step.


I can’t comment too much on what you suggest, because I have not done it personally.

I do think that what you suggest is going to be more expensive, since the hyperbolic nature of the depth is coming from the perspective divide. This means that you need to do extra work to avoid it.


Look at following article . It is DirectX based so the matrix and calculation needs to be modified to work in OGL however the idea is the same.

The article is incorrect. Depth is always linearly interpolated in window space (see OpenGL 2.0 spec, p. 110). While the proposed method gives you a linear mapping of view space Z to window space Z at the vertices, linear interpolation in window space will make the polygons appear curved in terms of depth data. Thus you could get rendering errors like a finely tessellated plane that is behind a single-quad plane appearing mostly in front.

I thought that it is the w-component (reciprocal of it) that is interpolated linearly?


  • Zw is window space z,
  • Zc is clip space z,
  • Wc is clip space w…

Zw = Zc/Wc

Zw is always interpolated linearly in screen space.

For perspective correction:

  • 1/Wc is interpolated linearly in screen space

  • Attributes to be perspective corrected are projected per-vertex (Aw = Ac/Wc) and interpolated linearly in screen space

  • Perspective correction is then uses linearly interpolated Ac/Wc and 1/Wc to compute Ac = (Ac/Wc ) / (1/Wc)

Old-skool w-buffering really was just computing eye-space Z (Ze) and interpolating it with perspective correction. Since Ze = Wc for most projection matrices, it just used the reciprocal of the 1/Wc that was already being interpolated for perspective correction.

W-buffering had the nice property that it was usually eye-space z-buffering, but the unfortunate property that eye-space z does not vary linearly in screen space. This nonlinearity is tragic for features like depth compression and multi-sample (lots of dividers!).

The article that Komat refers to does undo the per-vertex divide-by-Wc that happens when computing Zw, but all that really does is give you an un-perspective-corrected Zc. With a floating point z buffer, you could make that an un-perspective-corrected Ze.

Remember the old days when color was not perspective correct, and you’d get different colors by just tessellating a planar mesh at a higher density? (The more you tessellate, the better you approximate perspective correction.) Same problem exists with non-perspective correct z, except it also affects visibility calculations. Yuck.

Thanks -

Thanks for explanation, cass!

So, if I understood it correctly, you can get linear z (w-buffering) by setting Zc to something like (Zeye-(far-near)/2)/((far-near)/2)?

As I understood, it would be linear, but not perspective-correctly interpolated, right?

Yes, that is what Cass was saying. The only way to get Z interpolated with perspective correction is to do operations per-pixel (and write it from the shader). This is clearly a significant cost. Also, it would break multisampling for the cases of interpenetrating geometry.

Right. If you want a z-buffer that’s linear in eye space it must be perspective corrected per fragment.

Things that vary linearly in eye space vary nonlinearly in screen space unless their Wc
values are the same. In order to interpolate
z linearly in screen space, you must project
it, which is why Zw = Zc/Wc.

You could get Zc back by dividing it by 1/Wc, but that would only serve to give you a nonlinear Z in screen space, and it would not affect the result of the comparison.

As Evan says, you can do this today with depth replace, but it’s prohibitively expensive, even if you could work out the correctness issues like multisample.

The inversion of Z that Evan suggested seems the most reasonable way to get Zc/Wc packing toward the far plane to match the natural floating point packing of precision toward 0. All while preserving linear screen space z.

In reality, you probably want to put 0.0 at the place in your scene where you want the most depth resolution. Good luck finding that one place! :slight_smile: