[SOLVED] Rendering problem when two faces are close to each other

I’m trying to render a load of squares on rods (top image):

I’m doing this by with drawInstanced() for two gl::Batchs, one for the rods (a geom::Cube) and one the squares (a manually constructed square gl::VboMesh). The rods is slightly offset from the squares, but there’s a rendering issue, which I don’t understand. I’m calling gl::enableDepth() in draw().

I tried not drawing the rod and calling gl::enableFaceCulling() to hide the squares facing away, which gives much better results in terms of rendering (middle image).

But I don’t want to see through the rods! It looks like the issue is the proximity of the rods to the squares. If I reduce the size of the rods (and draw them in red for visibility) the the squares look better but still some aren’t drawn properly when they’re far away (lower image

Is there anything I can do to improve the quality of the rendering of the squares?

I guess you are using a persp camera.
Have you tried to reduce the view range (far and near)?

L

What you’re experiencing is called “z-fighting”, and as @dashandslash suggests, this has to do with depth buffer precision. If you specify an unusually large range (e.g. you want to render everything from 1 micrometer to 1 lightyear in the distance), the floating point precision of the buffer will not be able to distinguish between planes that are close to each other.

What makes matters worse is that depth is non-linear: there is much more precision available close to the near plane than there is further away from the near plane. A good article about it can be found here.

The solution is to tweak the depth range, especially the near plane distance. Make it as large as you can. And also reduce the far plane distance.

If that’s not enough, make sure to use the highest depth buffer precision available. The default (I think) currently is 24 bit with 8 bits stencil mask (GL_DEPTH24_STENCIL8). If you don’t need the stencil buffer, try GL_DEPTH_COMPONENT32 or even GL_DEPTH_COMPONENT32F. Note that this may have an effect on performance, so test this.

Finally, if all else fails, try looking into “reverse depth”, as mentioned in this article. This, however, is pretty advanced stuff, so try adjusting the near plane first.

-Paul

Edit: see below for additional resources and code snippets.

1 Like

Thanks @paul.houx for the in-depth explanation!
I feel so lazy now for not having invested a bit of more effort on explaining the reason behind my answer (which ended up being a question).

And thanks for the links, great articles!

L

Thanks @dashandslash and @paul.houx for your detailed answer. I did have some outrageous values for the near and far planes!

Putting something reasonable has done the trick. Thank you.

Just to give an extra solution: while the answer given by @paul.houx is usually the right fix, sometimes you might actually want two geometries on top of each other, with one having priority over the other (for example, when using decals).

In that case you can use glPolygonOffset to achieve that effect. This article is for Three.js but it gives a nice simple explanation.

2 Likes

Reviving an older thread here, since I’m currently banging my head against the wall with a similar problem. I’m trying to draw some blocks with text in front of them, and am seeing what looks like good old z-fighting. I have the text texture set to the position of the block’s front face + 1, so they’re not co-planar, but that doesn’t seem to matter.

I’ve tried…

  • playing with the near & far planes (originally I was at 0.01…10,000 but reduced to a range like 100…500)
  • changing the depth buffer to 32bits (via RendererGl(RendererGl::Options().depthBufferDepth(32)) since I’m not rendering into an FBO – is there a way to check that this is getting correctly set?)
  • briefly played with glPolygonOffset

…but none of these methods seem to work!

image

I’m working inside a scenegraph, but very curiously, I made a separate test app with a texture in front of a cube and it seems fine! I never called setPerspective() so it’s using the default 0.1…1000 near/far planes. So I’m not sure what’s going on here… in this example the texture was the front face + 0.1, so a very small offset but no issues with the render in this case.

image

My thoughts if I can’t get this worked out is to just texture the cube directly instead of drawing a texture in front, but it feels like this shouldn’t be that hard!

This looks like Z-fighting indeed. Do you really need the near plane at 0.1? Try to increase that value to 1 or more to see if that helps.

A few possible solutions if changing the near value does not work:

  • Try rendering the text first, using alpha testing. See “Discarding fragments” in this article. This will most certainly work, but it may not look the way you like.
  • Use textured text: I’d recommend this in your situation, because this is more efficient anyway (if the text does not change every frame). You could even render all texts to a single (grayscale) texture first (where the texture’s width loosely matches the cube size and the height leaves enough room for all lines of text), then use instanced rendering to render all cubes at once. The texture coordinates for the text are passed as per-instance data. Clip and center the text in the shader.
  • If you’re still adamant to the current approach, this article proposes a few solutions:
    • Don’t use the concatenated ciModelViewProjection matrix, but do the concatenation in the vertex shader:
    uniform mat4 ciModelView;
    uniform mat4 ciProjectionMatrix;
    
    in vec4 ciPosition;
    
    void main(void) {
       gl_Position = ciProjectionMatrix * ciModelView * ciPosition;
    } 
    
    • Construct a projection matrix with an infinite far plane:
    const float zNear = 0.1f;
    const float viewAngleVertical = glm::radians( 90.0f );
    const float fov = 1.0f / glm::tan( viewAngleVertical * 0.5f );
    const float aspect = float( resolutionX ) / float( resolutionY );
    
    // Infinite Perspective matrix
    glm::mat4 projectionMatrix = {
       fov/aspect, 0.0f,   0.0f,  0.0f,
             0.0f,  fov,   0.0f,  0.0f,
             0.0f, 0.0f,  -1.0f, -1.0f,
             0.0f, 0.0f, -zNear,  0.0f
    };
    
    • Use Reversed Z, where the values in the depth buffer range from 1 (nearest) to 0 (farthest), instead of 0 to 1:
    glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
    glDepthFunc(GL_GREATER);
    clearDepth( 0.0f );
    
    // Reversed Infinite Projection Matrix
    const float zNear = 0.1f;
    const float viewAngleVertical = glm::radians( 90.0f );
    const float fov = 1.0f / glm::tan( viewAngleVertical * 0.5f );
    const float aspect = float( resolutionX ) / float( resolutionY );
    glm::mat4 projectionMatrix =
    {
        fov/aspect, 0.0f,  0.0f,  0.0f,
              0.0f,  fov,  0.0f,  0.0f,
              0.0f, 0.0f,  0.0f, -1.0f,
              0.0f, 0.0f, zNear,  0.0f
    };
    

-Paul

P.S.: the reason your test app works, may be because you’re much more zoomed in.

3 Likes

Well it turns out that I had a transparent quad in the same plane as the text texture (essentially that object’s “background color”) that was the source of the fighting/flicker. Preventing that from rendering takes care of the issue – no changes to the camera or other techniques necessary. I’m confused as to why that would be an issue with alpha blending enabled, but very glad to have gotten it worked out. Thanks for all of the suggestions @paul.houx

Hi Matt,

my last post contained a lot of possible solutions that I never tried myself. So it inspired me to look at them in more detail. As it turned out, this required quite a substantial update to Cinder to make it support the latest OpenGL version (4.6), required for the glClipControl command.

I had to replace the current OpenGL loader (a.k.a. glLoad) with a new loader called GLAD. You can find this change in my fork of Cinder. There is currently a discussion among the Cinder devs about whether GLAD is the best choice, so keep in mind that my work on this does not mean it will be adopted in the official Cinder release.

On top of all this, I wrote additional functions to easily enable Reversed Z rendering. You can find this in my reverse-z branch. Check out the FightingPixelFighting sample.

I added a gl::enableDepthReversed( bool enable = true ) method that you call once at the beginning of your application. It will setup Cinder to clear the depth buffer with zero instead of one, set the depth comparison to GL_GREATER instead of GL_LESS, set the clip range to GL_ZERO_TO_ONE instead of GL_NEGATIVE_ONE_TO_ONE and adjust the projection matrix accordingly. Of course, there is also a gl::isDepthReversedEnabled() method.

The CameraPersp class now also supports infinite far clip distance, as well as zero-to-one clip mode and reverse depth.

Caveat: due to the fact that OS X hasn’t kept up with the times, this code will only work on Microsoft Windows and (in the near future) on Linux platforms. OpenGL ES is out of the question, too, so bad luck for iOS, ANGLE and Android.

-Paul

4 Likes