抗锯齿方法汇总
锯齿的成因
锯齿的来源是因为场景的定义在三维空间中是连续的,而最终显示的像素则是一个离散的二维数组。所以判断一个点到底没有被某个像素覆盖的时候单纯是一个“有”或者“没有”问题,丢失了连续性的信息,导致锯齿。
SSAA(Super Sampling Anti-Aliasing)
SSAA就是将结果渲染到大分辨率的贴图,再降采样到目标分辨率的方法。拿4xSSAA举例子,假设最终屏幕输出的分辨率是800x600, 4xSSAA就会先渲染到一个分辨率1600x1200的buffer上,然后再直接把这个放大4倍的buffer下采样致800x600。这种做法在数学上是最完美的抗锯齿。但是劣势也很明显,光栅化和着色的计算负荷都比原来多了4倍,render target的大小也涨了4倍。
MSAA(Multi-Sampling Anti-Aliasing)
为了理解什么是多重采样(Multisampling),以及它是如何解决锯齿问题的,我们有必要更加深入地了解OpenGL光栅器的工作方式。
光栅器是位于最终处理过的顶点之后到片段着色器之前所经过的所有的算法与过程的总和。光栅器会将一个图元的所有顶点作为输入,并将它转换为一系列的片段。顶点坐标理论上可以取任意值,但片段不行,因为它们受限于你窗口的分辨率。顶点坐标与片段之间几乎永远也不会有一对一的映射,所以光栅器必须以某种方式来决定每个顶点最终所在的片段/屏幕坐标。
这里我们可以看到一个屏幕像素的网格,每个像素的中心包含有一个采样点(Sample Point),它会被用来决定这个三角形是否遮盖了某个像素。图中红色的采样点被三角形所遮盖,在每一个遮住的像素处都会生成一个片段。虽然三角形边缘的一些部分也遮住了某些屏幕像素,但是这些像素的采样点并没有被三角形内部所遮盖,所以它们不会受到片段着色器的影响。
你现在可能已经清楚走样的原因了。完整渲染后的三角形在屏幕上会是这样的:
而MSAA的做法是改变了采样的方法,在每个像素中,生成多几个采样点,判断是否被三角形覆盖,然后将着色器的输出结果,与原像素位置保存的颜色按覆盖百分比插值,得到最终的结果保存到像素中。如下图所示
FXAA(Fast Approximately Anti-Aliasing)
FXAA 是基于边缘检测的抗锯齿算法,原来亮度信息的变化决定采样的半径并与原像素进行混合。下面以Unity URP提供的实现为例进行说明。
在Common.hlsl中的ApplyFXAA函数就是Unity提供的FXAA的核心实现。该函数还会依赖其他函数,都定义在这个文件中。
算法首先采样周围的颜色值,并对中心位置和周围的颜色进行一个归一化操作。
接着计算这些颜色的强度。在有的资料的实现中,是直接取的绿色分量的颜色值。而在Unity URP提供的实现是和一个常量进行点乘的结果,绿色分量的权值是比较大的。
然后根据计算的强度,计算偏移方向,越靠近边缘处的像素偏移的越大,而不在边缘处的像素,几乎不发生偏移。
最后进行采样,混合,输出结果。相当于对边缘的像素做了一个模糊的操作,而非边缘的像素则保持不变。
综合来看,FXAA是效率比较高的后处理抗锯齿的实现。MSAA的边缘会比较精准,因为算法是计算投影三角形对像素的覆盖程度,边缘不回外溢很多。而FXAA是将边缘像素与周围一定范围内的像素进行混合,控制范围的参数稍微调大一些的时候,混合的范围就会加大,因此边缘会模糊更多一些,在一些不是特别写实的场景,很适用。需要特别写实的场景,MSAA就比较合适。
MLAA(Morphological Anti-Aliasing)
它的基本思路是检测每帧图像的边缘 (通常可对亮度、颜色、深度或者法线进行边缘检测),然后对这些边缘进行模式识别,归类出 Z、U、L 三种形状,根据形状对边缘进行重新矢量化 (re-vectorization),并对边缘上的像素根据覆盖面积计算混合权重,将其与周围的颜色进行混合,从而达到平滑锯齿的目的。
以下图为例详细说明。用绿色标记的线条为检测到的 Z 形边缘,从这些边缘我们可以推断出原始边缘的形状,即图中蓝色线条。这个过程叫重新矢量化。此时我们能够得知边缘附近每个像素被蓝色线条截断了百分之多少。根据此信息将当前像素与邻近像素的颜色混合,便能得到平滑的边缘。
像素被蓝线截断面积的百分比(小于 50%的部分)叫做权重因子。对于任一个边缘像素,要计算其权重因子,只需要知道$d_{left}$、$d_{right}$,以及两端交叉边的朝向(即边缘的形状,此处为 Z 形)即可。其中$d_{left}$和$d_{right}$为当前像素距离边缘两端的距离。得到权重因子$a$后,通过如下计算实现边缘平滑:
为了节省计算资源,Jimenez 为每种形状模式预计算了一张查找表(称作AreaTex)以便快速获取权重因子,如下图所示:
注意贴图中的每个像素保存了两个数值(分别储存在R和G 通道里),这两个数值分别代表当前像素及共享同一边缘的邻接像素的权重因子。
以上是仅针对水平方向锯齿的情况,对于垂直方向的锯齿,处理方式类似。
MLAA 天然的能与延迟着色完美配合,但它只能用来消除几何锯齿,不能消除着色锯齿。
SMAA(Enhanced Subpixel Morphological Antialiasing)
SMAA 是思路是建立在 Jimenez 版 MLAA 的基础之上,但是每个步骤都经过了强化或者彻底的更新。
Jimenez 版 MLAA 的处理流程如下图所示:
分为三个步骤:1、边缘检测;2、计算权重因子;3、混合周围像素。
SMAA 在(b)、(c)的基础上加入了针对尖锐几何特征的处理,并加入了对角线模式识别;在(d)中加入了对局部对比度的考虑;在(e)改善了距离搜索算法。
DLSS(Deep Learning Super Samping)
DLSS(Deep Learning Super Sampling),深度学习超采样,是 NVIDIA 于 2018 年在 GeForce 20 系列的显卡上推出的一种基于深度学习算法进行图像缩放的技术。2020 年 NVIDIA 推出了 DLSS 2.0。
这项技术在 Turing 架构中首次引入,它的工作原理是利用 NVIDIA 神经图形框架 NGX,在超级计算机中以极低的帧率和每像素 64 个样本对数万张高分辨率的精美图像进行离线渲染,训练出一个深度神经网络。基于无数个小时的训练所获得的数据,网络就可以将分辨率较低的图像作为输入,构建高分辨率的精美图像。
训练好网络后,NGX 会通过 Game Ready 驱动程序和 OTA 更新将 AI 模型传递到 GeForce RTX PC 或笔记本电脑。并借助 RTX GPU 中专用于 AI 计算的 Tensor Core,DLSS 网络可以与密集的 3D 游戏同时实时运行。
TAA(Temporal Anti-Aliasing)
TAA 的核心思想是将 SSAA 采样空间不同样本方法改成使用多个历史帧的渲染结果作为样本来达到类似 SSAA 的效果。它的实现需要 N 倍的显存空间,但是不需要额外的着色计算。
实现思路如下:
1、把多次采样的过程分布到每一帧中去,每一帧都平均前面几帧保存下来的数据。从下图可以看到将 4 次采样分布在 4 帧的运算中。
2、每一帧会有一定的偏移,继承了 MSAA 采样。从下图可以看到每一帧的采样点的位置并不一样。
3、用Motion Vector保存每帧移动的偏移。
在实际工程中,需要注意 TAA 带来的一些细节问题;
1、因为灯光改变、遮挡改变、相机或场景运动所带来的历史帧中的样本失效问题。
2、因为样本来自于多个历史帧而产生的画面收敛延迟 (残影)的问题等等。
3、HDR协同工作的问题。
这里有一份Unity版本的实现(跳转链接)
这里有一份更详细的介绍文章(跳转链接),以及Motion Vector的生成(跳转链接)