查看: 3240|回复: 1

抗锯齿Anti-Aliasing技术综述

[复制链接]

665

主题

1234

帖子

6568

积分

xdtech

Rank: 5Rank: 5

积分
6568
发表于 2020-4-14 14:46:32 | 显示全部楼层 |阅读模式
导言渲染中,我们输出的结果是一张最终显示到屏幕上的一张图片,其最小的基本单元是像素。像素实际上就是一个个细小的色块,当图片分辨率足够高,色块足够细小时,相邻色块之间的颜色差异肉眼很难辨别,展现在观众眼前的就是一幅细腻顺滑的画面,而当图片分辨率较低时,为了填充满同样大小的屏幕,色块的尺寸就会变大,此时色块之间颜色的区别就很难被忽视,尤其是当色块之间的颜色差别过大时,整个画面就会呈现马赛克式的锯齿状,如图1下面一列所示。锯齿用英文来表述的话,一般称作Aliasing或者Jaggies。 渲染输出画面的锯齿通常来源于多个方面:物体边缘、阴影以及高光等。





为了使得画面变得更为精细,要么选择提升画面分辨率,降低色块尺寸,减少相邻色块之间的颜色差异;要么选择对色块的颜色进行处理,减少相邻色块之间的颜色差异。这种降低锯齿感的做法,我们称作抗锯齿Anti-Aliasing,简称AA。
采样理论渲染从实质上来说,也可以算是一种采样(Sampling):对三维场景进行采样,输出2D的图像。按照香农采样定律的理论,要想通过对采样后的信号进行重建(Reconstruct)来获得完美的原始信号,就必须要保证采样频率不低于原信号最高频率的两倍。这里就隐含了一个完美重建的一个前提条件,那就是原信号的最高频率必须要要是有限的(band-limited),但实际上,如果用点阵来表示三维场景的话,那么问题边缘,阴影边缘等位置的采样通常就会有突变,导致其最高频率通常都是无限的。因此,如果用点阵来表示三维场景的话,就无可避免的会遇到锯齿问题。
主流的AA方法上面说到,锯齿问题是渲染系统中不可绕过的问题,而为了解决这个问题,也涌现了一批优秀的解决方案,这些方案各有考虑,各有所长,下面对其中的一些主流的解决方案及其特点做一个简单的总结与归纳。
Full-Screen AA(FSAA) or Super Sampling AA(SSAA)前面说到,对付锯齿的方法主要有两类,其中一类是提高图像分辨率,降低色块的尺寸,从而减小色块之间的差异。这是最直观的理解,而从渲染原理的角度来考虑,则又有不同的解释。





如图2所示,对于屏幕中的每个像素而言,其最终显示的颜色是取决于覆盖于其上的三角面片对应位置的采样点的颜色。不可忽略的是,像素是有一定的宽度尺寸的,覆盖其上的三角面片可能不一定能将之完全覆盖,如果只覆盖住了一部分,就需要判定,是否应该用此面片上的颜色对像素颜色进行改写。那么,最简单粗暴的判定规则,就是根据像素中心点是否与三角面片有交集。所以上图左侧的图中,由于中心点未被覆盖,所以像素最终的输出颜色为白色。但实际上,此三角形也覆盖了此像素接近一半面积的区域,从主观的意识上来看,为了降低相邻色块之间的差异,取红白两色的中间值是比较恰当的。那么要怎样达成这个目标呢?
最直观的方法,就是将像素进一步细分成不同的小块,对每个小块进行上述的判定,像素最终的颜色,则由这些小块的颜色加权计算而来,如图2右图所示,将像素上的采样点一分为四,最终有两个采样点落在三角面片的覆盖范围上,如果权重相同的话,就实现了刚才所说的平均求值结果。将这个算法做一个通用处理,其计算公式大概如公式1所示:





其中,表示x,y处的像素颜色值,i表示采样点序号,表示第i个采样点的颜色值,表示第i个采样点的权重,一般情况下都是平均采样,所以各个采样顶点的权重值都是,n是单个像素中的采样点的数目,当n=1的时候,这种情况就退化为最原始的只在像素中心位置的唯一采样点的情况。
一般来说,某个pixel上的各个采样点的位置都是各不相同的,且这些采样点分布的模式也可能会随着像素的不同而变化。这种方法由于在每个像素上的采样点数目多于一个而被称为Supersampling/Oversampling Antialiasing(SSAA)或者也有人称之为Full-screen Antialiasing(FSAA)。
这个方法简单直观,行之有效,不过其缺点就是:由于单像素上的采样点数目多了,渲染pixel/fragment shader的时候的消耗就会成倍增加,通常SSAA在每个像素上的采样点数目为4,这就导致ps环节的渲染消耗是正常ps环节的4倍,即使有些项目中使用2x1或者1x2的方式进行SSAA,也有两倍的消耗。
除了这种需要一张更高分辨率的texture buffer的方法之外,还有另外一种使用与输出图像相同分辨率的Accumulation Buffer的AA方法,其基本原理很相似:都是将多个采样点的颜色值加起来之后除以n。不同之处在于,SSAA是使用一次pass,渲染出多个sample的颜色值;而Accumulation Buffer的版本则是使用多次pass,每次pass对view做一次x-或者y-轴上的一定程度的偏移(通常是半个像素单位),之后将结果输出到Accumulation Buffer中相加起来,为了避免Accumulation Buffer溢出,通常其bit数要比最终输出到屏幕上的buffer的bit数多一些。不过这种方法因为需要进行多次绘制,而且也需要进行Buffer之间的数据拷贝,所以消耗也非常的高。
MSAA相对于SSAA的通俗易懂简单直观,MSAA(Multisampling Antialiasing)算法就要稍微复杂一点点,不过其消耗也更容易令人接受,这也是为什么MSAA可以算得上是一种标杆式的AA方法的原因。
SSAA的主要缺陷在于采样数目过多导致同一个像素的ps执行次数增多,进一步导致消耗过高,那么最直接的思路就是如何在降低采样数的前提下保证图像的质量。MSAA的方法就是在这种思想的指导下提出的。
SSAA有两个要素(假设我们需要一个2x2倍分辨率的采样buffer):
  • 2x2倍于原输出buffer的ps输出buffer
  • 每个sample一次的ps计算,即2x2倍于原ps计算量
MSAA保留了其第一个要素,而对其第二个要素进行了改造,使得每个像素只执行一次ps计算,在这种情况下,一次ps计算只有一个数值,而我们有4个sample,参考图\ref{ssaa}右边的小图,我们可以看到,对于每个三角形面片而言,可以认为其覆盖在某个像素上的任意点上的颜色值是近似相同的(实际上可能会有轻微差别,通常情况下像素尺寸比较小,对于肉眼来说,这些差别可以忽略不计),那么对于这个红色三角面片覆盖的两个sample来说,其计算的结果可以认为是相同的,没有必要进行两次,只需要计算一次,之后将结果赋值到两个sample对应的buffer存储空间即可。为了使得这一次计算的结果更精确,一般会选择这些被三角面片覆盖的sample点的中心位置作为最终的采样点位置,对于图2右图中的情况来说,就是那两个采样点连线的中点的位置,这种做法叫做Centroid Sampling或者Centroid Interplotation,这个过程通常可以由GPU自动完成(如果GPU支持的话)。如果三角面片的完全覆盖了某个像素的所有采样点,那么就直接取像素的中心作为采样点进行计算。
这个过程大概可以用如Algorithm1的伪代码来描述:










如图3所示,对于中图中的像素而言,其四个采样点分别被两个三角面片覆盖,0,2,3号采样点被粉色三角面片覆盖,对此进行Centroid Sampling,其真实的采样点计算位置跟像素的中心正好重合,而1号采样点的真实采样点就是其自身所在的位置。
从刚才的伪代码中可以看到,对于处于物体边缘处的采样点群,执行的Centroid Sampling,而对于采样点被全部覆盖的情况则只对像素中心位置的采样点进行fs计算,而通常判断采样点群是否处于物体边缘的判定规则中,最常用的就是比较采样点群的中心位置是否与像素中心重合,如果重合则认为是处于边缘,而这种判定规则对于上图中的示例而言是不存在的,这是这种判定规则的一点瑕疵,在使用中需要注意。
刚才说到MSAA相对于SSAA,只改进了其FS计算频次过高的问题,对于SSAA所需的额外的存储空间过大的问题则未做处理,AMD在MSAA的基础上提出了一种叫做EQAA(Enhanced Quality AA)的改进方法,如图3所示,当一个像素中存在多个sample的数据一样的情况下,就会有冗余,而实际情况中,大多数像素中的sample都是存在这种情况的,因而导致了比较高的浪费。
为了减少浪费,EQAA决定将数据存储到一个Buffer Table中,而在原来MSAA存储数据的位置,只存储对应的数据在Buffer Table中的序号,从而用LUT的方式来降低内存的消耗。NVIDIA早于EQAA提出的CSAA(Coverage Sampling AA)也是采用的相同的思想实现的。
当所有三角面片都绘制完成后,MSAA就进入了与FSAA相同的环节,将所有的sample的数值整合起来求取平均值,并将平均值作为最终结果输出到屏幕上。这一步被称为resolve。不过需要注意的是,如果同时开启了MSAA与HDR,那么在MSAA Resolve 之后再进行HDR的Tone Mapping处理的话,可能会导致一些问题,所以建议尽量在Resolve之前完成Tone Mapping。
默认情况下,MSAA的Resolve操作都是使用最常用的box filter来对像素中的所有fragment进行平均处理,而在07年,ATI通过将box filter替换成tent filter来获取相邻像素的采样信息,从而得到更为平滑的AA效果,这种方法被称为CFAA(Custom Filter AA),而这种方法也在那个时候起就被集成到了AMD的EQAA方法中。
实际上,在现代GPU的PS或者Compute Shader中,都是可以直接操控MSAA的整个处理过程的,在这种情况下,可以使用任意自己觉得可能不错的filter来实现图像的AA重建。通常来说,如果filter的覆盖范围过广,采样面积过大,就会导致高频信息过滤得更干净,锯齿感就越轻,但相应的,细节丢失也就更严重,且filtering的过程也更久。有人研究发现,使用Cubic SmoothStep或者横跨两到三个像素的B-Spline Filter 得到的效果是最好的。
TAA前面的AA技术都是在同一帧中,对像素上不同位置的采样点进行考虑,可以认为是在空间上做文章。而从另一个角度,实时渲染大多不会只渲染一帧,在一秒钟会进行多帧的连续渲染,前后两帧之间的信息是存在极大重复与关联的,是否可以考虑从之前多帧的渲染结果中提取相关信息来对当前帧的渲染结果做优化呢?这是一种基于时间维度的考虑,根据这种思想,诞生了Temporal AA,也就是常说的TAA,NVIDIA的TXAA以及之后的MultiFrame AA(MFAA)都可以归入这个范畴。
先不考虑时间维度,还是用空间维度来举例,之前的SSAA以及MSAA,都是通过将单个像素上不同采样点之间的渲染结果合并起来,作为最终的输出结果。那么从时间维度上来考虑,实施AA算法,应该也是通过将单个像素上的多个不同的采样点位置的颜色输出合并起来,只是不同的是,这些采样点分布在不同的帧,这就是TAA的基本思想。
一般来说,TAA的数据来源是来自不同帧的最终的输出图像,而这些图像可能是直接按照单像素单采样点渲染的最常规的渲染结果,也可能是来自于MSAA或者其他AA方法处理后的输出结果,通过对这些不同帧(通常是2~4帧)的输出结果做一个加权平均之后,就得到了输出到最终的屏幕上的图像画面。
刚才说到TAA是对不同帧的输出进行加权平均,这个地方就有两种实现方式:一种是不同帧的数据权重不同,比如在时间上比较靠前的帧的权重比靠后的帧的权重低(按照指数比例或者线性比例),另一种则是前后多帧的权重完全相同。
不同权重的TAA方法在相机与场景都固定不动的时候,图像会呈现一种不停闪烁抖动的噪音效果,所以通常用得比较多的是平均加权的TAA方法:比如对前后两帧做平均加权输出,每一帧的采样点位置一般都是不同的,且为了避免出现摩尔纹(moire pattern),对于不同的像素,其同一帧之内采样的pattern可能也是不同的。
使用TAA方法的好处是同一帧内,没有额外的采样消耗,只需要对多帧的结果做一个融合即可得到更好的输出结果。因此,相对于MSAA,得到相同的效果,其时间成本要更小一些。而使用TAA的缺点在于,如果多帧是非平均加权融合(即多帧结果的权重不同)的话,那么对于静态物体而言,就会呈现一种抖动闪烁的瑕疵,而对于移动速度过快的物体而言,就会在其运动轨迹上留下一片残影,而残影过多就会导致画面乱七八糟不清不楚。解决这个问题的方法有两种:一种是只对低速移动的物体使用TAA;而另一种,则是对当前帧的画面与之前帧的画面进行映射与投影校正(reprojection)。而实现reprojection的一个常用方法就是使用速度贴图(velocity buffer),所谓的速度贴图,就是屏幕上各点的移动方向与速度的映射,通过速度贴图,可以根据当前帧的数据快速定位到之前帧对应的像素在贴图上的位置,那些无法建立映射关系的采样点数据会被舍弃。由于速度贴图不需要增加额外的采样消耗,因此这种方法的成本比较低。
TAA+Velocity Buffer的抗锯齿方法因为MSAA与延迟渲染(Deferred Rendering)的不兼容而变得大行其道,而这种方法的效果也随着具体使用情景的不同而有所不同,因此涌现出了一批以此为蓝本创造出来的优化版的TAA方法,比如Wihlidal就提出了一种将TAA,EQAA以及众多的过滤算法结合起来的TAA算法,这种算法可以在保证显示质量的前提下进一步降低ps的执行时间。
MLAA前面探讨的几种AA方法都是不需要任何的附加数据信息的辅助就可以直接实施的,但实际上我们知道,锯齿主要产生在几何形状的边缘或者颜色的交界处,如果能事先知道这些知识,就能够针对性的进行抗锯齿处理。09年Reshetov就根据这种思想提出了一种沿着边界线(edge)进行的AA方法,这种方法被称之为Morphological Antialiasing,缩写为MLAA。这种方法的提出再次激发了人们对于AA方法改进的热情,并且更加的侧重于边界线的定位与重构。
类似MLAA之类的抗锯齿方法,通常都是通过后处理来实现的:将渲染好的结果输入到MLAA中,通过MLAA后处理,输出更进一步的抗锯齿图形图像。这类方法包括了只能处理几何物体边缘锯齿的SRAA(subpixel reconstruction AA)方法,以及在渲染时同时输出用于定位边界线的相关数据的分析类方法GBAA(geometry buffer AA)以及DEAA(distance-to-edge AA)。
MLAA类方法的实施大致可以分成两步:
首先需要根据已有的数据信息(如最简单的就是一张color buffer)获取输出图像上的边界线信息。而单个像素对应的采样点数目越多,其输出的边界数据就越可靠,最终AA得到的效果也就越好,因此,Iourcha尝试用MSAA的采样点数据来获取边界线信息,证实这种方法确实有助于得到更好的AA效果。
之后对边界线上的像素进行blend处理。所谓的blend就是在图像上沿着某个方向将一定范围内的像素数据取出来做加权平均,而选取的blend方向通常都是与边界线的方向保持垂直(如directionally localized AA 即DLAA方法认为这种blend方式得到的效果要更好)
虽然MLAA方法能够增强输出图像的AA效果,但是这类方法也有着自身的几点不足:
  • 边缘检测算法的精度不够,对于一些差异比较小的边界情况,可能无法检测出来
  • 边缘检测算法对于一些变化频率较高的图像,比如一个黑白颜色逐像素交叉的图像,可能无法输出有效的边界信息。这种问题比较常见于文字渲染上。
  • 边缘检测算法通常只能处理一些直线边缘情况,对于一些曲线边缘的检测效果就比较差
  • 部分边缘检测算法的鲁棒性比较差,以至于有时候单个像素的误判就可能会导致极大的误差输出,使得这种算法的表现在帧与帧之间的差异显得极为明显。
  • MLAA算法的时间复杂度与场景有关,对于草地的AA消耗比对于天空的AA消耗要高得多。
