123456806 发表于 2021-1-7 09:35

Unity Temporal AA的改进与提高

最近在快乐的研究后处理特效时,经常需要通过Noise Random实现特效的平滑,如SSAO,SSR,PCSS等,最终再通过Temporal Filter实现降噪,然而Unity的TAA在许多时候过于闪烁,效果并不是很理想,因此我们决定对Temporal AA进行一些修改和提高。
首先,我们需要了解一下,Temporal AA的实现原理是什么样的。首先Temporal AA需要用到Motion Vector RT,Motion Vector是一个RGHalf(或RGFloat)的双通道贴图,储存的是当前像素点与上一帧的区别。要得出这个结果,我们就需要获得像素当前的世界坐标以及像素上一帧的世界坐标,这个实现是非常简单的:
可以看到,这一步式子其实就是通过上一帧的世界坐标,转换到上一帧的屏幕坐标,然后使用当前屏幕的坐标减去上一帧的坐标,就可以计算出Motion Vector信息了。
Temporal AA的原理是,通过Motion Vector,找到上一帧的当前像素点的信息,然后混合,因为上一帧信息有很大概率还会出现在屏幕内。同时,对投影矩阵进行半个像素距离的偏移,使得每一帧的投影矩阵与上一帧都不一样,也就相当于混合了多次采样的结果,实现超级采样,因此Temporal AA也称Temporal Super Sampler,通过抖动投影矩阵的做法被称为Jitter。
Jitter能带来非常不错的提升采样率的效果,然而Jitter同时也会给某些像素点带来闪烁,这是因为有些位置比较特殊,如粗糙度较低的水面或技术,就会因为一点点的投影偏差导致BRDF 高光运算结果截然不同,同时为了防止出现拖影(Ghosting),在融合时必须进行差异判断,放弃掉那些颜色差异大的像素点,最终结果就是屏幕上会有部分顶点在持续闪动。在研究中发现,Unity的TAA在这方面做得并不是很好,目测是为了屏幕保持清晰,降低了对色彩变化的容忍程度,最终导致了闪烁的发生。
之前在使用Unreal Engine的时候,发现其TAA平滑效果略高于Unity自带的TAA,效果大致如下:
感谢CGBull同学提供的盛世美颜的场景
本来我们计划,直接把Unreal的TAACopy过来,然而突然发现Unreal的TAA其实为了消除噪点,采用了过滤层,Tonemapping层等,这也导致了画面变的比较糊,在玩家眼里就是“画面比较油腻”。而这样的情况我们也是不想的。
那么最后只能自己做一些改动,融合两种TAA的效果的优势,并且进行一些改动,使抗锯齿效果更上一层楼。从游戏的角度来说,当画面静止时,闪烁变得非常不友好,比如FPS游戏中的狙击手,如果看着某个像素点拼命闪烁,那么狙击手的判断将会受到很大的影响,但如果玩家的屏幕在高速移动,那么实际上一点点的闪烁就变得无足轻重了。
闪烁与拖影是Temporal Filter的两大敌人,而且这两个敌人一般很难同时解决,如果色彩比较容忍度高,那么拖影往往很难避免,同时如果色彩比较容忍度低,屏幕就会因为Jitter出现难以解决的闪烁。而这个“容忍度”一般是通过与上一帧进行色彩的比较得出的。综上所述,我们最后计划通过Motion Vector Texture的结果,判断像素点是否在高速运动,从而决定色彩差异的容忍程度。
进行色彩过滤的第一步,通过比较深度选择当前像素周围一圈内最靠前的像素,之所以要这样做,是因为如果直接把颜色通过Motion Vector进行混合,极容易出现“像素点在下一帧被遮挡住”这样的情况。所以我们需要尽可能的选择深度最小的,距离摄像机最近的像素点作为采样点,实现如下:
在这一段代码中,对周围的一圈进行了采样,然后筛选出深度最低的采样点的UV并返回。然后接下来将使用返回的采样点获取Motion Vector:
在成功获得了过滤了的Motion Vector信息以后,还需要当前像素的处理过的色彩信息。那么Unreal Engine的TAA在这里的做法效果比较不错,通过采样了像素周围的一圈,总共9个像素的色彩,计算每个像素点的色彩值,这样做的目的是为了构建一个过滤和平均化的操作,当某个像素点的亮度远高于周围时,需要将其限制在可控的范围内,否则容易出现高光亮度较高的物体闪烁的问题:
获取周围一圈像素的色彩
对色彩进行ToneMap消灭掉过亮的点,并生成平均的颜色
在获得了已经平均化处理的色彩以后,我们就要开始确定色彩的Bounding Box了,为了防止拖影,当色彩与上一帧差距过大时,Shader需要“理智”的放弃掉上一帧的色彩,根据实际测试,动态的像素点更容易出现色彩变化(漫反,高光,反射,动态贴图等),所以这里首先使用motion vector判断像素移动速度,然后制定一个颜色变化的范围,最后通过色彩的平均值的平方和平均值算出色彩的范围:
得到的是min和max两个值,这两个值之后会被用来限制上一帧的色彩,使得上一帧的色彩不会与当前帧的色彩差距太大。stddev计算了颜色的差,也就是微分值,然后根据微分值决定范围,微分值越高,即差异越大时,容忍度越高,微分值越小,即差异越小时,容忍度越小,使Temporal Blend具有弹性。
最后一部分相比上几部分要简单得多,读取上一帧的色彩,并将其限制到上段代码求出的范围中,然后根据脚本传入值进行混合。值得一提的是,在混合之前我们还执行了Sharpness操作,这是因为前边的混合操作会使画面变得模糊,有时美术希望画风变得清晰,犀利一些,这时就需要时当前色彩在所有色彩中占的比重高一些,因此就可以通过公式,使当前像素与周围像素的差异更大:
官方的Temporal AA是使用MRT同时返回两个颜色,这里由于我们使用的是自己的渲染管线,因此只返回了一个SV_Target,除此之外与官方脚本没有任何区别,那么到此为止,Temporal AA的修改已经结束了,题图就是修改后的在内置管线中的表现,可以看到,清晰度还是相当高的,而且我们放心的把Jitter值调到了1(默认是0.75),闪烁情况也控制在一个比较可以接受的范围内。当然TAA的效果好坏还取决于其与其他后处理和绘制的配合,之后的文章中我们将讲解如何在自己制作的管线中加入Temporal AA。关于制作管线的文章,链接如下:
至于为什么要用这么灰暗看起来不舒服的场景……其实主要是为了挑战TAA在极端条件下的表现。开源暂时也没有完成,之后会和管线内其他东西一起放到Github上。
页: [1]
查看完整版本: Unity Temporal AA的改进与提高