陈栋梁

SMAA算法详解


SMAA(Enhanced Subpixel Morphological Antialiasing,增强型子像素形态学抗锯齿)是后处理抗锯齿技术的一种,它的基本处理流程建立在Jimenez优化改造后的MLAA(形态学抗锯齿)算法之上。原始的MLAA是由英特尔实验室提出的抗锯齿技术,这项技术代表着后处理式抗锯齿蓬勃发展的开端。最初,MLAA是为CPU设计的,Jimenez对其进行改造并移植到GPU上,使其适用于实时渲染。SMAA则是在此基础上进一步发展而来的。

1. MLAA的基本原理

SMAA的核心原理来源于MLAA。MLAA的基本思路是:检测每帧图像上的边缘(通常可对亮度、颜色、深度或者法线进行边缘检测),然后对这些边缘进行模式识别,归类出Z、U、L三种形状,根据形状对边缘进行重新矢量化(re-vectorization),并对边缘上的像素根据覆盖面积计算混合权重,将其与周围的颜色进行混合,从而达到平滑锯齿的目的。
image
以下图为例详细说明。用绿色标记的线条为检测到的Z形边缘,从这些边缘我们可以推断出原始边缘的形状,即图中蓝色线条。这个过程叫重新矢量化。此时我们能够得知边缘附近每个像素被蓝色线条截断了百分之多少。根据此信息将当前像素与邻近像素的颜色混合,便能得到平滑的边缘。
image
像素被蓝线截断面积的百分比(小于50%的部分)叫做权重因子。对于任一个边缘像素,要计算其权重因子,只需要知道d_left、d_right,以及两端交叉边的朝向(即边缘的形状,此处为Z形)即可。其中d_left和d_right为当前像素距离边缘两端的距离。得到权重因子a后,通过如下计算实现边缘平滑:
image
为了节省计算资源,Jimenez为每种形状模式预计算了一张查找表(称作areaTex)以便快速获取权重因子,如下图所示:
image
注意贴图中的每个像素保存了两个数值(分别储存在R和G通道里),这两个数值分别代表当前像素及共享同一边缘的邻接像素的权重因子。

以上是仅针对水平方向锯齿的情况,对于垂直方向的锯齿,处理方式类似。

2. SMAA的处理流程

SMAA的处理流程建立在Jimenez版MLAA的基础上,但是每一个步骤都经过了强化或者是彻底的更新。

Jimenez版MLAA的处理流程如下图所示:
image
分为三个步骤:1、边缘检测;2、计算权重因子;3、混合周围像素。

SMAA在(b)、(c)的基础上加入了针对尖锐几何特征的处理,并加入了对角线模式识别;在(d)中加入了对局部对比度的考虑;在(e)改善了距离搜索算法。

3. SMAA的边缘检测

SMAA的第一步是图像边缘的检测。SMAA在常规边缘检测的基础上加入了对局部对比度的考量。注意下方左图中圆圈内的像素,常规做法中如阈值设置不当,容易检测出多余的边(下方右图红色线段)。
image
SMAA的改良版检测方法如下图所示,灰点为当前像素,以检测左边缘为例,C_l必须同时满足:①大于一定阈值;②大于C_l 、C_r 、C_t 、C_b 、C_2l强度最大值的一半这两个条件时,才会被判定为边缘。这种考虑了局部对比度的检测可以有效避免误检。
image
对于上边缘的检测同理。这里只需要对像素的左边缘及上边缘进行检测即可,而不需要考虑下边缘及右边缘。

将边缘信息绘制到纹理时(称这张纹理为edgesTex),令存在左边缘的像素的r值=1,令存在上边缘的像素的g值=1,其余情况均输出0。前面流程图中的(d)图,就是一张edgesTex。

4. SMAA的形状检测及权重计算

边缘形状的检测及混合权重的计算是在上一步中得到的edgesTex上进行的。

通常锯齿只有一个像素的高度,因此可以分成水平方向的锯齿及垂直方向的锯齿两种情况分别处理。
image
这里先以水平方向的情况为例进行说明。

4.1 距离搜索

首先我们要检测出当前像素到所在边缘左右两端的距离,以及该边缘的形状,才能得到此像素及其邻接像素的权重因子。

