您的位置:首页 > 文旅 > 美景 > 设计素材网站特点_亚马逊雨林有原始人吗_ip或域名查询网_5118大数据平台官网

设计素材网站特点_亚马逊雨林有原始人吗_ip或域名查询网_5118大数据平台官网

2024/10/6 14:24:36 来源:https://blog.csdn.net/qq_53352162/article/details/142217154  浏览:    关键词:设计素材网站特点_亚马逊雨林有原始人吗_ip或域名查询网_5118大数据平台官网
设计素材网站特点_亚马逊雨林有原始人吗_ip或域名查询网_5118大数据平台官网

前言:文中补充的内容很多来自链接里的,建议看看链接的文章。

一、锯齿

(一) 什么是锯齿

在学习渲染的旅途中,你可能会时不时遇到模型边缘有锯齿的情况。这些锯齿边缘(Jagged Edges)的产生和光栅器将顶点数据转化为片段的方式有关。在下面的例子中,你可以看到,我们只是绘制了一个简单的立方体,你就能注意到它存在锯齿边缘了:

可能不是非常明显,但如果你离近仔细观察立方体的边缘,你就应该能够看到锯齿状的图案。如果放大的话,你会看到下面的图案:

这很明显不是我们想要在最终程序中所实现的效果。你能够清楚看见形成边缘的像素。这种现象被称之为走样(Aliasing)。有很多种抗锯齿(Anti-aliasing,也被称为反走样)的技术能够帮助我们缓解这种现象,从而产生更平滑的边缘。

具体到实时渲染领域中,可以将锯齿(走样)分为以下三种:

  • 几何走样(Geometry Aliasing),几何物体的边缘有锯齿,是对几何边缘采样不足导致。
  • 着色走样(Shading Aliasing),渲染方程也是一个连续函数,对某些部分(比如法线,高光等)在空间变化较快(高频部分)采样不足也会造成走样,比较明显的现象就是高光闪烁或高光噪点。
  • 时间走样,主要是对高速运动的物体采样不足导致。比如游戏中播放的动画发生跳变等。

(二) 锯齿如何产生

在计算机图形学中,锯齿(jaggies)的产生是由于采样错误引发的。这种错误通常发生在光栅化过程中,即当三维空间中的连续物体被离散化为由像素表示的图像时。由于显示器上的像素是离散化的点,而几何图形是连续的坐标连接实现的,因此在图形的边缘必然会产生锯齿。特别是在屏幕分辨率较低的情况下,这种离散化更为明显,导致锯齿效果更加显著。锯齿的生成原理可以追溯到采样理论,即当信号变化的速度太快(主要指高频信号变化太快),而采样的速度太慢时,最终结果就会导致采样错误,从而产生锯齿状边缘。

二、抗锯齿方法

(一) 超采样抗锯齿 SSAA

最开始我们有一种叫做超采样抗锯齿(Super Sample Anti-aliasing, SSAA)的技术,它会使用比正常分辨率更高的分辨率(即超采样)来渲染场景,当图像输出在帧缓冲中更新时,分辨率会被下采样(Downsample)至正常的分辨率。这些额外的分辨率会被用来防止锯齿边缘的产生。虽然它确实能够解决走样的问题,但是由于这样比平时要绘制更多的片段,它也会带来很大的性能开销。所以这项技术只拥有了短暂的辉煌。

例如,把原来的每个像素点进行细分,将每个像素点细分成了4个采样点,但是劣势也很明显,光栅化和着色的计算负荷都比原来多了4倍,render target的大小也涨了4倍。

再根据采样点在图形内的数量比例去决定最终的颜色,这样边缘会被模糊,弱化了锯齿的感觉。

(二) 多重采样抗锯齿 MSAA

然而,在SSAA这项技术的基础上也诞生了更为现代的技术,叫做多重采样抗锯齿(Multisample Anti-aliasing, MSAA)。它借鉴了SSAA背后的理念,但却以更加高效的方式实现了抗锯齿。

MSAA(Multi-Sampling AA)

  • 在光栅化阶段判断一个三角形是否被像素覆盖的时候会计算多个覆盖样本(Coverage sample)。
  • 但是在片元着色器阶段计算像素颜色的时候每个像素还是只计算一次

