I have implemented the Z pre-pass technique into my engine, leading to a 2x performance improvement in a test scene.
However, the technique darkens the objects (but not the skybox), introduces z-fighting, and causes weird artifacts at the border of objects:
Here is the same picture without z pre-pass (performance divided by 2, but no visual bug):
Here is the depth framebuffer setup:
GLCall(glGenFramebuffers(1, &cameraDepthMapFBO));
GLCall(glGenTextures(1, &cameraDepthMapTexture));
GLCall(glBindTexture(GL_TEXTURE_2D, cameraDepthMapTexture));
// If I put GL_FLOAT instead of GL_UNSIGNED_INT here, it doesn't work.
GLCall(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
SCR_WIDTH, SCR_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL));
GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
GLCall(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
GLCall(glBindFramebuffer(GL_FRAMEBUFFER, cameraDepthMapFBO));
GLCall(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, cameraDepthMapTexture, 0));
GLCall(glDrawBuffer(GL_NONE));
GLCall(glReadBuffer(GL_NONE));
GLCall(glBindFramebuffer(GL_FRAMEBUFFER, 0));
I then do a z pre-pass where I render the scene on the cameraDepthMapFBO framebuffer:
GLCall(glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT));
GLCall(glBindFramebuffer(GL_FRAMEBUFFER, cameraDepthMapFBO));
GLCall(glClear(GL_DEPTH_BUFFER_BIT));
// ... all draw commands
GLCall(glBindFramebuffer(GL_FRAMEBUFFER, 0));
Finally, in the main rendering pass, I copy the depth data from the pre-pass into the main depth buffer, and I render the scene.
// Clear screen, define viewport
GLCall(glClearColor(0.01f, 0.01f, 0.01f, 1.0f));
GLCall(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
GLCall(glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT));
// Copy depth framebuffer onto main depth buffer
glEnable(GL_DEPTH_BUFFER_BIT);
GLCall(glBindFramebuffer(GL_READ_FRAMEBUFFER, cameraDepthMapFBO));
GLCall(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
GLCall(glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST));
// Set depth settings
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDepthFunc(GL_LEQUAL);
// ... all draw commands
// Restore depth settings
glDepthMask(GL_TRUE);
Observations:
- I have tested to copy the cameraDepthMapFBO via a shader instead of simply using glBlitFramebuffer. It leads to the same result.
- The depth map itself seems correct, because I already use it for SSAO.
- In the framebuffer setup phase, I have noted that it stops working if I put "GL_FLOAT" instead of "GL_UNSIGNED_INT" as glTexImage2D parameter.
Any idea of what could be wrong?
invariant
qualifier on your vertex position, to ensure the floating point math is not optimized differently between the depth-only and real-colour shader passes. A small change in the ordering of operations can change which way a bit rounds, which is enough to throw off anLEQUAL
test. But again I'd need an OpenGL expert to weigh in as I haven't experimented with this myself. I hope those leads are useful to you or other readers who can take this further. \$\endgroup\$