如何开启抗锯齿
原理见末尾链接
glfwWindowHint(GLFW_SAMPLES, 4);
glEnable(GL_MULTISAMPLE);// 一般默认是打开状态
离屏MSAA
由于GLFW负责了创建多重采样缓冲,启用MSAA非常简单。然而,如果我们想要使用我们自己的帧缓冲来进行离屏渲染,那么我们就必须要自己动手生成多重采样缓冲了。
有2种方式生成多重采样缓冲,将其作为帧缓冲的附件:纹理附件和渲染缓冲附件,这和在帧缓冲教程中所讨论的普通附件很相似。
多重采样纹理附件
为了创建一个支持储存多个采样点的纹理,我们使用glTexImage2DMultisample来替代glTexImage2D,它的纹理目标是GL_TEXTURE_2D_MULTISAPLE。
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
它的第二个参数设置的是纹理所拥有的样本个数。如果最后一个参数为GL_TRUE,图像将会对每个纹素使用相同的样本位置以及相同数量的子采样点个数。我们使用glFramebufferTexture2D将多重采样纹理附加到帧缓冲上,但这里纹理类型使用的是GL_TEXTURE_2D_MULTISAMPLE。
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
当前绑定的帧缓冲现在就有了一个纹理图像形式的多重采样颜色缓冲。
多重采样渲染缓冲对象
和纹理类似,创建一个多重采样渲染缓冲对象并不难。我们所要做的只是在指定(当前绑定的)渲染缓冲的内存存储时,将glRenderbufferStorage的调用改为glRenderbufferStorageMultisample就可以了。
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);
渲染到多重采样帧缓冲
渲染到多重采样帧缓冲对象的过程都是自动的。只要我们在帧缓冲绑定时绘制任何东西,光栅器就会负责所有的多重采样运算。我们最终会得到一个多重采样颜色缓冲以及/或深度和模板缓冲。因为多重采样缓冲有一点特别,我们不能直接将它们的缓冲图像用于其他运算,比如在着色器中对它们进行采样。
一个多重采样的图像包含比普通图像更多的信息,我们所要做的是缩小或者还原(Resolve)图像。多重采样帧缓冲的还原通常是通过glBlitFramebuffer来完成,它能够将一个帧缓冲中的某个区域复制到另一个帧缓冲中,并且将多重采样缓冲还原。
glBlitFramebuffer会将一个用4个屏幕空间坐标所定义的源区域复制到一个同样用4个屏幕空间坐标所定义的目标区域中。你可能记得在帧缓冲教程中,当我们绑定到GL_FRAMEBUFFER时,我们是同时绑定了读取和绘制的帧缓冲目标。我们也可以将帧缓冲分开绑定至GL_READ_FRAMEBUFFER与GL_DRAW_FRAMEBUFFER。glBlitFramebuffer函数会根据这两个目标,决定哪个是源帧缓冲,哪个是目标帧缓冲。接下来,我们可以将图像位块传送(Blit)到默认的帧缓冲中,将多重采样的帧缓冲传送到屏幕上。
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);
但如果我们想要使用多重采样帧缓冲的纹理输出来做像是后期处理这样的事情呢?我们不能直接在片段着色器中使用多重采样的纹理。但我们能做的是将多重采样缓冲位块传送到一个没有使用多重采样纹理附件的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();
...
}
你可以看到,如果将多重采样与离屏渲染结合起来,我们需要自己负责一些额外的细节。但所有的这些细节都是值得额外的努力的,因为多重采样能够显著提升场景的视觉质量。当然,要注意,如果使用的采样点非常多,启用多重采样会显著降低程序的性能。在本节写作时,通常采用的是4采样点的MSAA。
自定义抗锯齿算法
将一个多重采样的纹理图像不进行还原直接传入着色器也是可行的。GLSL提供了这样的选项,让我们能够对纹理图像的每个子样本进行采样,所以我们可以创建我们自己的抗锯齿算法。在大型的图形应用中通常都会这么做。
要想获取每个子样本的颜色值,你需要将纹理uniform采样器设置为sampler2DMS,而不是平常使用的sampler2D:
uniform sampler2DMS screenTextureMS;
使用texelFetch函数就能够获取每个子样本的颜色值了:
vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3); // 第4个子样本
https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/11%20Anti%20Aliasing/