一、简介
在做OpenGL的渲染图像时,我们经常会遇到模型边缘有锯齿的问题。锯齿边出现的原因是由顶点数据像素化之后成为片段的方式所引起的。
二、抗锯齿技术
在立方体的边缘,我们放大后就会看到走样现象。有很多的技术能够减少走样,产生更平滑的边缘,这些技术叫做抗锯齿技术(Anti-aliasing,也成为反走样技术)。
首先,我们有一个叫做超级采样抗锯齿技术(Super Sample Anti-aliasing, SSAA),它暂时使用一个更高的解析度(以超级采样方式)来渲染场景,当视频输出在帧缓冲中被更新时,解析度便降回原来的普通解析度。这个额外的解析度被用来防止锯齿边。虽然它确实为我们提供了一种解决走样问题的方案,但却由于必须绘制比平时更多的片段而降低了性能。所以这个技术只流行了一段时间。
这个技术的基础上诞生了更为现代的技术,叫做多采样抗锯齿(Multisample Anti-aliasing)或叫MSAA,虽然它借用了SSAA的理念,但却以更加高效的方式实现了它。
OpenGL中的MSAA
如果我们打算在OpenGL中使用MSAA,那么我们必须使用一个可以为每个像素储存一个以上的颜色值的颜色缓冲(因为多采样需要我们为每个采样点储存一个颜色)。我们这就需要一个新的缓冲类型,它可以储存要求数量的多重采样样本,它叫做多样本缓冲(Multisample Buffer)。
多数窗口系统可以为我们提供一个多样本缓冲,以代替默认的颜色缓冲。GLFW同样给了我们这个功能,我们所要作的就是提示GLFW,我们希望使用一个带有N个样本的多样本缓冲,而不是普通的颜色缓冲,这要在创建窗口前调用glfwWindowHint来完成:
glfwWindowHint(GLFW_SAMPLES, 4);
当我们现在调用glfwCreateWindow,用于渲染的窗口就被创建了,这次每个屏幕坐标使用一个包含4个子样本的颜色缓冲。这意味着所有缓冲的大小都增长4倍。
现在我们请求GLFW提供了多样本缓冲,我们还要调用glEnable来开启多采样,参数是 GL_MULTISAMPLE。大多数OpenGL驱动,多采样默认是开启的,所以这个调用有点多余,但通常记得开启它是个好主意。这样所有OpenGL实现的多采样都开启了。
glEnable(GL_MULTISAMPLE);
当默认帧缓冲有了多采样缓冲附件的时候,我们所要做的全部就是调用 glEnable开启多采样。因为实际的多采样算法在OpenGL驱动光栅化里已经实现了,所以我们无需再做什么了。如果我们现在来渲染教程开头的那个绿色立方体,我们会看到边缘变得平滑了:
离屏MSAA
因为GLFW负责创建多采样缓冲,开启MSAA非常简单。如果我们打算使用我们自己的帧缓冲,来进行离屏渲染,那么我们就必须自己生成多采样缓冲了;现在我们需要自己负责创建多采样缓冲。
有两种方式可以创建多采样缓冲,并使其成为帧缓冲的附件:纹理附件和渲染缓冲附件。
多采样纹理附件
为了创建一个支持储存多采样点的纹理,我们使用glTexImage2DMultisample来替代glTexImage2D,它的纹理目标是GL_TEXTURE_2D_MULTISAMPLE:
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,图像上的每一个纹理像素(texel)将会使用相同的样本位置,以及同样的子样本数量。
为将多采样纹理附加到帧缓冲上,我们使用glFramebufferTexture2D,不过这次纹理类型是GL_TEXTURE_2D_MULTISAMPLE:
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
当前绑定的帧缓冲现在有了一个纹理图像形式的多采样颜色缓冲。
多采样渲染缓冲对象
和纹理一样,创建一个多采样渲染缓冲对象(Multisampled Renderbuffer Objects)不难。而且还很简单,因为我们所要做的全部就是当我们指定渲染缓冲的内存的时候将glRenderbuffeStorage改为 glRenderbufferStorageMuiltisample:
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);
有一样东西在这里有变化,就是缓冲目标后面那个额外的参数,我们将其设置为样本数量。
渲染到多采样帧缓冲
渲染到多采样帧缓冲对象是自动的。当我们绘制任何东西时,帧缓冲对象就绑定了,光栅化会对负责所有多采样操作。我们接着得到了一个多采样颜色缓冲,以及深度和模板缓冲。因为多采样缓冲有点特别,我们不能为其他操作直接使用它们的缓冲图像,比如在着色器中进行采样。
一个多采样图像包含了比普通图像更多的信息,所以我们需要做的是压缩或还原图像。还原一个多采样帧缓冲,通常用glBlitFramebuffer来完成,它从一个帧缓冲中复制一个区域粘贴另一个里面,同时也将任何多采样缓冲还原。
glBlitFramebuffer把一个4屏幕坐标源区域传递到一个也是4空间坐标的目标区域。你可能还记得帧缓冲教程中,如果我们绑定到GL_FRAMEBUFFER,我们实际上就同时绑定到了读和写的帧缓冲目标。我们还可以通过GL_READ_FRAMEBUFFER和GL_DRAW_FRAMEBUFFER绑定到各自的目标上。glBlitFramebuffer函数从这两个目标读取,并决定哪一个是源哪一个是目标帧缓冲。接着我们就可以通过把图像位块传送(Blitting)到默认帧缓冲里,将多采样帧缓冲输出传递到实际的屏幕了:
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);
三、总结
本篇博客中,我们主要介绍了关于OpenGL的抗锯齿技术,希望大家能有所收获。