不过虽然MLAA有着上述的一些缺陷,但是总的来说,这种方法可以在可以接受的时间消耗以及内存消耗的基础上对AA效果进行增强,因此在很多应用场景都有一席用武之地。此外,只借助于color buffer就能实现的MLAA方法还可以从渲染管线中拆卸下来,做成一个独立的原件,在使用的时候可以非常方便对之进行启用与禁用,还可以集成到GPU的硬件功能中去。
MLAA方法中比较出名的两个方法分别是FXAA(fast approximate AA)以及SMAA(subpixel MLAA)。这两种方法的都是只需要color buffer 作为唯一的输入数据,每帧消耗大概在1~2ms之间,且这两种方法在经过MLAA之后,其输出的图像都还可以使用TAA再一次AA效果进行增强。此外需要注意的是,SMAA 还可以利用MSAA的多个采样点数据对AA效果进行增强。
采样点纹AA方法中的一个对AA质量与性能有着重大影响的因子就是Sampling Pattern(采样点纹),即采样点在像素中的分布方式。研究表明,对人眼感官影响最严重的锯齿现象是由水平或者垂直边缘(即与水平方向呈0度与90度夹角)导致的,其次是45度夹角的边缘。因此,RGSS(Rotated Grid SuperSampling)将四点方格grid做了45度旋转得到的pattern作为采样点纹来提升AA质量。如图4所示。





