使用具有多个颜色纹理附件的 FrameBufferObject

Using a FrameBufferObject with several Color Texture attachments

我正在我的程序中实现高斯模糊效果。为了完成这项工作,我需要在特定纹理(我们称之为 tex_1)中渲染第一个模糊信息(Y 轴上的那个),并使用包含在 tex_1 中的相同信息作为输入信息第二个渲染通道(针对 X 轴)填充包含最终高斯模糊结果的其他纹理(我们称之为 tex_2)。

一个好的做法应该是创建 2 个帧缓冲区 (FBO),每个帧缓冲区都附加一个纹理,并将两者链接到 GL_COLOR_ATTACHMENT0(例如)。但我只想知道一件事:

是否可以使用相同的 FBO 填充这 2 个纹理?

所以我必须启用 GL_COLOR_ATTACHMENT0 和 GL_COLOR_ATTACHMENT1 并将所需的纹理绑定到正确的渲染通道,如下所示:

伪代码:

FrameBuffer->Bind()
{
     FrameBuffer->GetTexture(GL_COLOR_ATTACHMENT0)->Bind(); //tex_1
     {
          //BIND external texture to blur
          //DRAW code (Y axis blur pass) here...
            //-> Write the result in texture COLOR_ATTACHEMENT0 (tex_1)
     }
     FrameBuffer->GetTexture(GL_COLOR_ATTACHMENT1)->Bind(); //tex_2
     {
          //BIND here first texture (tex_1) filled above in the first render pass
          //Draw code (X axis blur pass) here...
            //-> Use this texture in FS to compute the final result
            //within COLOR_ATTACHEMENT1 (tex_2) -> The final result
     }
}
FrameBuffer->Unbind()

但在我看来有一个问题,因为我需要为每个渲染通道绑定一个外部纹理作为片段着色器的输入。因此,纹理的第一个绑定(color_attachment)丢失了!

那么是否存在使用一个 FBO 来解决我的问题的方法,还是我需要使用 2 个单独的 FBO?

Consequently, the first binding of the texture (the color_attachment) is lost!

不,不是。也许您的帧缓冲区 class 就是这样工作的,但是那样的话,这将是一个非常糟糕的抽象。 GL 不会仅仅因为您将该纹理绑定到某个纹理单元就从 FBO 中分离纹理。如果您创建一个 反馈循环 (渲染到您正在读取的纹理),您可能会得到一些未定义的结果。

编辑

然而,正如@Reto Koradi 在他的出色回答(以及他对此的评论)中指出的那样,您不能简单地渲染为未扩展的单一颜色附件 GLES1/2,并且需要一些技巧在 GLES3 中。因此,我在这里指出的事实仍然是正确的,但对您要实现的最终目标没有真正帮助。

我至少可以想到 3 个不同的选项来执行此操作。第三个在 OpenGL ES 中实际上工作,但我还是会解释它,因为你可能会想尝试其他方式,它在桌面 OpenGL 中受支持。

我也将使用伪代码来减少打字并提高可读性。

2 个 FBO,每个 1 个附件

这是最直接的方法。您为每个纹理使用单独的 FBO。在安装过程中,您将拥有:

attach(fbo1, ATTACHMENT0, tex1)
attach(fbo2, ATTACHMENT0, tex2)

然后进行渲染:

bindFbo(fbo1)
render pass 1
bindFbo(fbo2)
bindTexture(tex1)
render pass 2

1 个 FBO,1 个附件

在这种方法中,您使用一个 FBO,并每次附加要渲染的纹理。在安装过程中,您只创建了 FBO,还没有附加任何东西。

然后进行渲染:

bindFbo(fbo1)
attach(fbo1, ATTACHMENT0, tex1)
render pass 1
attach(fbo1, ATTACHMENT0, tex2)
bindTexture(tex1)
render pass 2

1 个 FBO,2 个附件

这似乎是你的想法。您有一个 FBO,并将两个纹理附加到此 FBO 的不同附加点。设置期间:

attach(fbo1, ATTACHMENT0, tex1)
attach(fbo1, ATTACHMENT1, tex2)

然后进行渲染:

bindFbo(fbo1)
drawBuffer(ATTACHMENT0)
render pass 1
drawBuffer(ATTACHMENT1)
bindTexture(tex1)
render pass 2

这在传递 2 中呈现为 tex2,因为它附加到 ATTACHMENT1,我们将绘制缓冲区设置为 ATTACHMENT1

主要警告是此 不适用于 OpenGL ES。在 ES 2.0 中(不使用扩展)它是一个非启动器,因为它只支持单一颜色缓冲区。

在 ES 3.0/3.1 中,有一个更微妙的限制:它们没有来自完整 OpenGL 的 glDrawBuffer() 调用,只有 glDrawBuffers()。您要尝试的呼叫是:

GLenum bufs[1] = {GL_COLOR_ATTACHMENT1};
glDrawBuffers(bufs, 1);

这在完整的 OpenGL 中完全有效,但在 ES 3.0/3.1 中会产生错误,因为它违反了规范中的以下约束:

If the GL is bound to a draw framebuffer object, the ith buffer listed in bufs must be COLOR_ATTACHMENTi or NONE.

换句话说,渲染到 GL_COLOR_ATTACHMENT1 的唯一方法是至少有两个绘制缓冲区。以下调用有效:

GLenum bufs[2] = {GL_NONE, GL_COLOR_ATTACHMENT1};
glDrawBuffers(bufs, 2);

但要使其真正起作用,您需要一个片段着色器来产生两个输出,其中第一个将不会被使用。到目前为止,您希望同意这种方法对 OpenGL ES 没有吸引力。

结论

对于 OpenGL ES,上面的前两种方法都可以使用,而且都非常好用。我认为没有非常充分的理由选择其中之一。不过,我会推荐第一种方法。

您可能认为只使用一个 FBO 会节省资源。但请记住,FBO 是仅包含状态的对象,因此它们使用的内存非常少。创建一个额外的 FBO 是微不足道的。

大多数人可能更喜欢第一种方法。想法是您可以在设置期间配置两个 FBO,然后只需要 glBindFramebuffer() 调用即可在它们之间切换。绑定不同的对象通常被认为比修改现有对象更便宜,而第二种方法需要这样做。