How to avoid Z-fighting in distance?
Asked Answered
P

4

5

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.

Pinup answered 4/10, 2015 at 20:40 Comment(4)
Nice question, but "casting" to a larger type does not automatically increase detail. If you store the value of Pi in a float and cast it to a double later on, it won't automagically gain more significant digits.Postrider
No but cast z-values to doubles before they're clamped--so that when they're clamped between -1.0 and 1.0, there's much more precision and you don't end up with overlapping Z-values as easily.Pinup
You'll have to keep in mind that OpenGL's depth buffer needs to store a depth value for every pixel. The only way to increase its fidelity is to set its depth to use more bytes. (And note that some OpenGL implementations have a limitation to this size.) You may want to read opengl.org/archives/resources/faq/technical/depthbuffer.htm.Postrider
Thanks Jongware! That's interesting. Multi passing seems a bit inefficient, but that article had some other interesting stuff and your comment implies that depth buffer precision isn't rigid. If you answer outside of the comment section, I'll choose your submission.Pinup
P
5

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.

Postrider answered 4/10, 2015 at 23:2 Comment(1)
The bit depth of the depth buffer of the default framebuffer is not controlled by the GL, but by the operating system. It is a property of the window (or whatever drawable is used), and as such, the platform's GL interfacing APIs (like wgl on windows, glX on unix/X11 and so on) must be used to set it. Don't expect to see wide support for more than 24 bits (integer) on common hardwaew, though. However, when using FBOs, 32bit floating point depth buffers are usually available.Carraway
E
3

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)

Edge answered 6/10, 2015 at 9:50 Comment(0)
C
3

first, an important detail: a frequent error is to have a near plane really too close to zero. Since z mapping is non-linear, then most of the dynamics goes at the very front and is missing at distance.

Clarendon answered 6/10, 2015 at 17:5 Comment(0)
C
3

Sorting all the geometry to bypass Z-buffer is costly. A classical compromise is to separate the scene in 2 or 3 big layers (like the very front ( cockpit / character / weapon ), close terrain, and distant landscape ), and to customize znear / zfar for each of them (resetting Z in between).

Clarendon answered 6/10, 2015 at 17:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.