刚刚发布的Unreal Engine 4.14中,其第一个重要的特性就是增加了在VR开发中对Forward Shading的支持。我们都知道在很多方面Deferred Shading都优于Forward Shading,它也是时下主流游戏引擎使用的渲染架构,而仅有在移动等设备中由于缺乏MRT的支持时才不得不选择Forward Shading,那么为什么Unreal Engine 4会在VR开发中选择使用Forward Shading?

blogassets-2016-november2016-nov15_4_14_releasenotes-414release_banner-770x250-09e89b17f26d1d81a9b75ede4ddc698cb8dd1cfe

“The forward renderer supports both multi sample anti-aliasing (MSAA) and temporal anti-aliasing (TAA). In most cases TAA is preferable because it removes both geometric aliasing and specular aliasing. In VR, the constant sub-pixel movement introduced by head tracking introduces unwanted blurriness, making MSAA a better choice.”

这里指出了Deferred Shading的其中一个重要的缺点,那就是不支持MSAA。MSAA一般是由硬件支持的反走样(anti-aliasing,AA)技术,它在光栅化阶段在一个像素区域内对每个像素使用多个深度采样值,但是每个像素内的这些深度采样值共享一个着色计算,即是每个像素仍然只执行一次fragment shader的计算,然后计算的结果被复制到每个子深度采样点上,这样一个像素内深度测试失败的子采样点将不会包含颜色信息,从而能够更使最终的颜色过渡更平滑。MSAA与SSAA的不同就在于MSAA的每个像素只执行一次着色计算,因此比SSAA具有较大的性能优势,但是也因此MSAA不能处理由于着色计算中对函数(如高光)采样不足导致的走样(如Shader Aliasing),但是由于人眼对于几何边缘的敏感度更高于对颜色的敏感度,因此MSAA是一种相当受偏爱的技术。

然而在Deferred Shading中,为了避免无数被深度测试剔除的像素的着色计算,它将着色计算从光照方程中分离出来,并将它延迟到深度测试之后使用单独的通道进行计算,通过深度测试的那些像素的材质参数被写入到G-buffer中,这样Deferred阶段就可以使用G-buffer中的材质数据仅对那些通过深度测试的像素进行计算,从而大大增加渲染性能,这也就是Deferred一词的来历。然而,MSAA被集成于光栅化阶段,也即是生成G-buffer的阶段,MSAA输出的是比屏幕分辨率更高的输出辨率,因此4xMSAA也就意味着4xG-buffer,这大大增加了显存的性能开支,更重要的,由于Deferred阶段读取这些G-buffer数据带来的带宽占用和内存读取延迟。

所以,Deferred Shading更偏爱也是目前主流的反走样技术是TAA,即时间反走样(temporal anti-aliasing),TAA的思路就是每帧还是执行一个正常的对每个像素执行单次采样和单次着色的计算,但是它在每一帧对摄像机的位置在屏幕区域内执行一个在一个像素尺寸内的抖动操作,这样如果将邻近的多个帧的数据混合起来,就相当于对每个像素执行了多次深度采样,如下图所示。TAA相当于SSAA的效果,它将多个采样点从单帧内的空间分布转化到时间上,使得每一帧绘制并不会增加多次采样导致的性能开支,又能达到像SSAA一样的图像品质,因此TAA是时下相当受偏爱的一种在Deferred Shading渲染架构下的反走样技术。

taa

然而TAA技术看似美好,其背后仍然存在很大的问题,这些问题从宏观角度来讲可以分为两类:即模糊(blur)和重影(ghosting),从原理上讲它们其实可以归为一个问题,即ghosting消除到一定程度就是blur,然而由于TAA的天生特性,这种模糊基本上是无法绝对消除的。

