使用 glBlitFramebuffer 进行多重采样

Multisampling with glBlitFramebuffer

这是我第一次尝试使用 opengl 进行多重采样(用于抗锯齿)。基本上,我在屏幕上绘制背景(不应该消除锯齿),然后我绘制应该消除锯齿的顶点。 到目前为止我做了什么:

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

问题是:当我调用 glBlitFramebuffer(...) 时,整个背景变黑,我只看到抗锯齿顶点。

有什么建议吗?

通常,如果要在现有渲染之上渲染新的 image/texture,同时考虑图像的透明度,混合是最明显的选择。将渲染到多重采样帧缓冲区中的图像视为具有透明度的图像,这正是您遇到的情况。

在这种情况下,有几个挑战使得混合的使用比平时更困难。首先,glBlitFramebuffer() 不应用混合。来自规范:

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

如果不使用多重采样,这很容易克服。您不使用 glBlitFramebuffer(),而是通过绘制屏幕大小的纹理四边形来执行 blit。由于现在所有片段操作都在进行,您可以使用混合。

然而,"drawing a textured quad" 部分变得更加棘手,因为您的内容是多重采样的。我想到了几个选项。

渲染背景到 FBO

您可以将背景渲染到多重采样 FBO 而不是主帧缓冲区。然后你就可以像现在一样使用glBlitFramebuffer()

您可能会想:"But I don't want my background to be anti-aliased!" 这不是什么大问题。您只需在绘制背景时禁用多重采样:

glDisable(GL_MULTISAMPLE);

我认为这应该可以满足您的需求。如果是这样,这是迄今为止最简单的选择。

多采样纹理

OpenGL 3.2 及更高版本支持多重采样纹理。为此,您可以使用纹理而不是渲染缓冲区作为 FBO 的颜色缓冲区。纹理分配为:

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

还有其他方面我无法在此一一详述。如果你想探索这个选项,你可以阅读规范或其他来源中的所有细节。例如,着色器代码中的纹理采样工作方式不同,采样器类型不同,采样函数一次只允许读取一个样本。

两阶段块传输

您可以混合使用 glBlitFramebuffer() 来解析多重采样内容,并使用 "manual" blit 将内容混合到默认帧缓冲区中:

  1. 创建第二个 FBO,其中颜色附件是常规纹理,而不是多重采样纹理。
  2. 使用 glBlitFramebuffer() 从第一个 FBO 中的多重采样渲染缓冲区复制到第二个 FBO 中的纹理。
  3. 设置并启用混合。
  4. 使用作为第二个 FBO 附件的纹理绘制一个屏幕大小的四边形。

虽然这看起来有些尴尬,并且需要额外的副本,这对性能来说是不受欢迎的,但它相当简单。

最后渲染背景

为此,您可以按照现在的方式进行操作,使用 glBlitFramebuffer() 将多重采样的 FBO 内容复制到默认帧缓冲区。但是你先做这个,然后渲染背景然后

您可能认为这行不通,因为它将背景置于其他内容的前面,这使得它... 背景不多。

但这里是混合再次发挥作用的地方。虽然将内容混合在其他内容之上是使用混合的最常见方式,但您也可以使用它来渲染现有内容 后面 的内容。为此,您需要做一些事情:

  • 具有 alpha 平面的帧缓冲区。您如何请求取决于您用于 OpenGL 设置的 window system/toolkit。它通常位于您请求深度缓冲区、模板缓冲区(如果需要)等的同一区域。它通常指定为多个 alpha 平面,您通常将其设置为 8。
  • 正确的混合函数。对于前后混合,您通常使用:

    glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
    

    这会在之前未渲染任何内容(即目标中的 alpha 为 0)的地方添加新渲染,并在已经渲染的地方(即目标 alpha 为 1)保持之前的渲染不变。

    如果您的渲染涉及部分透明度,混合设置可能会有点棘手。

这可能看起来有些复杂,但一旦您了解了混合函数的工作原理,它就会非常直观。而且我认为它总体上是解决您的整体问题的优雅而有效的解决方案。