实际上,RGSS使用的Sampling Pattern是Latin hypercube或者说N-rooks采样的一个特例,N-rooks采样简单说就是在NxN的棋盘格里摆上N个采样点,需要保证每一行每一列有且只有一个采样点。这种采样方式对于水平或者垂直的锯齿问题有非比寻常的减轻效果,不过N-Rooks对于45度夹角的对角线形式的锯齿的处理能力就不咋地了。实际上没有一种固定pattern的采样方式是能解决所有问题的,只知道需要分布比较均匀的,相邻采样点之间的间隔不能太近或者太远,目前能够适应各种情况的比较好的采样pattern都是分层式的,即将Latin Hypercube纹样与其他的采样方式(如抖动jittering,Halton sequences以及泊松圆盘分布等)相结合的方法来实现AA。
Halton Sequence指的是采样点在像素范围内随机分布,却又保持一定的距离避免采样过密导致收益降低,通常来说,一个基础的Halton Sequence要比由GPU硬件实现的MSAA的表现要好。
此外,对于场景内一些呈规则分布的细小物件(如多条平行的细线)而言,如果使用固定pattern的采样方式来进行supersampling的话,很容易就会导致moire纹。解决这种问题的方法有两种:
一种是使用随机采样stochastic sampling,即将全屏像素分成多个group,每个group有自己的一套采样pattern,随机采样的原理是将重复的锯齿替换成随机的噪声点,而相对于锯齿纹理,噪声点更容易为人眼忽视,虽然这种方法可以一定程度上减轻锯齿的表现问题,但是如果随机纹理数不够多的话,就会存在很多像素使用的是相同的pattern,还是不能完全消除锯齿;
另一种方法是对于每个像素都使用不同的采样pattern,或者对于每个像素其每帧使用的采样pattern都是在变化的。基于这种思想提出的interleaved stochastic sampling方法经过证实在场景中,同样的消耗能得到比stochastic sampling更好的表现。部分硬件已经开始支持interleaved stochastic sampling方法,比如ATI的SMOOTHVISION就支持每个像素16个采样点,且支持对屏幕空间中的像素按照子区域进行stochastic sampling,比如说在每一个4x4的像素范围内,使用用户自定义的16个随机采样pattern进行采样,保证每个像素的采样pattern在4x4范围内都是不相同的,且各个像素的采样pattern也是随着时间而变化的。
另外采样pattern还可以通过将采样点位置放置到像素边缘的方式来扩张滤波半径从而得到更好的AA效果,且平均采样点数也要更少,比如NVidia 的Quincunx 方法(参考图4)就是使用这种原理实现的:对于处于中间的采样点,给与1/2的权重,而处于四个顶点上的采样点,则给予1/8的权重,由于四个顶点上的采样点会自动使用双边滤波的方式得到结果,所以当前像素的输出值实际上是对周边像素的值都进行了考虑得到的结果,且由于四个顶点上的采样点是被四个像素共用的,所以平均下来,一个像素的采样点是2 个。基于同样的思想,RGSS也可以进行相应的扩张而得到相应的效果,参考图5,由于RGSS的特点,这样的采样纹理可以在每个像素平均2个采样点的情况下得到接近于传统RGSS的AA效果,在水平方向与垂直方向有更好的AA效果,其最终的效果就如图5中右图所示,这个纹样有个自己的名字,叫做FLIPQUAD,这种方法最开始是在移动显卡上使用的。





不论是Quincunx还是FLIPQUAD方法,都可以用在TAA上。而有人得出结论,在平均两个采样点的TAA方法中,FLIPQUAD是其中效果最好的。



作者:残编断简
链接:https://www.jianshu.com/p/fc459e883a1e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

回复

使用道具 举报

665

主题

1234

帖子

6568

积分

xdtech

Rank: 5Rank: 5

积分
6568
 楼主| 发表于 2020-4-14 14:46:45 | 显示全部楼层
作者:残编断简
链接:https://www.jianshu.com/p/fc459e883a1e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表