例如下图是4xMSAA,三角形只覆盖了4个coverage sample中的2个0和1。所以这个三角形需要生成一个fragment在片元着色器里着色,只不过生成的fragment还是在像素中央(位置,法线等信息插值到像素中央)然后只运行一次片元着色器,最后得到的结果在resolve阶段会乘以0.5,因为这个三角形只覆盖了一半的sample。

现代所有GPU都在硬件上实现了这个算法,而且在shading的运算量远大于光栅化的今天,这个方法远比SSAA快很多。

SSAA和MSAA区别:

一句话:同样是2x2=4x,SSAA做4次深度测试和4次shading,MSAA做4次深度测试和1次shading。

(三) FXAA 快速近似抗锯齿

参考:

FXAA算法演义 - 知乎 (zhihu.com)

FXAA(Fast Approximate Anti-Aliasing)是由 NVDIA 发明的高效后处理抗锯齿方案,也是目前所有后处理抗锯齿方案里面同等设置下效率最高的抗锯齿算法。

FXAA的思路很简单,图像锯齿通常出现在与背景呈现高对比度的物体边缘(视觉比较明显)。那我们可以通过计算像素和其周围像素的亮度对比,来判定一个像素是不是边缘像素。针对非边缘的像素,我们什么都不做,而对于边缘像素,我们可以让其和周围的像素进行Blend混合,从而起到模糊的效果。

所以FXAA算法的核心思路就两点:

  1. 边缘提取
  2. 边缘混合

1. 初代FXAA

一、边缘检测

FXAA1.0使用的边缘检测算法如下:

  • 读取上下左右4个方向 + 自身的像素亮度,筛选出其中的最大值lumaMax和最小值lumaMin
  • 令对比度lumaContrast = lumaMax - lumaMin,当对比度超过一定阈值时,便认为当前像素为边缘像素

//采集uv的上下左右中共计5个像素的RGB和亮度
FXAACrossData cross = SampleCross(tex,uv,offset);//计算对比度
half lumaMinNS = min(cross.N.a,cross.S.a);
half lumaMinWE = min(cross.W.a,cross.E.a);
half lumaMin = min(cross.M.a,min(lumaMinNS,lumaMinWE));half lumaMaxNS = max(cross.N.a,cross.S.a);
half lumaMaxWE = max(cross.W.a,cross.E.a);
half lumaMax = max(cross.M.a,max(lumaMaxNS,lumaMaxWE));half lumaContrast = lumaMax - lumaMin;

白色为高对比度像素,黑色为低对比度像素,物件的边缘被很好的勾勒了出来。接下来我们给定一个对比度阈值FXAA_ABSOLUTE_LUMA_THRESHOLD,当lumaContrast大于该阈值时,便认为是边缘像素:

#define FXAA_ABSOLUTE_LUMA_THRESHOLD 0.05
bool isEdge = lumaContrast > FXAA_ABSOLUTE_LUMA_THRESHOLD;

然后我们把边缘像素描红返回,效果如下:

可以看到该算法将阴影渐变区域也识别为了边缘。这也是FXAA的为人诟病的缺点之一,即只要是对比度高的像素,它都视作边缘像素进行处理。这可能会使图像丢失一些局部高频信息,使得画面不够锐利为了缓解这个问题,FXAA1.0在阈值判断这里额外加入一个修正参数FXAA_RELATIVE_LUMA_THRESHOLD,其关系如下:

float edgeThreshold = max(FXAA_ABSOLUTE_LUMA_THRESHOLD,lumaMax * FXAA_RELATIVE_LUMA_THRESHOLD);
bool isEdge = lumaContrast > edgeThreshold;

用中文来表示就是

最终阈值 = max(绝对阈值, lumaMax * 相对阈值比例)

这个修正带来以下效果:明亮的地方需要更高的周边对比度才能被判定为边缘

二、边缘混合

对于非边缘像素,我们什么都不做,原样返回颜色即可。这个叫做Early Exit

