使用glBlitFramebuffer进行多重采样

KO7*_*O70 2 opengl fbo multisampling antialiasing

这是我第一次尝试使用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);
Run Code Online (Sandbox Code Playgroud)

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

有什么建议?

Ret*_*adi 9

通常,如果要在图像中考虑透明度的同时在现有渲染上渲染新图像/纹理,则混合是最明显的选项.将渲染到多重采样的帧缓冲区视为具有透明度的图像,这正是您所拥有的情况.

在这种情况下,有一些挑战使得混合的使用比平常更困难.首先,glBlitFramebuffer()不适用混合.从规格:

Blit操作绕过片段管道.影响blit的唯一片段操作是像素所有权测试和剪刀测试.

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

Howerver,"绘制纹理四边形"部分变得更加棘手,因为您的内容是多重采样的.我想到了一些选择.

渲染背景到FBO

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

你可能会想:"但我不希望我的背景被反锯齿!" 这不是一个真正的问题.您只需在绘制背景时禁用多重采样:

glDisable(GL_MULTISAMPLE);
Run Code Online (Sandbox Code Playgroud)

我认为应该给你你想要的东西.如果确实如此,它是迄今为止最简单的选择.

多重采样纹理

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

glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8,
                         xsize, ysize, GL_FALSE);
Run Code Online (Sandbox Code Playgroud)

还有其他方面我不能在这里涵盖.如果您想探索此选项,可以阅读规范或其他来源中的所有详细信息.例如,着色器代码中纹理的采样工作方式不同,采样器类型不同,采样功能只允许您一次读取一个采样.

两阶段Blitting

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

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

虽然这看起来有些尴尬,并且需要一个对性能不利的额外副本,但它相当简单.

最后渲染背景

为此,您可以完成您现在正在执行的操作,将多重采样的FBO内容复制到默认的帧缓冲区glBlitFramebuffer().但你这样做第一,并呈现背景之后.

您可能认为这不起作用,因为它将背景放在其他内容的前面,这使得它不是很多背景.

但这里是混合再次发挥作用的地方.虽然在混合其他内容上面的内容是使用混合的最常见的方式,你也可以用它来呈现的东西背后现有内容.要做到这一点,你需要一些东西:

  • 带有alpha平面的帧缓冲.您的请求取决于您用于OpenGL设置的窗口系统/工具包.它通常位于您请求深度缓冲区,模板缓冲区(如果需要)等的同一区域中.它通常被指定为多个alpha平面,通常设置为8.
  • 正确的混合功能.对于前后混合,您通常使用:

    glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
    
    Run Code Online (Sandbox Code Playgroud)

    这会添加以前未呈现任何内容的新渲染(即目标中的alpha为0),并且在已经渲染(即目标alpha为1)的情况下将保持先前渲染不变.

    如果渲染涉及部分透明度,则混合设置会变得有点棘手.

这看起来有点复杂,但是一旦你了解混合功能的工作方式,它就会非常直观.而且我认为它总体上是一个优雅而有效的解决方案,可以解决您的整体问题.