5

I'm implementing a depth prepass in OpenGL. On an Intel HD Graphics 5500, this code works fine but on a Nvidia GeForce GTX 980 it doesn't (the image below shows the resulting z-fighting). I'm using the following code to generate the image. (Everything irrelevant to the problem is omitted.)

// ----------------------------------------------------------------------------
// Depth Prepass
// ----------------------------------------------------------------------------

glEnable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);

glUseProgam(program1); // The problem turned out to be here!

renderModel(...);

// ----------------------------------------------------------------------------
// Scene Rendering
// ----------------------------------------------------------------------------

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_FALSE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

glUseProgam(program2); // The problem turned out to be here!

renderModel(...);

It seems like the glDepthFunc is not changed to GL_LEQUAL. However, when I step through the GL calls in RenderDoc, glDepthFunc is set correnctly.

Does this sound like a driver bug or do you have suggestions what I could be doing wrong? When this is a driver bug, how can I implement a depth prepass anyway?

z-fighting on Nvidia GeForce GTX 980

Henkk
  • 609
  • 6
  • 18
  • Why do you disable depth writting with `glDepthMask(GL_FALSE)`? – Ripi2 Oct 24 '17 at 17:10
  • How many bits do you allocate for the depth buffer? – Michael IV Oct 24 '17 at 18:31
  • The code in the question is far from enough, there is so much that can go wrong. For example, did you properly qualify the `gl_Position` output as `invariant` in all shaders relevant for both passes? – derhass Oct 24 '17 at 19:44
  • 1
    @Rabbid76 I thought of `glPolygonOffset` but since the code worked on Intel I assumed there must be some other problem and wanted to explore that first. However, I implemented your solution (workaround) and it does the job. – Henkk Oct 24 '17 at 20:34
  • @Ripi2 If you're asking why: I disable depth writing because the main rendering pass does not need to render depth since it is already done by the prepass. If you're asking why with `glDepthMask(GL_FASLE)`: I presume that is the usual way. – Henkk Oct 24 '17 at 20:37
  • @MichaelIV I'm using 24 bits. 32 bits don't make a difference. – Henkk Oct 24 '17 at 20:38
  • @derhass I did not even consider `invariant` and was not aware of the problem it solves. In fact, I used a different program for the prepass that even computed `gl_Position` with a different expression. Declaring `gl_Position` as `invariant` in both shaders and equalizing the expressions solved the problem. Thanks! – Henkk Oct 24 '17 at 20:54

2 Answers2

12

When using a different shader program for the depth prepass it must be explicitly assured that this program generates the same depth values (although called on the same geometry) as the program for the main pass. This is done by using the invariant qualifier on gl_Position.

Variance explained by the GLSL Specification 4.4:

In this section, variance refers to the possibility of getting different values from the same expression in different programs. For example, say two vertex shaders, in different programs, each set gl_Position with the same expression in both shaders, and the input values into that expression are the same when both shaders run. It is possible, due to independent compilation of the two shaders, that the values assigned to gl_Position are not exactly the same when the two shaders run. In this example, this can cause problems with alignment of geometry in a multi-pass algorithm.

The qualifier is used as follows in this case:

invariant gl_Position;

This line guarantees that gl_Position is computed by the exact expression that was given in the shader without any optimization as this would change the operations and therefore quite likely change the result in some minor way.

In my concrete case, an assignment was the source of the problem. The Vertex Shader of the program for the main pass contained the following lines:

fWorldPosition = ModelMatrix*vPosition; // World position to the fragment shader
gl_Position = ProjectionMatrix*ViewMatrix*fWorldPosition;

The Vertex Shader of the program for the prepass computed gl_Position in one expression:

gl_Position = ProjectionMatrix*ViewMatrix*ModelMatrix*vPosition;

By changing this into:

vec4 worldPosition = ModelMatrix*vPosition;
gl_Position = ProjectionMatrix*ViewMatrix*worldPosition;

I solved the problem.

Henkk
  • 609
  • 6
  • 18
  • It is also possible to turn on depth biasing and move the depth prepass geometry with a small offset. Then turn on less_or_equal for the depth compare op in subsequent passes. – pmw1234 Dec 01 '22 at 13:27
0

Some of the versions of Sponza are huge. I remember I fixed that issues with one of two solutions:

The second approach is inferior on tiled architectures which leverages early depth test for hidden surface removal. On mobile platforms performance degradation can be quite noticeable.

Michael IV
  • 11,016
  • 12
  • 92
  • 223
  • As mentioned in the comment to the original question: I am using a 24 bit depth buffer and changing it to 32 bits does not help. The logarithmic depth buffer is interesting but *too much*. The scene is encapsulated by the frustum quite tightly and therefore (I presume) the bits are utilized well. The original scene is in fact huge but also scaled down. – Henkk Oct 24 '17 at 21:00