对于边缘像素,接下来要让其和周边像素进行混合,以起到模糊边缘的效果。这里有两个问题需要解决:

  • 上下左右4个方向,到底与哪个方向混合?
  • 混合的比例是多少?

(一) 混合方向

针对第一个问题,当我们从像素的微观角度去看时,边缘像素只有三种情况:在横边上,在纵边上,在角上。

初始想法是计算上下两个像素的亮度差以及左右两个像素的亮度差,如果前者大,那就是横边,反之则为纵边。伪代码如下:

float lumaGradV = abs(lumaN - lumaS); 
float lumaGradH = abs(lumaE - lumaW);
bool isHorz = lumaGradV > lumaGradH;

但这种方式有个缺陷,即对于单像素的线,无法正确判定其走向。对于1位置的像素,lumaGradV将与lumaGradH相等。

单像素宽度线

为了解决这个问题,改进如下。首先计算像素在S、N、W、E 4个方向的亮度变化梯度:

float lumaGradS = lumaS - lumaM;
float lumaGradN = lumaN - lumaM;
float lumaGradW = lumaW - lumaM;
float lumaGradE = lumaE - lumaM;

然后对垂直和水平方向的梯度分别相加,取绝对值,比较它们的大小,这样就成功的解决单像素线的问题。

float lumaGradV = abs(lumaGradS + lumaGradN);
float lumaGradH = abs(lumaGradW + lumaGradE);
bool isHorz = lumaGradV > lumaGradH;

(二) 法线朝向

成功判定了边缘像素横纵状态后,我们接下来需要计算其法线朝向。对于像素1,其边缘法向应当是朝左。对于像素2,其边缘法线则是朝上。边缘法线即表征了目标混合像素的方向。

边缘法线

这个就简单了,哪个方向的梯度大,法线就朝哪边。于是我们有:

float2 normal = float2(0,0);
if(isHorz)
{normal.y = sign(abs(lumaGradN) - abs(lumaGradS));//sign()取符号,正为1,负为-1,0不变
}
else
{normal.x = sign(abs(lumaGradE) - abs(lumaGradW));
}

(normal + 1) * 0.5作为颜色输出到画面,我们有如下效果:

4种颜色分别代表了边缘法线N、S、E、W4个朝向。

(三) 混合因子

我们期望的混合因子应当符合以下两个要素:

  • 需要在"高对比度的像素之间"构造出渐变。例如lumaN和lumaS对比度很高,那么lumaM就应该将自己修正为接近 (lumaN + lumaS) / 2。
  • 不破坏像素之间正常的渐变关系。(例如软阴影)

于是首先对NSEW4个方向的像素亮度求平均,计算出中间像素的期望亮度:

half lumaL = (lumaN + lumaS + lumaE + lumaW) * 0.25;

然后考察中间像素的实际亮度与期望亮度之差:

half lumaDeltaML = abs(lumaM - lumaL);

这个差值如果是0,说明当前的中间像素亮度已经完美符合,不用任何修改。否则,令

float blend = lumaDeltaML / lumaContrast;

lumaContrast在前面已经说过,是N、S、W、E、M,5个像素中最大亮度-最小亮度。易知,blend范围位于0~1。最终颜色采样代码如下:

half4 finalColor = SampleLinear(tex,pixelCoord + normal * blendL);

以上公式意味着:中间像素亮度(lumaM)与期望亮度(lumaL)的差值越大,混合因子(blend)越趋向1,也即中间像素的颜色越往其亮度的最大梯度方向(normal)偏移

改进混合因子后,抗锯齿效果如下图:

左为无抗锯齿,右为FXAA1.0

软阴影部分完全没受到影响,物体边缘也呈现出了模糊渐变效果。但也可以发现一些不足之处:物体边缘有些"过于"模糊了(相比较于MSAA而言)。这个不难理解。因为如我们前面所述的边缘查找算法里,边缘像素是呈"一对"存在的。1,2在黑色阵营里是边缘像素,那么1,2 normal朝向的白色像素,在白色阵营里自然也属于边缘像素。经过Blend之后,边缘渐变至少会覆盖两个像素宽度,于是就显得有些"厚"。

