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)