openglframebufferantialiasingmultisampling

OpenGL - How to draw to a multisample framebuffer and then use the result as a normal texture?


I'm developing a little gamedev library. One of the elements of this library is a Canvas (offscreen drawing area), which is implemented through an OpenGL framebuffer. So far, everything's been good, I generate a texture, attach it to a framebuffer, render to it, then use the framebuffer's texture as Texture2D.

Now, I'd like to add antialiasing to my library, so I'd like to be able to set multisampling on a Canvas. Now I'm confused because I've found that you need to alter shaders to use multisample textures and so on.

So, what should I do to enable multisampling for my framebuffers so that I minimize changing the rest of the code of the library? I'd like to just use the rendering result as a regular Texture2D if possible.


Solution

  • Just to make sure that there's no confusion. You can't just create a texture that is x times bigger and then wish that filters will do the magic. Because GL_LINEAR, etc. only averages the four texels closest to the center of the pixel being textured.

    To create a multisample texture you'd use glTexImage2DMultisample() (available in core since 3.2). You'd set it up like this.

    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
    glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA8, width, height, false);
    

    It should be self-explanatory as samples being the amount of samples in the multisample texture. Also change the internalformat as you please.

    To attach the texture to a framebuffer you equally use glFramebufferTexture2D(). But instead of setting the textarget as GL_TEXTURE_2D you set it to GL_TEXTURE_2D_MULTISAMPLE.

    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
    

    Remember to check the status of your framebuffer.

    In your shader you'll also have to use sampler2DMS to be able to access the multisample texture. However note that multisample textures work quite differently compared to regular textures. If you want to read from the texture you'll have to use texelFetch().

    So if you want to sample from a multisample texture then you can't use texture() but would have to utilize texelFetch() with something like.

    uniform int texSamples;
    uniform sampler2DMS tex;
    
    vec4 textureMultisample(sampler2DMS sampler, ivec2 coord)
    {
        vec4 color = vec4(0.0);
    
        for (int i = 0; i < texSamples; i++)
            color += texelFetch(sampler, coord, i);
    
        color /= float(texSamples);
    
        return color;
    }
    

    Note that texelFetch() doesn't take normalized coordinates, you can circumvent this with something like:

    vec2 uv = vec2(0.5, 0.5); // normalized coordinates
    ivec2 texSize = textureSize(tex, 0);
    ivec2 texCoord = ivec2(uv * texSize);
    vec4 color = textureMultisample(tex, texCoord);
    

    At the end of the day if you want display the crisp anti-aliased result, you'll have to blit it to the screen.

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    glDrawBuffer(GL_BACK);
    glBlitFramebuffer(0, 0, src_width, src_height, 0, 0, dst_width, dst_height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
    

    If you need a multisample depth buffer then look into glRenderbufferStorageMultisample().

    Also make sure that glEnable(GL_MULTISAMPLE). However today most drivers enable it by default.

    Last but not least, here's a few other Stack Overflow/Exchange question related to multisampling that you might find interesting.