于是添加了两个参数

  • FXAA_QUALITY_SUBPIX_TRIM 用来把混合因子整体往0靠靠,这样边缘就不会那么的糊了。
  • FXAA_QUALITY_SUBPIX_CAP 用来把混合因子进行一个整体的scale(0.x),也是为了不那么糊,至少保留住一点高频信息吧。
//float blend = lumaDeltaML / lumaContrast;
float blend = max(0,lumaDeltaML / lumaContrast - FXAA_QUALITY_SUBPIX_TRIM) * FXAA_QUALITY_SUBPIX_CAP;

2. 改进版FXAA 3.11

自2009年发布了FXAA初代目,Timothy在两年后的SIGGRAPH上发布了FXAA 3.11。与FXAA初代目相比,最大的不同就是改进了混合因子算法。Timothy觉得,要真正解决边缘模糊"太厚"的问题,就要深入分析锯齿产生的本质原因。

我们知道,SSAA和MSAA是通过在一个像素内部增加额外的采样点,然后进行颜色加权混合达到抗锯齿目的的。如上图所示,一个采样点,如果落入几何内部,那么整个像素就都是红色。如果落到几何体外部,那么整个像素都是蓝色。

一根线,穿过一个像素方块,将其劈成两半。一半归入蓝色阵营,一半归入红色阵营。很明显,我们可以将两部分面积分别作为蓝色和红色的权重,进行加权平均计算,最终得到混合色。当我们观察锯齿的一段局部线条区域时,容易发现,红色面积权重是随着线条方向逐渐减小的。当红色权重少于0.5时,自然像素就翻转成蓝色了。

基于这个,Timothy发明了边缘搜索算法。原理如下:

  • 首先确定边缘像素的横纵向
  • 往边缘两侧按照给定的步长进行边缘终点搜索
  • 确定了边缘线段的两个端点后,可以计算出当前像素在线段中的位置
  • 根据像素在线段中所处的位置来计算出混合因子

于是以上算法归纳出两个待解决的问题:

  1. 如何判定搜索的目标像素为边缘终点?
  2. 边缘上每个点的位置与混合因子满足怎样的关系?
一、边缘终点判定

首先我们可以将搜索的起始点定为像素往normal方向偏移0.5个单位的位置。如下图:

为什么要往normal方向偏移0.5个像素进行采样呢?假如我们不进行偏移,比如直接从黑色像素中心往两边搜索,那么左侧很显然无法查找到正确的端点。

使用线性插值采样,那么采样结果将是边缘两侧像素的平均亮度。不如将起始点亮度记为lumaStart。假设我们往两侧一个个像素进行判定,直到发现亮度与lumaStart的差值超过一定阈值,便认为到头了。并且这个阈值应当跟搜索起始点两侧像素的对比度lumaStartContrast呈正相关,其中0.25只是一个经验性的系数。

float searchEndThreshold = lumaStartContrast * 0.25

所谓边缘,是由边缘两侧的像素对比所产生的。在搜索的时候一定要同时考虑边缘两侧的像素。

那为什么不在搜索的通过计算两侧的亮度差来确认终点,而是要计算两侧的平均亮度呢? Timothy说你仔细想想,当然是为了少一次采样啊。计算差要采样两个像素做减法,而我用平均,可以直接使用Bilinear Filter一次采样足矣。

二、混合因子计算

混合因子的计算方式:

  • 设边缘线长为edgeLength(可以通过搜索到的两个端点位置相减得到)
  • 计算出距离当前像素比较近的一个端点,记为targetP,距离记为dst
  • 考察targetP与当前像素是否属于同一阵营。
  • 如果是,则blend为0,即当前像素保持原样,不进行混合。
  • 如果否,则blend = abs(0.5 - dst/edgeLength)(个人理解:偏移中心的程度)

那么如何考察边缘端点与当前像素是否属同一阵营呢?不妨假设搜索结束时,已经得到距离当前像素较近的边缘端点亮度为lumaEnd。我们知道,搜索起始点的亮度lumaStart为两个阵营的平均亮度。

  • 因此可以将当前像素亮度(lumaM)与阵营平均亮度做比较: lumaM - lumaStart
  • 同时将端点亮度(lumaEnd)也与阵营平均亮度做比较: lumaEnd - lumaStart

