Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linear depth buffer

Many people use usual perspective matrix with third line like this:

(0  0  (n+f)/(n-f) 2*n*f/(n-f))

But it has problem with float precision near far clipping surface. The result is z-fighting. What about to use linear transformation of z? Let's change the matrix third line to this:

(0  0  -2/(f-n) (-f-n)/(f-n))

It will be linear transformation z from [-n, -f] to [-1, 1]. Then, we will add the line in vertex shader:

gl_Position.z *= gl_Position.w;

After perspective divide the z value will be restored.

Why don't it used everywhere? I found a lot of articles in internet. All of them used a usual matrix. Is linear transformation described by me has problems what I don't see?

Update: This is not a duplicate of this. My question is not about how to do linear depth buffer. In my case, the buffer is already linear. I don't understand, why is this method not used? Are there traps in the inner webgl pipeline?

like image 723
lzv Avatar asked Dec 08 '22 16:12

lzv


2 Answers

The approach you're describing simply doesn't work. One advantage of a hyperbolic Z buffer is that we can interpolate the resulting depth values linearly in screen space. If you multiply gl_Position.z by gl_Position.w, the resulting z value will not be linear in screen space any more, but the depth test will still use linearly interpolated values. This results in your primitives becoming bend in the z-dimension, leading to completely wrong occlusions and intersections between nearby primitives (especially if the vertices of on primitive lie near the center of the other).

The only way to use a linear depth buffer is to actually do the non-linear interpolation for the Z value yourself in the fragment shader. This can be done (and boil's down to just linearly transform the perspective-corrected interpolated w value for each fragment, hence it is sometimes called "W buffering"), but you're losing the benefits of the early Z test and - much worse - of the hierarchical depth test.

An interesting way to improve the precision of the depth test is to use a floating point buffer in combination with a reversed Z projection matrix, as explained in this Depth Precision Visualized blog article.

UPDATE

From your comment:

Depth in screen space is linear interpolation of NDC, how I understand form here. In my case, it will be linear interpolation of linear interpolation of z from camera space. Thus, depth in screen space interpolated already.

You mis-understood this. May main point was that the linear interpolation in screen space is only valid if you're using Z values which are already hyperbolically distorted (like NDC Z). If you want to use eye-space Z, this can not be linearly interpolated. I made some drawings of the situation:

enter image description here

This is a top-down view on eye-space and NDC. All drawings are actually to scale. The green ray is a view ray going through some pixel. This pixel happens to be the one which directly represents the mid-point of that one triangle (green point).

After the projection matrix is applied and the division by w has happened, we are in normalized device coordinates. Note that the direction of the viewing ray is now just +z, and all view rays of all pixels became parallel (so that we can just ignore Z when rasterizing). Due to the hyperbolic relation of the z value, the green point now does not lie on exactly on the center any more, but is squeezed towards the far plane. However, the important point is that this point now lies on the straight line formed by the (hyperbolically distorted) end points of the primitive - hence we simply can interpolate z_ndc linearly in screen space.

If you use a linear depth buffer, the green point now lies at z in the center of the primitive again, but that point is not on the straight line - you actually bend your primitives.

Since the depth test will use a linear interpolation, it will just get the points as in the rightmost drawing as input from the vertex shader, but will interpolate them linearly - connecting those points by straight lines. As a result, the intersection between primitives will not be where it actually has to be.

Another way to think of this: Imagine you draw some primitive which extents into the z-dimension, with some perspective projection. Due to perspective, stuff that is farther away will appear smaller. So if you just go one pixel to the right in screen space, the z extent covered by that step will actually bigger if the primitive is far away, while it will become smaller and smaller as closer you get. So if you just go in equal-sized steps to the right, the z-steps you're making will vary depending on the orientation and position of your primitive. However, we want to use a linear interpolation, so we want to make the same z step size for every x step. The only we to do this is by distorting the space z is in - and the hyperbolical distortion introduced by the division by w exactly does that.

like image 143
derhass Avatar answered Jan 06 '23 22:01

derhass


We don't use a linear transformation because that will have precision problems at all distances equally. At least now, the precision problems only show up far away, where you're less likely to notice. A linear mapping spaces the error out evenly, which makes errors more likely to happen close to the camera.

like image 32
Nicol Bolas Avatar answered Jan 06 '23 23:01

Nicol Bolas