为了混合在一个像素内的多个子采样点的结果,TAA需要对一个历史缓冲进行采样,将历史颜色和当前帧计算的结果以一定的比例进行混合,当前帧颜色混合的比例越大,则越快逼近最终结构,但是品质更差,反之逼近过程更慢,但是品质更好。但是对历史颜色的获取不是一个例如从数组中获取某个数据的问题,而是一个对纹理进行采样的问题,因为我们不可能在下一帧得到一个和上一帧完全一模一样的位置,所以只能使用纹理采样技术,而纹理的采样涉及过滤(filtering)的过程,它是对采样位置周围一定范围内的像素的值进行加权混合的结果,所以每次混合计算每个像素融合了周围比如4个像素的颜色结果,而这4个颜色值则混合了来自它们周围4个颜色的混合结果,以此类推,随着时间的推移,TAA中每个像素会包含周围很大范围内的颜色的信息,造成了严重的模糊现象,如下图所示。

untitled-2

这种模糊效果可以通过增加当前帧颜色的混合比例来减少历史颜色的比值而有所缓解,例如下图所示,或者一些引擎使用锐化过滤器(sharpen filter)来提升邻近像素的比重,但由于TAA历史累计以及颜色过滤的特性,本质上这种模糊是不可绝对消除的。

untitled

在这个历史混合过程中,如果每个像素上一帧和当前帧没有发生太多变化,例如画面完全是静态的,则这种累计过滤结果仅会导致模糊现象。然而如果屏幕颜色发生了变化,例如物体移动,则当前帧和上一帧的颜色信息发生了完全的变化,如果我们不能够区分一个像素内多个子像素之间的这种绝对变化,而仍然将它们混合,则会产生严重的重影现象,即一些物体内的颜色被混合近另一个物体内,如下图所示。

ghosting-2

关于重影及模糊相关的问题及弱化方案,请参考《游戏引擎全局光照技术》第4章的内容,本篇仅简单讨论TAA模糊的问题。这种模糊现象在VR开发中会显得更加严重,因为VR头盔频繁的移动,以及VR相对普通屏幕对清晰度的更高要求都会进一步放大TAA的模糊问题,所以Unreal Engine 4在最新的版本中针对VR使用了Forward Shading。

那有没有什么更好的方案用于在Deferred Shading架构中替代TAA,这里笔者能够想到的就是Nvidia最新的AGAA(Aggregate G-Buffer Anti-Aliasing)技术,Nvidia已经将AGAA技术实现在Unreal Engine 4(还没有在UE4主分支)当中。与TAA的思路不一样,AGAA并不累计历史颜色,因此不存在TAA的历史累计混合问题,AGAA使用MSAA的思路,在同一帧内使用多个子采样点,例如8x,由于这在Deferred Shading会导致8x的G-buffer,所以AGAA是利用pre filtering的思路将这8x的材质参数pre filter成2x(或更多,但通常2个足够)的G-buffer,因此称作为Aggregate G-Buffer,这样8x的深度采样只输出2x的G-buffer,这样就减少了G-buffer的内存占用以及导致的带宽占用性能问题,如下图所示。

agaa-1

然而AGAA对硬件是有要求的,聚集几何缓存数据生成阶段的主要目的是将n个子采样点的数据过滤到c个(c<n)聚集中,这需要用到目标无关光栅化(target independent rasterization)技术,这是NVIDIA的NV_framebuffer_mixed_samples扩展提供的一个功能,它可以对深度测试使用更高的采样率,而对输出颜色目标使用更低的分辨率,在每个片元着色器仅输出到c个颜色目标中的一个,这通过一个目标覆盖的位掩码设置:NV_sample_mask_override_coverage。

AGAA适用于Deferred Shading,同时没有时间累积混合的问题,它可以产生媲美SSAA的品质,因此在当前TAA对VR极度不友好,Forward Shading又由于众所周知的性能问题下,AGAA或许未来会有一定的舞台。

然而AGAA也是有缺点的,它的主要问题是由于Pre filtering要求每个像素内的材质类型必须一致,否则pre filtering的结果必定是错误的,这在当前PBR主流的光照计算中,场景中大部分物体都可以使用统一的一套材质参数表述,因此还是能够适应大部分场景,少部分特殊材质类型可以使用单独的渲染通道来解决。另外,Pre filtering还要求光照方程中能够被Pre filter的参数必须是相互独立的。这两个问题是AGAA今后优化发展的重点方向。

本篇仅就Unreal Engine 4引出的TAA的模糊问题的原因及问题展开相关的分析讨论,关于本篇的全部详细内容都可以在《游戏引擎全局光照技术》试读章节中找到。