Anti Aliasing 抗锯齿

LearnOpenGL 顺序:Advanced OpenGL → Anti Aliasing · 原教程

这一节在渲染管线中的位置

抗锯齿最核心的问题发生在 Rasterization:连续的三角形最终必须落到离散的像素网格里。普通单采样通常用像素里的一个采样点判断图元是否覆盖该像素,边缘于是只能表现成“这个像素算 / 不算”的阶梯形状。

连续几何边缘
      ↓
Rasterization
      ↓
有限的像素采样点
      ↓
有的像素被覆盖,有的没被覆盖
      ↓
锯齿

MSAA 改的不是几何,而是覆盖率

MSAA(Multisample Anti-Aliasing)把一个像素里的单个采样点变成多个子采样点。以 4x MSAA 为例,一个边缘像素可能只覆盖 4 个子采样点中的 2 个,因此最终 Resolve 后,这个像素不会是纯前景色,也不会是纯背景色,而会得到更接近“覆盖了一半”的结果。

1x sampling               4x MSAA
 
┌─────────┐               ┌─────────┐
│    •    │               │  •   •  │
│   /     │               │   /     │
│  /      │               │  •   •  │
└─────────┘               └─────────┘
 
只问中心点是否在图元内        能估计这个像素被覆盖了多少

这就是为什么 MSAA 主要改善的是几何边缘。它不是简单把 Fragment Shader 跑 4 次;实际实现还涉及 sample coverage、逐样本深度/模板以及颜色样本的存储。

桌面 OpenGL 中,默认窗口的 MSAA 通常从创建窗口时申请:

glfwWindowHint(GLFW_SAMPLES, 4);
...
glEnable(GL_MULTISAMPLE);

离屏 MSAA:为什么还要 Resolve

如果直接渲染到默认 framebuffer,窗口系统可以帮我们处理多重采样。但一旦自己使用 FBO,就要自己准备 multisample attachment。

Scene
  ↓
Multisampled FBO
每个像素保存多个 color/depth samples
  ↓
Resolve / Blit
  ↓
普通单采样 Texture
  ↓
Post Processing / Screen Quad

官方离屏示例使用多重采样颜色附件和深度模板 Renderbuffer,然后通过 glBlitFramebuffer 把 multisampled framebuffer Resolve 到普通 framebuffer。这个步骤不是“再渲染一次场景”,而是把多个 samples 合成为普通单采样图像,后面才能像普通纹理一样继续后处理。

glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
 
glBlitFramebuffer(
    0, 0, width, height,
    0, 0, width, height,
    GL_COLOR_BUFFER_BIT,
    GL_NEAREST
);

Interactive Lab

Lab 使用 WebGL2 真正创建两套离屏目标:左边是单采样 framebuffer,右边是 multisample renderbuffer,再通过 blitFramebuffer Resolve 到普通纹理。把画面放大观察斜边,会比只看概念图更容易理解 MSAA 到底改变了什么。

和 Framebuffer 一节连起来

Framebuffer 一节解决的是:

“这一遍渲染写到哪里?”

Anti Aliasing 在此基础上多问了一层:

“这个 framebuffer 的每个像素只有一个 sample,
还是保存多个 samples?”

所以离屏 MSAA 可以记成:

普通 FBO 存一个像素结果;Multisampled FBO 先存多个 samples,Resolve 后才得到最终单像素结果。

最终数据流:

Geometry
  ↓
Rasterization + Coverage
  ↓
Multisample Color / Depth
  ↓
Resolve
  ↓
Normal Texture / Screen