I assume pretty much every 3D programmer runs into Z-buffer issues sooner or later. Especially when doing planetary rendering; the distant stuff can be a thousand kilometers away but you still would like to see fine details right in front of the camera.

Previously I have dealt with the problem by splitting the depth range in two and using the first part for near stuff and another for distant stuff. The boundary was floating, somewhere around 5km - quad-tree tiles up to certain level were using the distant part, and the more detailed tiles that by law of LOD are occurring nearer the camera used the other part.

Most of the time this worked. But in one case it failed miserably - when a more detailed tile appeared behind a less detailed one.

I was thinking about the ways to fix it, grumbling why we can't have a Z-buffer with better distribution, when it occurred to me that maybe we can.

Steve Baker's document explains common problems with Z-buffer. In short, the depth values are proportional to the reciprocal of Z. This gives amounts of precision near the camera but little off in the distance. Common method is then to move your near clip plane further away, which helps but also brings its own problems, mainly that .. the near clip plane is too far.

A much better Z-value distribution is a logarithmic one. It also plays nicely with LOD used in large scale terrain rendering.

Using the following equation to modify depth value after it's been transformed by the projection matrix:

Update: Logarithmic depth buffer optimizations & fixes

Where C is constant that determines the resolution near the camera, and the multiplication by w undoes in advance the implicit division by w later in the pipeline.

Resolution at distance x, for given C and n bits of Z-buffer resolution can be computed as

So for example for a far plane at 10,000 km and 24-bit Z-buffer this gives the following resolutions:

Along with the better utilization of z-value space it also (almost) gets us rid of the near clip plane.

And here comes the result.

Looking into the nose while keeping eye on distant mountains ..

10 thousand kilometers, no near Z clipping and no Z-fighting! HOORAY!

Update: a more complete and updated info about the logarithmic depth buffer and the reverse floating point buffer can be found in post Maximizing Depth Buffer Range and Precision.

Previously I have dealt with the problem by splitting the depth range in two and using the first part for near stuff and another for distant stuff. The boundary was floating, somewhere around 5km - quad-tree tiles up to certain level were using the distant part, and the more detailed tiles that by law of LOD are occurring nearer the camera used the other part.

Most of the time this worked. But in one case it failed miserably - when a more detailed tile appeared behind a less detailed one.

I was thinking about the ways to fix it, grumbling why we can't have a Z-buffer with better distribution, when it occurred to me that maybe we can.

Steve Baker's document explains common problems with Z-buffer. In short, the depth values are proportional to the reciprocal of Z. This gives amounts of precision near the camera but little off in the distance. Common method is then to move your near clip plane further away, which helps but also brings its own problems, mainly that .. the near clip plane is too far.

A much better Z-value distribution is a logarithmic one. It also plays nicely with LOD used in large scale terrain rendering.

Using the following equation to modify depth value after it's been transformed by the projection matrix:

z = log(C*w + 1) / log(C*Far + 1) * w //DirectX with depth range 0..1

or

z = (2*log(C*w + 1) / log(C*Far + 1) - 1) * w //OpenGL, depth range -1..1

Note: you can use the value of w after your vertices are transformed by your model view projection matrix, since the w component ends up with the view space depth. Hence w is used in the equations above.

Update: Logarithmic depth buffer optimizations & fixes

Where C is constant that determines the resolution near the camera, and the multiplication by w undoes in advance the implicit division by w later in the pipeline.

Resolution at distance x, for given C and n bits of Z-buffer resolution can be computed as

Res = log(C*Far + 1) / ((2^n - 1) * C/(C*x+1))

So for example for a far plane at 10,000 km and 24-bit Z-buffer this gives the following resolutions:

1m 10m 100m 1km 10km 100km 1Mm 10Mm ------------------------------------------------------------------------ C=1 1.9e-6 1.1e-5 9.7e-5 0.001 0.01 0.096 0.96 9.6 [m] C=0.001 0.0005 0.0005 0.0006 0.001 0.006 0.055 0.549 5.49 [m]

Along with the better utilization of z-value space it also (almost) gets us rid of the near clip plane.

And here comes the result.

Looking into the nose while keeping eye on distant mountains ..

10 thousand kilometers, no near Z clipping and no Z-fighting! HOORAY!

#### More details

The C basically changes the resolution near the camera; I used C=1 for the screenshots, having theoretical resolution 1.9e-6m. However, the resolution near the camera cannot be utilized fully as long as the geometry isn't finely tessellated too, because the depth is interpolated linearly and not logarithmically. On models such as the guy on the screenshots it is perfectly fine to put camera on his nose, but with models with long stripes with vertices few meters apart the bugs from the interpolation can be visible. We will be dealing with it by requiring certain minimum tessellation.#### Fragment shader interpolation fix

Ysaneya suggested a fix for the artifacts occurring with thin or large triangles when close to the camera, when perspectively interpolated depth values diverge too much from the logarithmic values, by writing the correct Z-value at the pixel shader level. This disables fast-Z mode but he found the performance hit to be negligible.Update: a more complete and updated info about the logarithmic depth buffer and the reverse floating point buffer can be found in post Maximizing Depth Buffer Range and Precision.

## 4 comments:

this is damn sexy! :) awesome

What a beautiful masterpiece of virtual engineering! You guys should be working for the big ones...

Great solution!

Is there a source code you are willing to share?

It would be highly appreciated.

Source code is really just that one line modifying the value of z, that you put at the end of your vertex shader. Here's what I use:

gl_Vertex = modelviewproj * pos;

gl_Vertex.z = logzbuf(gl_Vertex, invfarplanecoef);

Where logzbuf function is:

float logzbuf( vec4 xyzw, float invfarplanecoef ) {

return (log(1 + xyzw.w) * invfarplanecoef - 1) * xyzw.w;

}

invfarplanecoef is a uniform containing precomputed value:

invfarplanecoef = 2.0f / log(farp + 1);

with farp being the far plane distance.

Post a Comment