Search code examples
openglframebufferdepth-bufferdepth-testing

Merging two separate framebuffers onto default framebuffer after depth testing


I have two framebuffers that I am rendering two different objects to. When I use the default framebuffer, I have both the objects rendering on the same one.

I want this behaviour to work when using multiple framebuffers! How do I merge two framebuffers and render the winning fragments on top (Depth tested)! Basically like a Photoshop Layer Merge but with depth testing!

I got as far as blitting a single framebuffer onto the default framebuffer, but I'm lost as to how I would merge two framebuffers together!

Note: I have a color and a depth attachment to the framebuffers.

Edit:

Alright. I almost have the setup of rendering to a quad working except for one little thing. My color buffes are properly sent to the shader using uniform samplers but my depth values return '0' all the time from the depth buffers.

This is how I have my depth buffers setup within the framebuffer.

  glGenFramebuffers(1, &_fbo);
  glBindFramebuffer(GL_FRAMEBUFFER, _fbo);

  glGenTextures(1, &_cbo);
  glGenTextures(1, &_dbo);

  {
    glBindTexture(GL_TEXTURE_2D, _cbo);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dim.x, dim.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

  }

  {
    glBindTexture(GL_TEXTURE_2D, _dbo);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, dim.x, dim.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
  }

  glBindFramebuffer(GL_FRAMEBUFFER, _fbo);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _cbo, 0);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _dbo, 0);

This is how I send uniform samplers to the shader.

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,cbo1);
glUniform1i(glGetUniformLocation(QuadShader.Program, "color1"),0);


glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, cbo2);
glUniform1i(glGetUniformLocation(QuadShader.Program, "color2"), 1);

glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, dbo1);
glUniform1i(glGetUniformLocation(QuadShader.Program, "depth1"), 2);


glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, dbo2);
glUniform1i(glGetUniformLocation(QuadShader.Program, "depth2"), 3);

glBindVertexArray(BgVao);
glDrawArrays(GL_TRIANGLES, 0, 6);

This is how my shader looks:

uniform sampler2D color1;
uniform sampler2D color2;
uniform sampler2D depth1;
uniform sampler2D depth2;

out vec4 FragColor;

void main()
{
  ivec2 texcoord = ivec2(floor(gl_FragCoord.xy));

  vec4 depth1 = texelFetch(depth1, texcoord,0);
  vec4 depth2 = texelFetch(depth2, texcoord,0);

  if(depth1.z > depth2.z)
  {
    FragColor = texelFetch(color1, texcoord, 0);
  }
  else
  {
   FragColor = texelFetch(color2, texcoord, 0);
  }

}

Solution

  • You will need a shader to achieve this. There is no built-in way to blit with depth values. Here is one way to do it that combines the contents of both FBOs.

    Vertex shader (assumes a quad is drawn from (-1,-1) to (1,1) )

    layout(location = 0) in vec4 Position;
    
    void main()
    {
        // Snap the input coordinates to a quad with it lower-left at (-1, -1, 0)
        // and its top-right at (1, 1, 0)
        gl_Position = vec4(sign(Position.xy), 0.0, 1.0);
    }
    

    The pixel shader could look like this:

    uniform sampler2D Color0;
    uniform sampler2D Color1;
    uniform sampler2D Depth0;
    uniform sampler2D Depth1;
    
    in vec2 TexCoords;
    layout(location = 0) out vec4 FragColor;
    
    void main()
    {
        ivec2 texcoord = ivec2(floor(gl_FragCoord.xy));
        float depth0 = texelFetch(Depth0, texcoord, 0).r;
        float depth1 = texelFetch(Depth1, texcoord, 0).r;
    
        // possibly reversed depending on your depth buffer ordering strategy
        if (depth0 < depth1) {
            FragColor = texelFetch(Color0, texcoord, 0);
        } else {
            FragColor = texelFetch(Color1, texcoord, 0);
        }
    }
    

    See also OpenGL - How to access depth buffer values? - Or: gl_FragCoord.z vs. Rendering depth to texture for how to access the depth texture.

    Note that I use texelFetch() here because linearly interpolating depth values does not give valid results.