During the last week, I calculated the z value of a point projected to a screen for debugging reasons. This reminded me that depth values are badly distributed, with lots of precision near the front plane…
I remembered an article I read long ago about a technique to improve this. Out of interest, I looked it up and was happy to find that there has been some progress. I tought this might be interesting for anyone who tries to mitigate Z-Fighting, so I try to summarize my findings here. At very least this might help me to remember the details in the future… 😉
If you are not familiar with the distribution of values in the depth buffer, there is an excellent article visualizing the situation here. After demonstrating the issue, Nathan Reed presents a trick to improve the distribution of the depth values a lot, called…
Turns out that if you reverse the depth values of the near and far plane, so that near = 1 and far = 0, the inherent higher float precision for values around 0 helps to increase the depth resolution for objects further away. Of course this comes at the cost of reduced resolution close to the near plane, but since we have plenty resolution there, it does not matter. In fact it is a perfect fit, resulting in almost linearly distributed discrete values between the near and far plane.
The story could be over here, but there is a catch. This technique works fine with Direct3D, which has a [0, 1] depth range. OpenGL per default uses a [-1, 1] depth range. Inverting the near and far plane depth values does not help here, since the precision around 0 is already in the middle and stays there (as also shown in the article mentioned before).
OpenGL’s behavior can be changed by using one of the following extensions to make it behave like DirectX:
Are you doomed to suffer from z-fighting, if you are on a platform that does not support DirectX or OpenGL with the mentioned extensions, then? Turns out there is yet another approach, with similar results:
As proposed in the original article from 2009, the log value of the z coordinates can be calculated in the vertex shader, resulting in a nice distribution of discrete depth values, as shown here. There are two drawbacks, though:
- The vertex shader need to calculate the log value, which might decrease performance. But it does not seem to be that much, according to the benchmarks in the article.
- The vertex attributes might not be interpolated perspective-correct any more, resulting in visual artifacts on large triangles. This can either be mitigated with a high tessellation (resulting in smaller triangles) or by calculating the correct depth value for every fragment in the pixel shader. The second solution might reduce the performance again, so be sure to use the optimization described here.
There are multiple ways to improve the depth value distribution and reduce z-fighting issues. With Direct3D you can simply swap the near and far plane to do so. To achieve the same result with OpenGL, you might have to use other approaches, depending on your graphics card and platform. These approaches might decrease the performance, but at least there is always a way to reduce z-fighting issues (except for VERY old graphics card without shaders…).