如果两者符号一致,则属于同一阵营,否则属于不同阵营。

于是blend计算公式如下:

float blend;
if((lumaM - lumaStart) * (lumaEnd - lumaStart) > 0)//同一阵营
{blend = 0;
}
else//不同阵营
{blend = 0.5 - dst/edgeLength;
}
三、搜索步长优化

在进行边缘搜索时,一个个像素搜索是不现实的,我们需要一个策略来将采样次数控制到限定范围内。很容易想到的一种方式是,给定一个最大搜索次数MAX_EDGE_SEARCH_SAMPLE_COUNT,然后在搜索时逐渐增大步长。

FXAA3.11 Quality原算法里提供了很多组预设,可以参考GitHub上的源码。例如Preset12的定义如下

#define FXAA_MAX_EAGE_SEARCH_SAMPLE_COUNT 5
static half edgeSearchSteps[FXAA_MAX_EAGE_SEARCH_SAMPLE_COUNT] = {1,1.5,2,4,12};

意思是最多执行5步搜索(每步同时往两个方向进行采样,最多会有10次采样),edgeSearchSteps[]数组定义了每步搜索的步长。自然给予的最大搜索步骤越多、步长越小,边缘的渐变就能更细腻。 通过使用不同的参数,我们可以在图像质量与性能之间寻求平衡。

到此为止, 结果如下:

无抗锯齿对比图

带瑕疵的FXAA Quality

水平边缘的抗锯齿效果很不错,边缘渐变层足够"薄",一举解决了FXAA初代目令边缘太模糊的问题。

可是垂直方向却出现了一些瑕疵点。这是怎么回事呢?

四、额外对角线采样

打开边缘法线调试效果Debug一下:

4种颜色,代表了边缘法线的4种朝向

原来垂直向的噪点是由于混合方向计算不正确引起的。仔细深入分析了一下,Timothy终于找到了原因。在早先我们计算边缘法线的时候,只采样了N,S,E,W4个方向的像素亮度。再看一下当时的计算方式:

float2 normal = float2(0,0);
if(isHorz)
{normal.y = sign(abs(lumaGradN) - abs(lumaGradS));
}
else
{normal.x = sign(abs(lumaGradE) - abs(lumaGradW));
}

如果仅有这4个方向,不足以分析如下的情况:考察像素,其lumaGradN(上)lumaGradE(右)梯度相同,lumaGradS(下)lumaGradE(左)梯度相同,因此假如只考虑上下左右4个方向的像素亮度,我们是无法计算出像素1的边缘法线的,这时候就要额外考虑其4个对角线像素的亮度。

于是Timothy将边缘法线计算公式修改如下:

//第一行,第二行和第三行的梯度
lumaGradH = abs(lumaNW + lumaNE - 2 * lumaN) //横-上+ 2 * abs(lumaW + lumaE - 2 * lumaM) //横-中+ abs(lumaSW + lumaSE - 2 * lumaS); //横-下
//第一、二、三列
lumaGradV = abs(lumaNW + lumaSW - 2 * lumaW) //纵-左+ 2 * abs(lumaN + lumaS - 2 * lumaM) //纵-中+ abs(lumaNE + lumaSE - 2 * lumaE); //纵-右

意思的在水平方向,我们同时计算第一行,第二行和第三行的梯度,对应纵向是第一、二、三列,将他们加权求和后进行对比来确认边缘法线朝向。如此这般就可以得到正确结果了。

增加对角线采样后的效果:

可以发现垂直边缘的瑕疵点消失了。

3. FAXX Console版

边缘搜索,即便是只进行5步的搜索,也需要10次采样(至多),再加上需要进行对角线采样,因此累计采样次数竟多达19次! 这个开销还是比较大的。

对主机版的需求:

  • 将采样次数控制在尽量少
  • 依旧要满足边缘渐变足够"薄"

Timothy心想,既然如此,也不去判断什么局部边缘的横纵轴了,我直接利用局部亮度信息,去估算一下该局部区域边缘的斜率(切线走向)。具体怎么做呢?

