LearnOpenGL——抗锯齿
关于抗锯齿技术的原理可以参考另外一篇笔记 Games101学习笔记
一、OpenGL中的MSAA
因为MSAA需要我们在买个采样点都存储颜色值,所以在OpenGL中我们需要使用一个能在每个像素中存储大于1个颜色值的颜色缓冲——多重采样缓冲(Mutisampler Buffer)
可以在创建窗口之前使用glfwWindowHint(GLFW_SAMPLES,4)
来使用一个每个像素包含4个采样点的多重采样缓冲,然后再使用glEnable(GL_MULTISAMPLE)
命令来开启多重采样。具体实现再OpenGL驱动的光栅器中已经实现。
二、离屏MSAA
如果我们想使用自己的帧缓冲来进行离屏渲染,那么我们就得自己动手生成多重采样缓冲了。
有两种方式可以创建多重采样缓冲,将其作为帧缓冲的附件:纹理附件和渲染缓冲附件,这和在帧缓冲笔记中所讨论的附件很相似。
1.多重采样纹理附件
使用glTexImage2DMultisample代替glTexImage2D来创建纹理,它的纹理目标为GL_TEXTURE_2D_MUTISAMPLE
- 第2个参数为纹理的样本个数
- 最后一个参数若为GL_TRUE,那么就会对每个纹素使用相同的样本位置和相同数量的子采样点个数
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,samples,GL_RGB,width,height,GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE,0);
使用glFramebufferTexture2D将多重采样纹理附加到帧缓冲上
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
当前绑定的帧缓冲现在就有了一个纹理图像形式的多重采样颜色缓冲。
2.多重采样渲染缓冲对象
对于帧缓冲部分,可以看之前的笔记加深了解 OpenGL学习笔记
我们所要做的只是在指定(当前绑定的)渲染缓冲的内存存储时,将glRenderbufferStorage的调用改为glRenderbufferStorageMultisample就可以了。
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);
3.渲染到多重采样帧缓冲
渲染到多重采样帧缓冲对象的过程是自动的,我们只要在帧缓冲绑定时绘制任何东西,OpenGL光栅器就会自动执行计算。我们不能直接将它们的缓冲图像用于其他运算,比如在着色器中对它们进行采样。
因为多重采样帧缓冲中,每个采样点都包含颜色等信息,所以我们需要将每4个(根据采样点数量来)采样点颜色合并为1个像素颜色,并且将结果存储到一个不需要多重采样的帧缓冲中。
我们使用glBlitFramebuffer来完成将多重采样帧缓冲复制到另一个帧缓冲中。glBlitFramebuffer会将一个用4个屏幕空间坐标所定义的源区域复制到一个同样用4个屏幕空间坐标所定义的目标区域中。我们在绑定到GL_FRAMEBUFFER时,是同时绑定了读取和绘制的帧缓冲目标,我们也可以分开绑定至GL_READ_FRAMEBUFFER与GL_DRAW_FRAMEBUFFER。glBlitFramebuffer函数会根据这两个目标,决定哪个是源帧缓冲,哪个是目标帧缓冲。
glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
4.用作后期处理
我们不能直接在片段着色器中使用多重采样的纹理。我们可以将多重采样帧缓冲位块传送到一个没有使用多重采样纹理附件的FBO中,然后用这个普通的颜色附件来做后期处理。这也意味着我们需要生成一个新的FBO,作为中介帧缓冲对象,将多重采样缓冲还原为一个能在着色器中使用的普通2D纹理。这个过程的伪代码是这样的:
unsigned int msFBO = CreateFBOWithMultiSampledAttachments();
// 使用普通的纹理颜色附件创建一个新的FBO
...
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);
...
while(!glfwWindowShouldClose(window))
{
...
glBindFramebuffer(msFBO);
ClearFrameBuffer();
DrawScene();
// 将多重采样缓冲还原到中介FBO上
glBindFramebuffer(GL_READ_FRAMEBUFFER, msFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// 现在场景是一个2D纹理缓冲,可以将这个图像用来后期处理
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ClearFramebuffer();
glBindTexture(GL_TEXTURE_2D, screenTexture);
DrawPostProcessingQuad();
...
}
但是要注意,因为现在的帧缓冲并不是多重采样纹理,后处理可能会重新造成锯齿