Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid Z-fighting in distance?

So I recently watched a video on Z-fighting, and learned of a simple way to take care of it--mostly. The solution given was to actually skew the projection so that closer objects were given more space for more accurate depth testing (since floats can only be so precise), and further away objects were crammed into a small area of the projection. Now I'm quite new to OpenGL and graphics programming (just working through slowly), and I haven't actually made anything complex enough where this is a problem for me, but I'll probably need to know this in the future. Anyways, the new problem posed by said solution is even worse Z-fighting in the distance (e.g. the mountains in Skyrim, Rust, etc.). Is there a better work around that doesn't involve graphical compromises, even if it does cost performance? Speaking hypothetically (since I'm not totally cozy with the OpenGL pipeline yet), could Z-values in a program be cast to doubles just before being clamped for depth testing?

Let me clarify. Think of Skyrim. Notice how the mountains sometimes flicker? When a scene is rendered with OpenGL, all the objects are crammed, or "clamped" into a small coordinate plane with Z-values from -1.0 to 1.0. Then depth testing is performed on every object--trees, snow, mountains, animals, houses, you name it, so that things aren't drawn when they're covered by something else. However, floating points can only get to a certain level of precision, so clamping hundreds of objects into a tiny space inevitably results in some having the exact same Z-coordinates, and the two objects flicker together on screen in a phenomenon known as "Z-fighting". I'm asking if every object's depth (z-) coordinates can be cast into doubles, so that they have sufficient precision (worth the negligible extra memory used for a negligible period of time) in order to accurately draw objects in the right order, without clipping into each other.

like image 498
Sam Claus Avatar asked Dec 18 '22 23:12

Sam Claus


2 Answers

When a scene is rendered with OpenGL, all the objects are crammed, or "clamped" into a small coordinate plane with Z-values from -1.0 to 1.0.

This is only part of the story. The other part is that z is stored non-linearly: the part with the highest 'fidelity' is that close to the near Z plane, and the fidelity decreases as you travel backwards. See this page for a formula.

The OpenGL FAQ 12. The Depth Buffer discusses this in 12.070 Why is there more precision at the front of the depth buffer?, and 12.080 proposes multi pass rendering for distinct z distances.

You can query the current depth buffer size with glGetIntegerv(GL_DEPTH_BITS, &bits);, but (after searching) there seems to be no standard way to change it to use a greater (or lesser) depth.

Z fighting in far-off objects can be countered by not drawing them as 3D objects. For instance, if the mountains in your example are very far off, any parallax effects will be invisible. So in that case you'd probably be better off with drawing far objects onto a skybox.

like image 91
Jongware Avatar answered Dec 29 '22 03:12

Jongware


You can workaround. Usually you render all objects in one go by sorting from nearest to farest.

You can instead break the objects in 2 groups based on distance, call them FarGroup and NearGroup.

If your application do not have certain restrictions such as:

  • You are not using stencil buffer for something
  • You do not need Depth for special effects (ScreenSpace Ambient Occlusion, Depth of Field, etc)

You can use the stencil buffer to resolve the Z-fight issue.

  • Clear Stencil,Depth and Color buffers
  • You setup a stencil function so that every object drawn set a bit

    glStencilOp(GL_KEEP,GL_KEEP, GL_INCR);

    glStencilFunc( GL_ALWAYS, 1, 0x01);

  • Then you render the NearGroup

  • Clear depth buffer
  • Setup a stencil test that draw only where stencil bit is not set

    glStencilOp(GL_KEEP,GL_KEEP, GL_KEEP);

    glStencilFunc( GL_NOTEQUAL, 1, 0x01);

  • Render FarGroup

You can somehow tweak by inverting rendering order to not use stencil (performance penality due to pixels overdraw) and maybe you can restrict SSAO only to NearGroup and use a baked AO value for far objects, but you get the idea, you gain something but you lose the ability to do something else (well there are many workarounds to limitations at cost of performance and some brainstorming).

For the camera you just setup 2 different frustums wich are just the original frustum sliced in 2.

If you are not too much constrained and you are able to use the above tecnique you get both no z-fight and also faster rendering than using 32 bit Z buffers (due to internal optimizations of GPUs)

like image 40
CoffeDeveloper Avatar answered Dec 29 '22 04:12

CoffeDeveloper