首先在当前像素的四个角进行4次采样,如下图所示。

注意,这里只偏移0.5个像素位,并非采样对角像素。因此通过Bilinear插值采样是能够一次性获得4个像素的平均亮度的。不妨记4个角采样到的亮度为lumaNW,lumaNE,lumaSW,lumaSE

Timothy给出的边缘走向估计如下:

float2 dir;
dir.x = (lumaSW + lumaSE) - (lumaNW + lumaNE);
dir.y = (lumaNW + lumaSW) - (lumaNE + lumaSE);
dir = normalize(dir);

这个估计是什么意思呢?其实等于以下的滤波核:

 -1/4 | -1/2 | -1/40   | 0    | 0   1/4 | 1/2  | 1/4

它表征了当前像素上方3个像素和下方3个像素的对比强度,以此作为边缘切线在x轴向的投影分量,这个是合理的。y轴也是同理。

既然决定只采样4个角,那么局部亮度对比度的计算也只能从这4个采样点中去估计了。局部对比度计算公式如下:

float lumaMax = max(lumaSW,lumaSE,lumaNW,lumaNE,lumaM);
float lumaMin = min(lumaSW,lumaSE,lumaNW,lumaNE,lumaM);
float lumaContrast = lumaMax - lumaMin;

当对比度大于阈值时,视为边缘像素。 阈值方式同初代目版本。在估算出切线走向后,接下来要想办法进行混合。为了让边缘能够Sharp一些,不像初代目那般边缘模糊,Timothy决定不向normal方向进行混合。他决计在切线的正反两个方向,偏移一定距离(0~1),各进行一次采样,求平均作为当前像素的颜色。这样求得的颜色,就在切线方向起到了一个渐变过渡作用。

沿切线方向的两次采样

不难看出,偏移距离决定了当前像素颜色在最终颜色里所占的比重。很明显,Timothy的这个思路是优先照顾那些45度角的锯齿边缘,抛弃那些接近于水平或者垂直的锯齿边。对于这种接近水平/垂直的边缘,沿切线方向进行少量偏移采样极难覆盖到敌方阵营的像素。因而也就几乎失去了混合的效果。

无法有效抗锯齿的水平边缘

为了补救这个问题,Timothy额外加入了两次采样。这两次采样的偏移距离根据切线的斜率来决定。越趋于水平/垂直,那么偏移距离就越远,企图以此覆盖到敌对阵营。计算公式如下:

//FXAA_SHARPNESS是一个暴露给用户的可调节参数
float dirAbsMinTimesC = min(abs(dir.x),abs(dir.y)) * FXAA_SHARPNESS;
float2 dir2 = clamp(dir / dirAbsMinTimesC,-2,2) * 2;

我们知道,切线为45度角的情况下,dir.x == dir.y ~= 0.7,dir.length=1,因此随着切线角度变化

  • dirAbsMinTimesC的取值范围为[0 , 0.7 * FXAA_SHARPNESS]
  • dir2的范围为 (-2/FXAA_SHARPNESS , -4)U(2/FXAA_SHARPNESS , 4)

因此随着FXAA_SHARPNESS增大,dir2会越保守,越靠近当前像素,边缘就会越锐利。

需要额外注意的是,由于我们估计的只是局部边缘切线,在dir2较大的情况下,可能会采样到差异很大的像素。因此需要针对额外的两次采样做一下噪点过滤。过滤规则为,若额外两次采样的亮度,超出局部亮度范围[lumaMin,lumaMax],那就丢弃,只使用前两次的采样结果。这样我们对最终合法的采样结果(2或4次采样)求平均作为当前像素颜色即可。

但总得来说,这只能算是"补救"。FXAA Console针对水平/垂直的边缘抗锯齿效果的确比较勉强。水平和垂直的FXAA-Console抗锯齿效果图如下,局部会出现一些不自然的过渡点。

FXAA Console水平/垂直抗锯齿效果

对于斜边或者曲线的抗锯齿效果则很不错

FXAA Console 曲边/斜边抗锯齿效果

4. 最终大比拼

三种方式对比

采样次数

缺陷