Jimenez版MLAA中运用了硬件的线性插值机制来加速距离的搜索。下图为向左搜索的例子,其中★为当前像素,◆为采样位置。通过将采样位置偏移0.5个像素,可以使搜索时一次行进两个单位。当采样到的g值小于1时(等于0.5或者0),我们便知道已到达了左边界。
image
但这样的搜索方式存在一个问题:会错过途中出现的交叉边。如下图:
image
SMAA对此进行改进,利用硬件的双线性插值机制,通过一次采样得知4条边的情况。在水平向左搜索的例子中,SMAA先对当前像素偏移(-0.25, -0.125)个单位,再向左以每步2个单位的速度搜索。当采样到的g值小于0.8,或者r值大于0时,即可知已抵达或越过边界。如果已越过边界,便需要将采样位置往回退,以得到准确的边界点坐标。回退距离是与前面采样到的rg值的组合一一对应的。因此SMAA提供了一张叫做searchTex的查找表(如下图),以快速获取回退距离。
image
对于向右、上、下方向搜索的情况,则需要先分别偏移(1.25, -0.125)、(-0.125, -0.25)、(-0.125, 1.25)个单位,再向各自方向行进。

这样,便得到了端点的坐标以及到两端的准确距离d_left和d_right。

4.2 形状检测

得知端点坐标之后,需要判断交叉边的朝向,以确定边缘形状。利用硬件线性插值机制,只需一次采样即可获取交叉边的信息。以右端点为例,如下图所示:
image
采样点向上偏移了0.25个单位,采样得到的r值存在4种可能(0, 0.25, 0.75, 1.0),分别代表了4种形状。我们将左右端采样到的r值分别记作e_1和e_2。

4.3 权重因子的获取

这一步延续了Jimenez版MLAA的做法,有了d_left、d_right、e_1和e_2信息,便可以直接从查找表中获取权重,省下了像素截断面积的计算。
image
整张贴图被分成了5x5共25个小格,除去中间的一行和一列,每一格对应一种形状。第一行对应(e_1, e_2)的值分别为(0, 0),(0.25, 0),(0.5, 0),(0.75, 0),(1, 0),第一列则对应(0, 0),(0, 0.25),(0, 0.5)……

贴图中每个像素存储了两个数值(r和g),分别是当前像素及另一边像素(共享边缘的邻接像素)的权重因子,记作a_t和a_b。

MLAA直接将此权重因子用于后续的颜色混合,然而目前的形状检测并不能区分锯齿与真正的转角,这就容易导致尖锐形状被模糊,如下图所示:
image
圆圈中存在真正的转角,但从重矢量化的结果得知该转角将被模糊掉。
image
SMAA在此加入了对真正转角的检测处理。其检测原理基于一个很基本的发现:锯齿通常只有一个像素的高度。因此在检测交叉边的时候,将检测范围扩展到两个像素的宽度,并对权重因子作如下更新即可:
image
image
其中r为圆滑因子,若要完全保留转角,则令r=0,若要把转角当锯齿处理,则令r=1。上图中左上方的转角,由于e_3不为零,因此转角处像素的权重因子将会乘以r,这就实现了尖锐转角的保留。

将获取到的权重因子保存到一张叫blendTex的纹理中,其中rg通道存储水平方向锯齿的权重因子,ba通道存储垂直方向锯齿的权重因子。

4.4 对角模式的检测与权重计算

此前,大部分后处理抗锯齿技术只考虑水平方向及垂直方向的锯齿,而忽视了对角方向的处理。对于接近45°的斜线,容易出现下图中矢量化错误的问题:
image
SMAA引入了针对对角模式的处理,使矢量化更加准确:
image
绿色线段代表检测出来的对角线,蓝色线段代表修正后的矢量化结果。基本处理思路与水平垂直方向的情况类似,分为以下三个步骤:

  1. 查找当前像素到两端的距离d_l和d_r。
  2. 提取交叉边信息e_1和e_2。
  3. 综合(d_l, d_r, e_1, e_2)信息从预计算的贴图中获取权重信息,如下图所示:
    image
    对角线搜索不能像水平垂直搜索那样一步两个单位地行进,只能在对角方向上一步一个单位地行进。另外,45°方向及-45°方向的搜索是需要分别处理的,因为45°方向的边缘像素上r和g同时=1(像素呈黄色),而-45°方向的边缘像素则是左绿右红的状态。如下图所示。
    image
    45°的情况,采样点可直接设在像素中心,而-45°的情况则需要将采样点往x方向偏移+0.25个单位,以一次获取两个像素的情况。

