Do float32 depth buffers use all of their exponent bits?

I have been writing a bachelor thesis about depth buffering and optimal depth resolution for astronomic scale space simulations/applications for some time now and I came across the question whether 32-bit floating-point depth buffers really use all of their bits, especially the exponent bits. From all I know, depth values are only valid in the range [0, 1], but why is that and does it has to be this way?

I can imagine that it is some implication from the normalized integer format in the background. But for depth especially it could be very useful to not have this limitation. As far as my understanding goes it would double the number of distinct representable values 7 times because of the 7 additionally useable exponent bits. One could store the actual view space distance of a fragment easily. Unless clipping utilizes some simple and fast bit comparison in the exponent part (simply kicking everything that has no exponent of 0) I don’t see why it wouldn’t be possible to implement clipping by comparing distance values against given near and far clipping plane values (maybe set through a special function).

Purely theoretically speaking one could store an 8-bit stencil buffer in the exponent part since the order of magnitude for depth is fixed and implicit anyway. I don’t know anything about how the driver or the graphics hardware stores a combined depth/stencil attachment. It says GL_DEPTH32F_STENCIL8 and that indicates two separate buffers, but other than that it doesn’t tell anything about the memory layout and alignment.

I would be glad if someone could enlighten me :slightly_smiling_face:

Kind regards

Using the [0,1] range is only actually wasting two bits: the sign bit and the topmost bit of the exponent. All of the negative exponents (corresponding to values less than 1.0) are valid.

When using a floating-point depth buffer, it’s common to set up the projection matrix so that the far plane (which is often at infinity) maps to a depth value of zero while the near plane maps to a depth value of one. Thus, the fact that depth scale decreases with increasing distance is offset by the fact that floating-point precision increases as you approach zero. This convention requires the use of glClipControl with a depth parameter of GL_ZERO_TO_ONE.

Furthermore, glEnable(GL_DEPTH_CLAMP) disables clipping at the near and far planes, so you can actually use as much of the floating point range as you desire. In practice, you would only use the positive range; once you go beyond zero, you have worsening depth precision due to the reciprocal nature of the transformation coupled with worsening depth precision as you get farther from zero and the exponent starts increasing.

This is discussed in some detail in the ARB_depth_clamp and ARB_clip_control extensions. ARB_depth_clamp is core in 3.2, ARB_clip_control in 4.5.

Thank you, GClements. Of course! Now that I read it I don’t know why I hadn’t noticed that all negative exponents must be in use :man_facepalming:

But my understanding of glEnable(GL_DEPTH_CLAMP) is that everything that is below 0 in z_ndc is just set to 0 and everything above 1 is set to 1. But there is no determination of depth or draw order for such fragments or is there? If each draw call can write 1 at most into the depth buffer then such fragments beyond the far clipping plane (assuming non-reverse depth mapping) will be drawn in the order in which their corresponding draw calls were made. So I don’t understand what you mean with “so you can actually use as much of the floating point range as you desire”. Sure internally I guess the entire range is used, but the depth clipping is still restricted to [0, 1], isn’t it?

The depth buffering approaches that I cover in my work are not restricted to projections and implicit depth value determination. I also cover approaches that manually write to gl_FragDepth (for better or worse). My understanding is that there values are also expected to be in the range [0, 1] and clipping happens for values outside that range, unless glEnable(GL_DEPTH_CLAMP) clamps it into that range.

I will have a look at the ARB extensions.

That’s not true.

Enabling depth clamp does two separate things. First, it turns off clipping against the near and far planes. Clipping happens in clip-space. Since clipping doesn’t happen against the near/far planes, Zndc for primitives is no longer restricted to the [-1,1] range (or [0, 1] if you’re using ARB_clip_control).

The other thing it does is, when computing the window-space depth value of a fragment, the window-space Z coordinate is clamped to the depth range.

Note that one facet of depth clamping pertains to the behavior of primitives, and the other to fragments generated by primitives.

So if you generate vertex positions outside of the NDC-space Z bounds, those primitives don’t get clipped. The depth range can then rescale those positions back into the valid depth range… in theory.

In practice, what GClements missed is that depth range values themselves are clamped to be in the [0, 1] range.

Yes, but you still have to contend with the fact that [0, 1] still represents all but 2 bits of floating-point numbers.

Thank you Alfonse_Reinhart, that cleared things up. The DEPTH in GL_DEPTH_CLAMP is a little misleading then I’d say. So it does remove clipping restriction.

But I assume everything that is behind the camera is still being clipped, isn’t it? It would not make sense if not, but I haven’t yet tested it.

Yes. You don’t need the near plane clip for that. Any point behind the viewpoint will inherently fail either the left or right plane test or both, and fail the top or bottom plane test or both.

The left and right tests check that -w <= x <= w (in clip space). For a point behind the viewpoint, w will be negative and -w will be positive. If x<-|w| => x<w => x<-w and it will fail the left plane test. If x>|w| => x>-w => x>w and it will fail the right plane test. If -|w|<x<|w| then x>w and x<-w and it will fail both. Similarly for the y direction. It’s only possible for both the left and right (or top and bottom) tests to pass if clip-space w is positive, which is only true if eye-space z is negative (a conventional perspective transformation sets wclip = -zeye).

1 Like

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.