图形学基础|抗锯齿(Anti-Aliasing)
文章目录
图形学基础|抗锯齿(Anti-Aliasing)
一、前言
二、锯齿
2.1 采样理论
2.2 分类
三、抗锯齿概述
3.1 SSAA(Supersampling Anti-Aliasing)
3.2 MSAA (Multisample Anti-Aliasing)
3.2.1 Coverage(覆盖)和Occlusion(遮挡)
3.2.2 MSAA Resolve(MSAA 解析)
3.2.3 MSAA与延迟渲染
四、时间抗锯齿(Temporal Anti-Aliasing)
4.1 算法框架
4.2 采样
4.3 合成
参考博文
一、前言
在图形渲染中,锯齿或者说走样是一个不得不提的问题。
本文将对锯齿的产生以及相关的抗锯齿技术进行一个简单的介绍,尤其是时间抗锯齿(Temporal AA)。
二、锯齿
2.1 采样理论
图形渲染,实质而言是一种采样(Sampling):对三维场景进行采样,输出 2 D 2D 2D的图像。
根据奈奎斯特采样定理:
要想通过对采样后的信号进行重建(Reconstruct)来获得完美的原始信号,就必须要保证采样频率不低于原信号最高频率的两倍。
更多细节请参考《信号与系统》相关的知识。
这里隐藏着一个完美重建的条件,即原信号的最高频率必须要要是有限的(band-limited)。
但场景的是义在三维空间中是连续函数(包含的场景的几何覆盖关系,着色参数和着色方程等),这个函数并非有限带宽的函数。
因此,不论以多大的采样频率(反应在图形上,即图像分辨率)去采样这个函数,都不可能完美的恢复原始信号。
最终显示的像素则是一个离散的二维数组,判断一个点到底没有被某个像素覆盖的时候单纯是一个“有”或者“没有"问题,丢失了连续性的信息,导致锯齿(走样,Aliasing)。
其实在现实生活中,走样现象还是比较常见的,比如摩尔纹,车轮倒转等。
2.2 分类
具体到实时渲染领域中,可以将锯齿(走样)分为以下三种:
几何走样(Geometry Aliasing),几何物体的边缘有锯齿,是对几何边缘采样不足导致。
着色走样(Shading Aliasing),渲染方程也是一个连续函数,对某些部分(比如法线,高光等)在空间变化较快(高频部分)采样不足也会造成走样,比较明显的现象就是高光闪烁或高光噪点。
时间走样,主要是对高速运动的物体采样不足导致。比如游戏中播放的动画发生跳变等。
三、抗锯齿概述
锯齿问题是渲染系统中不可绕过的问题,而为了解决这个问题,也涌现了一批优秀的解决方案。
这些降低锯齿感的做法,称作抗锯齿Anti-Aliasing,简称AA。
抗锯齿的方法一般可以分为两类:
空间抗锯齿技术
时域抗锯齿技术
空间抗锯齿技术是指:
仅依据当前帧的信息,对锯齿或走样现象进行缓解减少。
时域抗锯齿技术是指:
不仅根据当前帧的信息,还使用了历史帧的信息,从而实现锯齿现象的减弱。
空间抗锯齿的算法有很多,如:SSAA、MSAA、CSAA、DEAAA、MLAA、SRAA、FXAA、SMAA。
本节将对其中的SSAA、MSAA进行简单的介绍。
时域抗锯齿技术将在第四节中进行介绍。
3.1 SSAA(Supersampling Anti-Aliasing)
SSAA,基于超采样的方法,是最简单粗暴的抗锯齿技术。
注,FSAA是Full-Screen AA的缩写,虽与SSAA名字不同,但两者指的是同一项AA技术。
拿4xSSAA举例子:
假设最终屏幕输出的分辨率是800x600, 4xSSAA就会先渲染到一个分辨率1600x1200的buffer上,然后再直接把这个放大4倍的buffer下采样致800x600。
这种做法在数学上是最完美的抗锯齿(同时是几何反走样和着色反走样方法),因为它不但增加了当前几何覆盖函数(Coverage)的采样率,也对渲染方程进行了更高频率的采样(单独计算每个子像素的颜色)。
但是劣势也很明显,光栅化和着色的计算负荷都比原来多了4倍,RenderTarget的大小也涨了4倍,这导致其性能太差。
SSAA,简单的来说可以分三步:
在一个像素内取若干个子采样点;
对子像素点进行颜色计算(采样)
根据子像素的颜色和位置,利用一个称之为resolve的合成阶段,计算当前像素的最终颜色输出;
不同SSAA方式在子采样位置的选取和最终resolve使用的滤波器上有所不同。
可以使用不同的采样模板(规则采样,旋转采样,随机采样,抖动采样等)或者不同的滤波函数(方波滤波器或者高斯滤波器)。
3.2 MSAA (Multisample Anti-Aliasing)
摘自深入剖析MSAA和延迟渲染与MSAA的那些事
SSAA,需要更多的显存空间和更多的着色计算(每个子采样点都需要进行光照计算),所以一般不会使用这种技术。
MSAA,基于人眼对几何走样更敏感的原则,将几何覆盖函数的采样率和着色方程的采样率进行了解耦。其将一个像素划分为若干个子采样点,但相较于SSAA,每个子采样点的颜色值完全依赖于对应像素的颜色值进行简单的复制(该子采样点位于当前像素光栅化结果的覆盖范围内),不进行单独计算,即只计算一次着色。
子采样点计算一个覆盖信息(coverage)和遮挡信息(occlusion),即每个子像素会在光栅化阶段分别计算自身的Z值和模板值,有完整的Z-Test和Stencil-Test并单独保存在Z-Buffer和Stencil-Buffer里。
3.2.1 Coverage(覆盖)和Occlusion(遮挡)
覆盖(Coverage):
通过判断一个图形是否跟一个指定的像素是否重叠来决定的。
如下图,一个三角形的覆盖信息。蓝色的点代表采样点,每一个都在像素的中心位置。红色的点代表三角形覆盖的采样点。
遮挡(Occlusion):
一个图形覆盖的像素是否被其它的像素覆盖了,即基于z-buffer的深度测试。
覆盖和遮挡两个一起决定了一个图形的可见性。
由于对于每个子采样点而言,都需要存储额外的深度值,这意味着深度缓冲区是非MSAA情况下的 n n n倍。
并且,虽然只对每个像素进行着色一次,但是这并不意味着我们只需要存储一个颜色值,而是需要为每一个子采样点都存储颜色值,所以我们需要额外的空间来存储每个子采样点的颜色值。所以,颜色缓冲区的大小也为非MSAA下的n倍。
一般情况下是这样,如果没有优化。
因此如果一个三角形覆盖了4倍采样方式的一半,那么一半的子采样点会接收到新的值。或者如果所有的子采样点都被覆盖,那么所有的都会接收到值。
通过使用覆盖掩码来决定子采样点是否需要更新值,最终结果可能是n个三角形部分覆盖子采样点的n个值。
下图展示了4倍MSAA光栅化的过程。
3.2.2 MSAA Resolve(MSAA 解析)
像超采样一样,过采样的信号必须重新采样到指定的分辨率,这样我们才可以显示它。
这个过程叫解析(resolving)。
在它最早的版本里,解析过程是在显卡的固定硬件里完成的。一般使用的采样方法就是一像素宽的box过滤器。这种过滤器对于完全覆盖的像素会产生跟没有使用MSAA一样的效果。
不同的硬件厂商可能会使用不同的算法。
不同的子采样点的个数会带来不同的抗锯齿效果,如下图所示。
随着显卡的不断升级,我们现在可以通过自定义的shader来做MSAA的解析了,比如DX12就支持。
小结如下:
MSAA并不是在光栅化阶段就可以完全的,它在这个阶段只是生成覆盖信息,然后计算像素颜色,根据覆盖信息和深度信息决定是否来写入子采样点。
整个完成后再通过某个过滤器进行降采样得到最终的图像。大体流程如下所示:
3.2.3 MSAA与延迟渲染
延迟渲染到底能不能开MSAA?为什么?
从原理上来说,是完全没有问题的。
延迟渲染分为GBuffer阶段和光照阶段,看其具体步骤:
执行一个GBuffer Pass,通过多目标渲染(Multiple Render Targets,MRT)技术,将最终会显示到屏幕上的像素的颜色(BaseColor)、深度(Depth)、法线(Normal)等信息写入多个RT/纹理中,这些纹理组成了GBuffer。
执行Lighting Pass,逐像素分别从GBuffer的纹理中取出需要的信息,运行像素着色器计算出最终的颜色缓冲进行显示。
从MSAA的原理中,MSAA对于光照是没有抗锯齿的功能的(因为并没有计算多余的光照信息),其本身是一种几何抗锯齿算法。
因此,有效的应用阶段其实是GBuffer阶段。
几何锯齿的产生原因是像素有大小,在光栅化时对于三角形边缘的像素采样不足,MSAA就是提高了对光栅化采样率来达到抗锯齿的效果。GBuffer中,记录光栅化覆盖信息的是BaseColor,因此只需要在渲染BaseColor纹理的过程中执行MSAA,即可达到抗锯齿的效果。
为什么会有“延迟渲染没法使用硬件抗锯齿”说法呢?
因为:在十几年前的DX9时代,MRT是不支持MSAA的!!!
后来的DX10.1就支持了带MSAA的MRT。
不过,MRT要求对每个RT使用相同的Sample,所以我们是没法对BaseColor开小灶的,要么多花一个Pass先画BaseColor再画其他,要么对其他RT也进行多倍采样。
对于前者,新增Pass又再一次增大了性能消耗。
而后者会对深度和法线进行插值,显然也不可行,除非自定义最后插值的过程。
这样一通操作下来,还是消耗了数倍的带宽。
这样权衡下来,还不如直接上SSAA算了,至少后者效果更好。
而实践中,TAA等后处理抗锯齿加SSAA的组合成为主流,MSAA的身影反而几乎看不到了。
四、时间抗锯齿(Temporal Anti-Aliasing)
TAA(Temporal Anti-Aliasing),是一种基于时间的反走样方法,它仍是为了解决几何走样和着色走样,并非为了解决时域走样(如旋转车轮)。
根据前文介绍,走样的出现是由于采样不足, SSAA 和 MSAA 都是将采样点散布在当前帧的二维空间里。
而基于时间的反走样则是将把采样点散布在帧序列(时间)里,从而减轻了单帧渲染的负担。
如下图所示,SSAA在每帧都需要执行多个子像素采样,而TAA则是把这些采样点均摊到多帧。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jALfFNAI-1626444779199)(images/10-SSAA-TAA.png)]
它基于一个假设:
整个场景很少发生大幅度的镜头/物体运动,帧与帧之间具有比较明显的连续性,上一帧某个物体的微小表面在下几帧中仍会出现(只是位置发生了较小移动)。
就理论而言,TAA在场景运动变化不大的情况下,效果和性能都显著优于上述的各类算法。
4.1 算法框架
TAA的基本框架如下图所示:
总体而言,可以分为两个部分:
采样(Sampling),包含了对当前帧的采样和历史帧的采样。
合成(Resolve),对历史帧的信息和当前帧的进行合成,其中包含了很多处理的算法。
4.2 采样
TAA的核心思想如下图所示,把样本分布到过去的N帧(历史帧)中去,然后每一帧从过去的N帧中取得样本信息然后Filter,达到N倍Super Sampling的效果。
对于一个完全静止的画面(相机不运动,场景中的物体也不发生运动),只需要每帧简单地对采样点的位置进行抖动(Jitter),稍微改变采样点的位置即可生成多个采样点,再采样加权这些样本,即可实现超采样,从而实现抗锯齿的目的。
那么如何生成这些样本点呢?
常用的方法是:对采样点进行偏移。具体的实现方法为对投影矩阵添加小的偏移量。
如UE4中:
ProjMatrix[2][0] +=(SampleX * 2.0f -1.0f)/ViewRect.Width();
ProjMatrix[2][1] +=(SampleY * 2.0f -1.0f)/ViewRect.Height();
当然,随机生成采样偏移的方法是可行的。
但是我们希望采样点在像素矩形内尽可能均匀且分散,而纯随机的采样点可能会导致一定的聚集。
对于TAA而言,除了在空间上均匀且分散以外,我们还希望样本点能够在时间上也是均匀分散的。
Low-discrepancy(低差异序列)则可以为我们提供非常好的均匀散布的特性。
使用低差异序列作为采样点的一个好处是,无论需要多少个采样点,都可以通过取序列中的前 N 个值,得到一个均匀分布的 pattern。
不过,采样点的数量会影响TAA的收敛速度,如果配合不恰当的历史帧混合还会导致画面抖动的现象。
UE4使用的是Halton Sequence。在二维空间下的 Halton Sequence 通常采用 2 和 3 作为 XY 坐标序列的基数,更大的基数带来非常规整的分布。
UE4默认选择策略是halton(2, 3)的前8次采样,同时还提供了多种配置。
下面为UE4不同采样数的抖动策略:
// [-0.5, 0.5]
if( CVarTemporalAASamplesValue == 2 )
{
// 2xMSAA
// Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx
// N.
// .S
float SamplesX[] = { -4.0f/16.0f, 4.0/16.0f };
float SamplesY[] = { -4.0f/16.0f, 4.0/16.0f };
check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX));
SampleX = SamplesX[ TemporalSampleIndex ];
SampleY = SamplesY[ TemporalSampleIndex ];
}
else if( CVarTemporalAASamplesValue == 3 )
{
// 3xMSAA
// A..
// ..B
// .C.
// Rolling circle pattern (A,B,C).
float SamplesX[] = { -2.0f/3.0f, 2.0/3.0f, 0.0/3.0f };
float SamplesY[] = { -2.0f/3.0f, 0.0/3.0f, 2.0/3.0f };
check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX));
SampleX = SamplesX[ TemporalSampleIndex ];
SampleY = SamplesY[ TemporalSampleIndex ];
}
else if( CVarTemporalAASamplesValue == 4 )
{
// 4xMSAA
// Pattern docs: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476218(v=vs.85).aspx
// .N..
// ...E
// W...
// ..S.
// Rolling circle pattern (N,E,S,W).
float SamplesX[] = { -2.0f/16.0f, 6.0/16.0f, 2.0/16.0f, -6.0/16.0f };
float SamplesY[] = { -6.0f/16.0f, -2.0/16.0f, 6.0/16.0f, 2.0/16.0f };
check(TemporalAASamples == UE_ARRAY_COUNT(SamplesX));
SampleX = SamplesX[ TemporalSampleIndex ];
SampleY = SamplesY[ TemporalSampleIndex ];
}
else if( CVarTemporalAASamplesValue == 5 )
{
// Compressed 4 sample pattern on same vertical and horizontal line (less temporal flicker).
// Compressed 1/2 works better than correct 2/3 (reduced temporal flicker
友情链接:
Copyright © 2022 卡塔尔世界杯排名_98世界杯决赛 - dylfjc.com All Rights Reserved.