FXAA初代

5

边缘太模糊

FXAA3.11 Quality

9 + N * 2(N至少>=3才有比较好的效果)

相对而言比较吃性能

FXAA3.11 Console

9

水平/垂直边缘抗锯齿较弱

同一个场景对比(画面放大6倍后):

5. 补充

下面说说FXAA的最佳使用规范:

  • 最好在完成所有后处理效果之后再执行FXAA
  • 最好在sRGB空间执行
  • 需要在LDR空间执行

这些规范不难理解。因为FXAA图像抗锯齿是基于"视觉的"而不是基于物理的。因此在我们进行亮度计算时,必须时基于"视觉"的亮度,而不是"物理"的亮度。另一方面,HDR到LDR的ToneMap不是线性的,所以即便在HDR空间中执行FXAA,ToneMap后也会失效。

优势:

  1. 减少可见的锯齿,同时保持清晰度并保持在游戏引擎的实际毫秒/帧成本范围内。对于 FXAA 预设 2,在 GTX480 上处理 1920x1200 帧的成本低于 1 毫秒。
  2. 以三角形边缘和着色器结果中的锯齿为目标。FXAA 能够减少单像素和子像素锯齿:抖动采样阴影区域中点画锯齿的减少。
  3. 易于集成到单个像素着色器中。FXAA 作为单通道滤镜在单样本彩色图像上运行。与 MSAA 相比,FXAA 可以提供内存优势,尤其是在立体和多显示器渲染目标或后台缓冲区上。
  4. 与使用 MSAA 和对多个样本进行着色相比,可以为延迟渲染提供性能优势。

(四) 时域抗锯齿 TAA

参考:深入浅出Temporal Antialising - 知乎 (zhihu.com)

抗锯齿技术可以从原理上分为两个大类:

  • 空域抗锯齿,代表是FXAA和SMAA
    • 是通过超采样几何边缘达到模糊边缘的效果,从理论上解释就是通过低通滤波抑制几何边缘的高频信息,它只能缓解几何锯齿,对着色锯齿无效。帧间抗锯齿技术,是通过加权混合相邻多帧达到抗锯齿效果,从理论上解释就是将计算量分摊(Amortized)至多帧的超采样。超采样能有效地提高采样率,帧间抗锯齿能有效缓解几何锯齿和着色锯齿。
  • 时域抗锯齿,代表是TAA。
    • 帧间抗锯齿技术的实现依赖于帧间相关性(frame-to-frame coherence)。我们看到的视频或者游戏画面,上一帧画面中绝大部分内容也会出现在下一帧画面中,通常是平滑过渡的,比较少出现突变。也正是因为受到帧间相关性地启发,才有了帧间抗锯齿技术。

TAA/TXAA(Temporal Anti-Aliasing)并不是一个单独的算法,更像是一个算法框架,有很多的变种。有些引擎,将TAA与一些空域抗锯齿技术结合起来使用,以期待达到更好的抗锯齿效果。

TAA是英伟达设计的更高画质的抗锯齿技术,可以达到和SSAA接近的效果,只不过多采样是基于历史帧缓冲,从历史帧中采样。缺点是随着历史颜色的累积,会导致不可绝对消除的模糊(运动模糊),尤其在移动过快的镜头或物体情况下,会导致重影现象,尤其VR设备中此方法不适用。

帧间抗锯齿技术的核心思想就是将运算量分摊至多帧,如图所示,任意一帧只计算一个像素点,随着t个帧的累积,就有t个采样像素,再混合结果,就相当于做了t个子像素的超采样。当然,每帧中像素需要做一定的抖动(Jitter),不然不同帧相同位置的像素值就完全一样,也就没有了分摊多帧的意义。通过对像素分摊至多帧的超采样,来提高采样效率,达到缓解锯齿的目的。

尽管通过分摊超采样的方法,将计算量分摊到了不同帧,但是保存多帧也是不切实际的。那就有了只混合相邻两帧的思路,本篇文章分别称之为当前帧和历史帧(History Frame)。若当前帧的像素值是

,前t-1帧的历史帧像素是

,将它们加权混合就得到分摊多帧超采样得到的结果,如等式所示。