另外,在具体实现时,对角模式的检测是执行在水平与垂直检测之前的,若对角检测获取到了大于0的权重因子,则跳过后续的水平与垂直的检测。获取到的权重保存在blendTex的rg通道里。前面流程图中的(e)图就是一张blendTex。

至此便完成了第二个pass的渲染。由于整个pass是仅对边缘像素起作用的,因此可以用stencil buffer将边缘像素标记出来,以减少不必要的计算。

5. 颜色的混合

第二个pass所得到的blendTex作为该pass的输入。blendTex记录了每一个像素的权重因子,根据权重信息将画面的每个像素与其相邻像素混合,便能达到抗锯齿的目的。

首先需要获取当前像素与上下左右4个邻接像素的混合权重因子。(此处需要注意blendTex对权重的记录方式:blendTex中每个边缘像素记录的不仅有当前像素的权重,还有对向像素(共享边缘的邻接像素)的权重)。混合时,要么在水平方向进行混合,要么在垂直方向混合。假设float4 a的xyzw四个分量分别记录了当前像素与右、上、左、下4个方向的混合权重因子,则判断max(a.x, a.z)与max(a.y, a.w)的大小关系来决定混合的方向。以水平方向为例,分别将向左及向右混合得到的颜色记作C1和C2,那么该像素最终的颜色=a.zC1+a.xC2。

像素颜色的混合也可以利用硬件线性插值机制实现,如下图所示:
image
至此,我们便成功地把图像上的锯齿平滑掉了。

6. 亚像素渲染

基础的SMAA算法是以像素为单位进行处理的,因此不能捕捉到亚像素级别的细节。比如下面这张从《刺客信条》中截取的游戏画面(使用了SMAA),背景处的长线仍存在明显锯齿。
image
原作者提出将基础SMAA算法结合其他AA处理以达到更好的亚像素渲染效果,并总结出以下几种模式:

SMAA 1x:包含了上述章节所述特性的抗锯齿算法。
SMAA S2x:SMAA 1x与空间多重采样(MSAA 2x)相结合。
SMAA T2x:SMAA 1x与temporal AA相结合。
SMAA 4x:SMAA 1x与空间多重采样以及temporal AA同时结合。

其中SMAA T2x的基本思路是:结合速度场信息,将当前帧(SMAA 1x处理后)的像素与上一帧的对应像素进行混合:

s_t=αxt+(1-α)s(t-1)

其中混合的权重α是取决于像素在帧间的对应程度的,与当前帧及上一帧的速度相关。具体计算在此暂不详述。

在渲染不同帧时,需要对摄像机在视平面上作不同的少量偏移,以捕捉亚像素级别的细节。SMAA T2x的做法是,对单数帧偏移(0.25, -0.25)个像素单位,对双数帧偏移(-0.25, 0.25)个单位。对于不同的偏移值,像素的权重因子显然也是不一样的,如下图所示:
image
对于橙色采样点,权重因子比原来更大,而对于紫色采样点则相反。SMAA为所有用到的偏移值分别预计算了一份权重查找表,并全部整合到areaTex里:
image
加入了temporal AA后,亚像素级别的细节得以更好地重建:
image
SMAA S2x的做法也类似,只不过不是在时间上混合,而是在空间上混合。SMAA S2x为同一个像素计算不同偏移值((0.25, -0.25)和(-0.25, 0.25))下的权重因子,将由两个权重因子计算得到的两个颜色的均值作为最终的颜色。

SMAA 4x则是在SMAA S2x的基础上加上temporal AA,其中空间及时间上的偏移值更改如下:
image

PS:

① 本文为SMAA算法的详细科普;
② 如果有疑问或者错误不足之处,请和TA组联系!欢迎交流,我们会及时补充纠正!