11 - 抗锯齿

如图,在没有开启抗锯齿的情况下,我们渲染出的画面会出现走样现象:

在 OpenGL 中,可以采用内建的 MSAA 技术进行反走样 / 抗锯齿。

MSAA

MSAA 原理

有关多重采样 MSAA 技术的原理,详见 GAMES101 关于反走样的文章。

OpenGL 的 MSAA

要使用 MSAA,得准备一个能在每个像素中存储大于 1 个颜色值的缓冲,它叫做 多重采样缓冲 (Multi-sample Buffer)。对于当前使用的 GLFW,可以用 glfwWindowHint() 来提示它,我们希望使用一个包含 N 个样本的多重采样缓冲:

glfwWindowHint(GLFW_SAMPLES, 4);

这样 GLFW 就会创建每像素 4 个子采样点的深度和样本缓冲,这也意味着所有缓冲的大小都增长了 4 倍。

接下来通过 glEnable() 启动 MSAA 即可:

glEnable(GL_MULTISAMPLE);

可以看到模型边缘确实平滑了很多。

离屏 MSAA

通过 GLFW 来启动 MSAA 很简单,因为它负责创建多重采样缓冲等工作。如果我们想要用自己的帧缓冲来进行离屏渲染,就得自己动手生成多重采样缓冲 FBO:

   
unsigned int framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

然后给它附加多重采样纹理附件,使用 glTexImage2DMultisample() 代替 glTexImage2D(),它的纹理目标是 GL_TEXTURE_2D_MULTISAMPLE;第二个参数是纹理拥有的样本个数;如果最后一个参数是 GL_TRUE,就会对每个纹素使用相同的样本位置和相同的子采样点个数(这里是 4)。

代码如下:

   
unsigned int textureColorBufferMultiSampled; glGenTextures(1, &textureColorBufferMultiSampled); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB, SCR_WIDTH, SCR_HEIGHT, GL_TRUE); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled, 0);

接下来附加多重采样渲染缓冲对象附件,将 glRenderbufferStorage 的调用改为 glRenderbufferStorageMultisample 就可以了:

   
unsigned int rbo; glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT); glBindRenderbuffer(GL_RENDERBUFFER, 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

检查完整性后,这个 MSAA 的 FBO 就准备好了,接下来还要用一个新的 FBO,用于将 MSAA 效果跟后处理(如果有的话)的效果组合到一起,它只有一个纹理附件:

   
// configure second post-processing framebuffer unsigned int intermediateFBO; glGenFramebuffers(1, &intermediateFBO); glBindFramebuffer(GL_FRAMEBUFFER, intermediateFBO); // create a color attachment texture unsigned int screenTexture; glGenTextures(1, &screenTexture); glBindTexture(GL_TEXTURE_2D, screenTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0); // we only need a color buffer

最后我们要通过 glBlitFramebuffer(),将两个 FBO 的效果综合起来,读取 MSAA 的帧缓冲,写入后处理的帧缓冲中:

   
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO); glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);

效果如下:

自定义抗锯齿算法

OpenGL 可以直接将多重采样的纹理图像(未还原)传入着色器,让我们能够对纹理图像的每个子样本进行采样。因此我们也能创建自己的抗锯齿算法,在大型的图形应用(如游戏引擎)中通常都会这么做。

只需将纹理采样器类型设为 sampler2DMS,使用 texelFetch(texture, texcoord, num) 即可获取第 num 个子样本的颜色值。

参考资料