Search code examples
openglantialiasingfbomultisampling

Multisampling with glBlitFramebuffer


This is my first attempt to do multisampling (for anti-aliasing) with opengl. Basically, I'm drawing a background to the screen (which should not get anti-aliased) and subsequently I'm drawing the vertices that should be anti-aliased. What I've done so far:

//create the framebuffer:
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

//Generate color buffer:
glGenRenderbuffers(1, &cb);
glBindRenderbuffer(GL_RENDERBUFFER, cb);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, x_size, y_size);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, cb);

//Generate depth buffer:
glGenRenderbuffers(1, &db);
glBindRenderbuffer(GL_RENDERBUFFER, db);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT, x_size, y_size);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, db);

...

glBindFramebuffer(GL_FRAMEBUFFER, 0);
//draw background ... ...

glBindFramebuffer(GL_FRAMEBUFFER, fbo);
//draw things that should get anti-aliased ... ...

//finally:
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, x_size, y_size, 0, 0, x_size, y_size, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);

The problem is: when I call glBlitFramebuffer(...) the whole background gets black and I only see the anti-aliased vertices.

Any suggestions?


Solution

  • Normally, blending is the most obvious option if you want to render a new image/texture on top of existing rendering while taking transparency in the image into account. Looking at the rendering into the multisampled framebuffer as an image with transparency, that's exactly the situation you have.

    In this case, there are a couple of challenges that make the use of blending more difficult than usual. First of all, glBlitFramebuffer() does not apply blending. From the spec:

    Blit operations bypass the fragment pipeline. The only fragment operations which affect a blit are the pixel ownership test and the scissor test.

    Without multisampling in play, this is fairly easy to overcome. Instead of using glBlitFramebuffer(), you perform the blit by drawing a screen sized textured quad. Since all fragment operations are in play now, you could use blending.

    Howerver, the "drawing a textured quad" part gets much trickier since your content is multisampled. A few options come to mind.

    Render background to FBO

    You could render the background to the multisampled FBO instead of the primary framebuffer. Then you can use glBlitFramebuffer() exactly as you do now.

    You may think: "But I don't want my background to be anti-aliased!" That's not really a problem. You simply disable multisampling while drawing the background:

    glDisable(GL_MULTISAMPLE);
    

    I think that should give you what you want. And if it does, it's by far the easiest option.

    Multisample Textures

    OpenGL 3.2 and later support multisample textures. For this, you would use a texture instead of a renderbuffer as the color buffer of your FBO. The texture is allocated with:

    glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8,
                             xsize, ysize, GL_FALSE);
    

    There are other aspects that I can't all cover here. If you want to explore this option, you can read up on all the details in the spec or other sources. For example, sampling of the texture in the shader code works differently, with a different sampler type, and sampling functions that only allow you to read one sample at a time.

    Two-Stage Blitting

    You could use a hybrid of glBlitFramebuffer() for resolving the multisample content, and the "manual" blit for blending the content into the default framebuffer:

    1. Create a second FBO where the color attachment is a regular, not multisampled texture.
    2. Use glBlitFramebuffer() to copy from multisampled renderbuffer in first FBO to texture in second FBO.
    3. Set up and enable blending.
    4. Draw a screen sized quad using the texture that was the attachment to the second FBO.

    While this seems somewhat awkward, and requires an extra copy which is undesirable for performance, it is fairly straightforward.

    Render the background last

    For this, you do exactly what you're doing now, copying the multisampled FBO content to the default framebuffer with glBlitFramebuffer(). But you do this first, and render the background afterwards.

    You may think that this wouldn't work because it puts the background in front of the other content, which makes it... not much of a background.

    But here is where blending comes into play again. While blending content on top of other content is the most common way of using blending, you can also use it to render things behind existing content. To do this, you need a few things:

    • A framebuffer with alpha planes. How you request that depends on the window system/toolkit you use for your OpenGL setup. It's typically in the same area where you request your depth buffer, stencil buffer (if needed), etc. It is often specified as a number of alpha planes, which you typically set to 8.
    • The right blend function. For front to back blending, you typically use:

      glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
      

      This adds the new rendering where nothing was previously rendered (i.e. the alpha in the destination is 0), and will keep the previous rendering unchanged where there was already rendering (i.e. the destination alpha is 1).

      The blending setup can get a little trickier if your rendering involves partial transparency.

    This may look somewhat complicated, but it's really quite intuitive once you wrap your head around how the blend functions work. And I think it's overall an elegant and efficient solution for your overall problem.