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个子样本的颜色值。

参考资料

  • 抗锯齿 - LearnOpenGL CN (learnopengl-cn.github.io)