Merging two separate framebuffers onto default framebuffer after depth testing

3.2k Views Asked by At

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);
  }

}
3

There are 3 best solutions below

8
On BEST ANSWER

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.

1
On

Blitting will never use the depth test, so you have to use a full-screen shader pass to combine both framebuffers. There are two options:

  1. Combine both framebuffers into a third one. This requires that both the color attachments as well as the depth attachments of both input FBOs are textures. You then render a full-screen quad and sample from both color buffers and depth textures. You basically do the depth test manually in the shader by comparing the two depth to decide which of the two color values you use as final output color for the fragment.

  2. You composite one of the framebuffers into the other, using the real depth test. In that case, only one of the FBOs has to use textures, the other one can use renderbuffers or the window-system provided buffers. You just have to render a full-screen quad, this time sampling the depth and color textures only from one input FBO, and render into the output FBO with depth testing enabled. YOu just set the color value as output of the fragment shader and additionally output the depth value to gl_FragDepth.

1
On

This should be possible with a fragment shader that uses the color data from both framebuffers and the depth data from both framebuffers and fills in each pixel by evaluating, for each fragment, the texture corresponding to the fragment that won the depth test.

#version 430

layout(location = 0) in vec2 tex_coord;

layout(binding = 0) uniform sampler2D color_texture_1;
layout(binding = 1) uniform sampler2D color_texture_2;
layout(binding = 2) uniform sampler2D depth_texture_1;
layout(binding = 3) uniform sampler2D depth_texture_2;

layout(location = 0) out vec4 fragment_color;

void main() {
    float depth_1 = texture(depth_texture_1, tex_coord).z;
    float depth_2 = texture(depth_texture_2, tex_coord).z;
    if(!/*check to verify *something* was rendered here in either framebuffer*/)
        discard;
    else {
        if(depth_1 > depth_2) //I'm pretty sure positive z values face the user
            fragment_color = texture(color_texture_1, tex_coord);
        else
            fragment_color = texture(color_texture_2, tex_coord);
    }
}

You'd render a (full-screen, presumably) quad, use a pass-through vertex shader with this fragment shader, and attach the respective textures.

I don't know what format your Depth Texture is in; my assumption is that it contains vectors representing the individual fragment coordinates, and that the z-coordinate contains its depth. If that isn't the case, you'll need to make adjustments.