**0**

# Tip of the day: logarithmic zbuffer artifacts fix

**Logarithmic zbuffer artifacts fix**

In cameni's Journal of Lethargic Programmers, I've been very interested by his idea about using a logarithmic zbuffer.

Unfortunately, his idea comes with a couple of very annoying artifacts, due to the linear interpolation of the logarithm (non-linear) based formula. It particularly shows on thin or huge triangles where one or more vertices fall off the edges of the screen. As cameni explains himself in his journal, basically for negative Z values, the triangles tend to pop in/out randomly.

It was suggested to keep a high tesselation of the scene to avoid the problem, or to use geometry shaders to automatically tesselate the geometry.

I'm proposing a solution that is much more simple and that works on pixel shaders 2.0+: simply generate the correct Z value at the pixel shader level.

In the vertex shader, just use an interpolator to pass the vertex position in clip space (GLSL) (here I'm using tex coord interpolator #6):

void main()

{

vec4 vertexPosClip = gl_ModelViewProjectionMatrix * gl_Vertex;

gl_Position = vertexPosClip;

gl_TexCoord[6] = vertexPosClip;

}

Then you override the depth value in the pixel shader:

void main()

{

gl_FragColor = ...

const float C = 1.0;

const float far = 1000000000.0;

const float offset = 1.0;

gl_FragDepth = (log(C * gl_TexCoord[6].z + offset) / log(C * far + offset));

}

Note that as cameni indicated before, the 1/log(C*far+1.0) can be optimized as a constant. You're only really paying the price for a mad and a log.

Quality-wise, I've found that solution to work perfectly: no artifacts at all. In fact, I went so far as testing a city with centimeter to meter details seen from thousands of kilometers away using a very very small field-of-view to simulate zooming. I'm amazed by the quality I got. It's almost magical. ZBuffer precision problems will become a thing of the past, even when using large scales such as needed for a planetary engine.

There's a performance hit due to the fact that fast-Z is disabled, but to be honnest in my tests I haven't seen a difference in the framerate. Plus, tesselating the scene more or using geometry shaders would very likely cost even more performance than that.

I've also found that to control the znear clipping and reduce/remove it, you simply have to adjust the "offset" constant in the code above. Cameni used a value of 1.0, but with a value of 2.0 in my setup scene, it moved the znear clipping to a few centimeters.

**Results**

Settings of the test:

- znear = 1.0 inch

- zfar = 39370.0 * 100000.0 inches = 100K kilometers

- camera is at 205 kilometers from the scene and uses a field-of-view of 0.01°

- zbuffer = 24 bits

Normal zbuffer:

http://www.infinity-universe.com/Infinity/Media/Misc/zbufflogoff.jpg

Logarithmic zbuffer:

http://www.infinity-universe.com/Infinity/Media/Misc/zbufflogon.jpg

**Future works**

Could that trick be used to increase precision of shadow maps ?

Btw. the log-z equation actually utilizes only half the z-buffer range because z/w will lie in [0,1] range and not [-1,1] as OpenGL would like. But like you've said - it's almost magical anyway [smile]

We are also thinking about using it for logarithmic shadow maps, see Logarithmic Perspective Shadow Maps

Thanks!

Yeah. But of course it depends on the application (if the bottleneck is somewhere else, for example bandwidth, it won't show up) and the hardware, so I hesitate to say that it's no problem in general.

I need to do some more serious testing in a "real" scene. Unfortunately my engine isn't in a stage to do this test right now (need to re-enable a lot of the pipeline), but I'm confident the benefits will outweight the costs (if any).

True, I forgot that. Couldn't it be solved by doing z = z * 2 - 1 at the end ? The w shouldn't come into play as we're already in the pixel shader.

Cool, I'll have a look, thanks.

This can get a notorious on bigger screen resolutions and the more pixels are covered by non-overlapping triangles.

You also lose Early Z, which is a shame if you do a lot of overdraw and you were decreasing fillrate by rendering front to back. You lose that advantage.

Early Z is not automatic, so you won't see a difference if you weren't already doing it. Early-Z is great when you're GPU-bound, but makes you more CPU-bound.

So, if you don't have big screen resolutions, bandwidth-limited, don't consume lot of fillrate, and/or do a lot of overdraw, writting to the Z buffer won't hit performance.

Cheers

Dark Sylinc

More info here

Perhaps something like this could be done in the vertex shader?

Essentially for negative z values inverting the graph given by positive Z values.

Cheers,

Martin

Nope, I already tried that, to symmetrize the function ( with success ) or to use alternative functions, but the problem still remains, you get various popping of triangles as soon as some vertices as behind the camera.

The problem is not so much in the values of Z being negative - lower C linearizes the function well enough so that this would not be problem. Problem is that rasterizer interpolates Z/W and 1/W linearly and computes per-pixel z by dividing the two values. It does this to obtain perspective correct values. The value written to Z-buffer could be a different thing from this all, but actually it's taken from this.

Anyway, the problem is that we are pre-multiplying Z with values of W at the vertices, to get rid of inherent 1/W. Basically then, even when the logarithmic function is linear enough in +-200m range, we are also linearly interpolating between values of W. But since the rasterizer interpolates 1/W, the corresponding values of W are quite different there.

I'm currently using the pixel shader fix only for objects that are close enough to pass through the near plane

* I don't really understand this log trick with Z: vertex shader has 32bit float precision calculation, z-buffer also has 32-bit precision. If, after matrix multiplication z value already lost necessary precision, doing log here just increases rounding error & nothing more. At the same time, if resulting z value still has enough precision then z-buffer perfectly will store the resulting value. So where is the win here?

* Lack of fast-z optimization appears because you're doing logarithm in pixel shader, right? It causes a confusion for me, because I didn't even imagine that logarithm is being done in pixel shader. What was a reason of using log in PS?

* About multiplying on 'w' - if you don't need w value (you're trying to compensate for the division) then just make it equal to '1', don't need to multiply on 'w' then (& you may also avoid w = 0 suspicious situations).

* Yes, fast-Z problem arises when you use the logarithm in pixel shader, what is done to suppress the artifacts appearing when polygons cross the near plane. You may use the logarithm in pixel shader only for objects crossing the plane, though.

* If you mess with W you will affect perspective correct texturing ...

Note: GameDev.net moderates comments.