我们在绘制的时候,不管用的算法多高级,最后都是一个像素一个像素地绘制出来。每一个像素是对连续世界的一个采样。
三大源泉:
- 几何的边,只要不是严格的横平竖直,就一定会产生 zig zag 的效果。
- 物体表面有很多细节,Texture 从不同角度看的时候,就会产生很多 Artifact。(mipmap 解决了)
- 平常生活中有很多高频的(变化大的)变化,例如高光。我们拿一个高亮的东西,动一下,上面的高光一下子就流动。
高光也是游戏里产生 Alias 很重要的一个源泉。
怎么去除走样呢?Anti-aliasing。
核心思想就是,我知道我在每个像素的采样是不充分的,那我就多采样(sub sample)几个,这样采样的值就有变化了。变化的值进行平均,平均之后就会发现不再是硬的边界,而是会产生很多过渡区,这样看起来就会显得很光滑。
现在我们打的所有的字都是 True-Type,也就是矢量,可以无限放大的。但是放大的时候,实际上屏幕的分辨率是一定的,仔细看,就会发现会产生 zig zag 的结果。现代计算机在渲染的时候,一般都会打开 Anti-aliasing。
怎么做 AA?
SSAA - Super-sample AA
超采样,假如要绘制一个 1024*768 的图像,我们把所有尺寸 double 一下,变成 2048*1536 的尺寸,得到这个 4x 的图像后,做 down sampling,做一个小的滤波,就能得到上图左边的结果。
代价大,Depth Buffer 、Frame Buffer 、pixel shader 渲染都得付出额外 4 倍的代价。
Alias 的问题多产生在边界,三角形中间的点其实不需要进行超采样,因此后人有发明 MSAA。
MSAA - Multi-sample AA
虽然还是四倍采样,但 Shading 的时候会看,如果四个 sub-pixel(sub sample) 都在里面(实现的时候的逻辑是:如果这个 triangle 的 coverage 对这四个采样点是百分之百的话),就只 Shading 一次,如果在这四个采样点中不止一个三角形贡献,那就多 shading 一下求平均。
现代硬件很早就支持了。
还是要付出 4 倍 Depth Buffer 和 Frame Buffer,只是在做 pixel shading 的时候能跳过很多不必要的渲染。
这个方法的问题是:现代游戏中,我们的几何密度是非常高的,例如:unreal nanite 技术要解决的:场景中三角形的数量会超过像素的数量。这么密的三角形,MSAA 会彻底失效。
SSAA 和 MSAA 是上古时代的技术,简单实用,也古老。
现代有很多种反走样的变种,下面主要讲两种,它们代表两个非常巧妙思想的方向。
FXAA - Fast Approximate AA
名字就能看出来,是一个快速估算的 AA 算法。
只给一个图像就能做反走样。
前面我们做 AA 的时候都是在边界,高光、颜色的变化、或者三角形折起来,都会产生颜色的跳变。计算机视觉有个领域就是 edge detection,它会把图片的 edge 提取出来。
如果在这些 edge 上做一些插值算法,是否也能产生反走样的效果呢?
对每个像素点,对其左右邻居,上下邻居加在一起,算出差异值,当其超过某个阈值的时候,就认为是一个边界。
怎么查找阈值?把图片转换到亮度空间,亮度空间可以根据下面的经验公式算出。变成了黑白图片后,就左右上下比较色差,如果超过阈值,就可以认为是边界点。
竖向做卷积,横向做卷积,然后看哪个方向差别大,就认为边界在哪个方向上。
上图的例子中,横向差别大,再比较是横向左边的邻居差别大,还是右边差别大。
上图是右边邻居差别大,就能得出 offset 朝向。因此如果要做 AA Blending 的时候,我们应该跟右边进行 Blending。
我们也不能粗暴的 Blending。我这个点可以和应该要 Blending 的点结成对,求出平均强度值,然后根据之前算出的朝向。图中例子 perpendicular 垂直的方向是左右,就左右找,如果找到的像素颜色变化和我本身差不多,就认为是一伙的。如果找到一对像素和我本身差别大,例如上图右下最左边蓝框框起来的两个白色像素,和最右边两个黑色像素,这样就找到了边的两个端点。
然后再把左边的端和右边的端进行比较,这个比较就能告诉我,是应该多听我一点还是多听别人一点?
这里数学不展开,原理是相似三角形,知道了 edge 的长短,就能知道上下的长度是多少。
就能知道每个点应该去真实渲染采样的时候,应该往上移还是往下移。
对于每一个点都这么算,所有点都算出就能得出下图右边的结果。
这个算法没有多绘制东西,就能把可以接受的效果绘制出来。
FSAA 是一个非常实用的算法,现代的显卡基本集成了这个算法,速度非常快。商业级游戏也在用。
一倍的信息,只对数据进行观察和处理,也能做到反走样的处理。
TAA - Temporal AA
这个世界,过去渲染了很多帧,只要在过去的帧找到像素的对应,把信号拿过来进行加权平均,实际上也是进行了信息的 blending。
很多现代引擎的算法里,用到了很多 Temporal 时序上的数据。
这就引入了一个概念:Motion vector。
当前像素看到的每一个点,通过 Motion Vector,找到上一帧的当前像素点的信息,然后混合。这里比较复杂,渲染要做准备。
不需要做更多的采样,只在时间轴上找数据。
上图左边可以看到,不动的话,Motion Vector 部分就是黑的。如果人在动或者云在动,Motion Vector 就是有值的。
Blending 的权重取决于当前帧的置信度和过去帧的置信度,如果一个高速的物体在运动,我们会更相信当前帧的结果。不会过度相信过去帧的结果。
如果不动的话,两帧的权重是差不多的。运动的话,当前帧的权重会更高一点。
TAA 也是现代游戏非常主流的算法。
TAA 开启之后和没开的比较,像素会有小小的错位,因为相机一直在动,但 TAA 一直在用过去的图像进行 Blending,因此这也是 TAA 的一些问题。
TAA 做不好的时候也会出现一些残影,这是时序上比较的时候出的问题。