称这种方法为指数平滑滤过滤(Exponential Smoothing Filter),其中,α是一个加权值,称为指数平滑系数,可以是一个固定值,也可以是一个自适应变化的值。它有点类似于累积缓存器(Accumulation Buffer)的原理,不断累积结果。

重投影

在实际应用中,画面并不是完全静止的,镜头在运动、模型在动,例如当前帧模型上的一个像素点位于(100, 520),它在历史帧对应的位置可能是(99,530)。那么,我们就需要计算当前帧像素点在历史帧所处的位置,这个过程称为重投影(Reprojection)。如果只有镜头在动,那通过镜头的变换矩阵就可以计算出历史帧的位置;但是如果模型本身也在动,就需要计算模型顶点在当前帧与历史帧的位置差值,这个差值称之为运动矢量(Motion Vector),可以把场景内运动模型的运动矢量存储在一个缓存中,这个缓存就称为速度缓存(Velocity Buffer)。重投影是基于当前帧和历史帧的视图投影矩阵与速度缓存,推算出屏幕上当前帧像素点的位置p在历史帧对应位置

的过程。

修正历史帧像素

至此,已经得到当前帧像素在历史帧中的位置,也就容易根据历史帧采样到历史帧像素值

。但是,由于每帧渲染时有像素抖动、模型运动、渲染环境变化(例如光照条件)等,它们会导致渲染结果发生变化,此时得到的历史帧像素就可能不具参考性。如图7所示,当前帧位置p的像素,在历史帧对应位置的像素被遮挡住了,那么重投影得到的历史帧像素与当前帧像素无法对应。那么,就需要一个验证历史帧像素并修正的阶段,修正(Rectify)历史帧像素值,再将它代入等式算出最终结果。

相反,如果不对历史帧像素进行修正,就可能会出现鬼影(Ghosting)现象


重新复盘下帧间抗锯齿技术的流程,如图所示。对镜头进行抖动,渲染场景至主缓存中;如果模型发生运动,需要将渲染运动模型的运动矢量存至速度缓存。在一个TAA的后处理历程中,通过重投影获取当前帧像素在历史帧中的位置,获取历史帧像素,验证并修正历史帧像素,将历史帧像素和当前帧像素根据等式加权混合,输出结果。

三、移动端抗锯齿的建议

开发者选项中的“4倍抗锯齿”对游戏有没有用?用两部手机测一测就知道了_安卓手机_什么值得买 (smzdm.com)

如果我们在开发者模式中强制开启4倍抗锯齿,并且始终使用GPU来处理图像画面,在一定程度上可以改善手游的画质,让游戏运行变得更加流畅吗?

通过新旧两部手机的两轮测试,我们可以得到以下结论:

  • 开发者模式中的“强制启用4×MSAA”、“停用HW叠加层”两个选项,一定程度上可以让游戏运行变得更加流畅,但改善画质真的不能体现,因为开启与否平均帧率都非常相近,这代表单位时间内加载的画幅数量是相近的,所以看不出画质有本质的提升;所以尽量从设计上避免引起锯齿。
  • 旗舰手机在芯片性能溢出的前提下,可以选择开启这两个选项提高游戏的流畅度,但付出的代价是温度,手机明显会更烫;
  • 非旗舰手机、旧手机不建议这样折腾,因为芯片算力本身就吃紧,渲染任务全推给GPU后,游戏运行不光更卡,机身也更烫,游戏体验非常糟糕。

参考链接:

(3 封私信) 请问FXAA、FSAA与MSAA有什么区别?效果和性能上哪个好? - 知乎 (zhihu.com)

抗锯齿 - LearnOpenGL CN (learnopengl-cn.github.io)

photoshop中羽化的操作技巧-百度经验 (baidu.com)

https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf

GeForce Tech Demo: MFAA

https://zhuanlan.zhihu.com/p/90982812?utm_source=qq

【Metal2研发笔录(七):抗锯齿之基于A11 Imageblock特性的增强MSAA - 知乎 (zhihu.com)

Anti-Aliasing 技术盘点 - 知乎 (zhihu.com)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com