这里是底特律变人项目在GDC2018上关于其渲染技术的分享(之一),主要包含两个部分:
- 管线
- TAA
照例,对其中的技术要点做一个总结:
工作室简介:
- 索尼独占工作室
- 擅长交互式戏剧游戏
- 200+员工
大概3~5年发布一款新作
引擎的基本介绍:
- 针对PS主机做了大量优化
- 编辑是在maya中完成的
Heavy Rain:
- PS3
- 前向
- 2x的MSAA
Two Souls:
- PS 3
- 延迟渲染
- 伽马矫正
- PBR
- MLAA
Sorcerer:
- PS4
- 延迟渲染,最多5个RT
- 对材质表现做了优化(采用Cook-Torrance公式实现高光反射)
底特律的需求给出如上:
- 交互式戏剧
- 需要考虑性能
- 同时兼顾质量(废话)
游戏特点:
1.城市
- 夜景众多
- 室内场景多
- 支持下雨跟下雪
效果图还是很赞的
因为不是动作游戏,所以帧率要求会低一些,只要30fps(看看人家怎么挑选帧率目标的),可以用帧率来换表现。
另外一个要求是不要有加载界面。
针对需求做了一番整理后发现大部分的特性都需要用到GBuffer:
- 基于法线的深度偏移
- 多层材质(皮肤、雨水等效果)
- 存储在顶点中的自遮挡信息(不是太了解)
- 眼睛渲染
- 如果将这些特性都塞到RT中,可能会导致RT数目超标
- 同时不同材质对RT的需求不一样,设计上会存在冲突
- 延迟渲染在一些复杂逻辑下(比如上面列的两点?),可能性能会更差(没有展开介绍)
基于这几点考虑,最终还是想着走回前向:可以针对不同的特性,采用不同的渲染逻辑,从而规避了特性冲突的问题,同时由于分门别类的渲染,RT的消耗也不会太过分?
自研引擎主要做了这些改动,今天主要聚焦在前两项。
前面说了,前向的好处(主要还是Forward+的好处)是灵活高效,主要有三种渲染管线
Tiled Rendering的基本介绍,值得关注的有两点:
- 带宽会更节省一点(谁不是呢?)
- 不支持半透(是因为只支持depth plane附近的像素,没有考虑半透在深度上的分布可能性)
Forward+:
- 添加了tile的深度范围,剔除了部分范围之外的光源
- 支持半透(因为有了深度覆盖范围,所以分布在depth plane之外的半透物件也可以被考虑到了,就不知道这个覆盖范围是咋计算的,从上面描述来看,近平面是取的相机的,远平面则取一个最大值)
- cluster的深度覆盖范围不是线性的
- cluster在深度上做了划分,避免了Forward+的tile归类方法
- cluster数目高于tile数目
之前有这么几个项目尝试了这个方案
数据结构包括三个buffer:
- 包含cluster信息的buffer,3D数组格式,每个元素对应于一个cluster,存储了第一个light的索引与light的数目
- 包含光照数据的buffer:一维数组,存储的是光源的基本信息,如类型、位置、颜色、衰减等,尺寸等于最大光源数目
- 包含光源的间接索引数据,用的是16bit的格式,尺寸与光源最大密度有关
下面看下Cluster数据怎么填充:
- 填充是通过CS完成的,大概位置在depth跟shadow pass之间(需要拿到depth来完成填充)
- 每个cluster需要跟所有的光源进行比对以得到关联性(这里列了一些具体的tips与技巧)
整个过程通过3个pass完成
- 计算每个cluster关联的光源数目
- 计算每个cluster中第一个光源的索引
填充每个光源的数据
cluster分为两层(目的是?)
看下填充消耗
针对上百个光源,大约花费1.23ms
在lighting的时候,会基于当前像素的位置与深度来实现cluster的查找匹配
第一次尝试之后发现shader复杂,用了过多的寄存器,导致性能下降。
之后通过精细化管理做了优化:
- 尽量使用标量寄存器,避免浪费
- 尽可能的实现数据复用(目的何在?)
- 在TAA开启的时候,降低阴影贴图采样的次数(PCF)
- 编译器循环采样2x4这里没看懂
- 远距离情况下,减少阴影贴图采样数目(常规操作)到1
- 增加depth pass减少shading浪费
- 将lighting从PS改成部分使用VS
- 尽可能的将IBL放到后续的延迟pass一次性完成(避免分cluster执行,毕竟是全局效果,浪费多)
这里看下优化细节,先是光照计算的优化:
- 一共四种类型灯光(projector应该是将某个光源按照贴图投影到场景里)
- 采样的话,有阴影贴图跟投影贴图两种
- 最开始的时候,需要通过一个4次的循环来对每种光源做单独处理
- 后面改成移除循环,一次性完成所有光源的处理(区别在哪里?)
原始循环的逻辑如上图所示
其中方向光阴影计算对寄存器(参数多,逻辑复杂)的占用高
将之从循环中提出来,从而不用循环的每一步都需要耗费这么多寄存器(是吗?)
光源的可见性计算逻辑:
- 基于portal/zone来实现
- 可以用于剔除掉不可见的光源
- 通过bit field存储
- 最后这个没看懂
基于剔除,这里可以增加一个提前退出的判断环节(shader消耗真的能降低吗?)
同样的思路,增加其他的剔除条件(看起来是逐像素的)
统合到流程里
这里应该是具体的可以提前退出的debug视角,只是不确定颜色代表的是啥意思
透明度的优化:
- 消耗很高,需要处理
- 对于玻璃来说,可以只计算IBL(效果不会有问题吗?)
- 粒子则做更狠(每个面片只shading中间点,类似于VS shading,通过SH存储的数据来计算,跳过其他光照计算,采用半分辨率渲染方案等)
头发处理比较复杂,性能问题也相对比较严重
首先是有个accumulation pass,采用1/16的分辨率渲染(会不会比较糊?)
这个debug view不知道显示的是overdraw还是啥
基于前面的透明度累加,再做一次绘制得到对应的depth数据(直接逐发丝获取depth肯定是不行的,逐面片呢?也会需要多层反复,所以干脆复用前面的数据,是个好想法)
还有其他的几个pass:
- 因为头发是双面可见的,因此分为前后方向的两次渲染
- 还需要一个motion vector pass(用于TAA?)
发现有些计算其实不分成cluster来处理会更好:
- SSR(全屏处理,跟光照没啥关系,其实大部分的后处理应该都是吧)
- IBL光照
- 这俩都需要法线跟粗糙度数据
debug的一些信息
镜子这里通常采用的是平面反射,即将场景额外绘制一遍,这里针对镜子的优化貌似是针对的cluster的数据填充(光源列表信息吗?),但是不是太明白其精妙之处(毕竟随着玩家视角的变化,会导致cluster划分的变化才是)。
这里针对平面反射倒是有一个没有尝试过的想法:
- 约束参与反射的物件,其中为每个镜子设置一个cubemap,远处就用这个cubemap遮挡
- 在镜子处渲染一个cubemap,运行时根据反射射线取用,不确定场景拉伸效果会否有问题,还需要实测确认一下。
- 在上述绘制好的静态场景上,叠加动态的物件,比如角色,从而每帧只需要承担角色的绘制消耗,即可实现此前每帧都需要重绘场景两次的效果。
体积光能跟着沾光,走延迟的话,是需要做全屏的ray marching的,做Clustered Forward的话,应该可以约束最小单位为cluster(是这样吗?)
同样的逻辑,贴花也是。
照这样看来,如果全屏的操作较多,但实际上只有部分区域需要的话,而且考虑到移动端的Tiled Chip架构的话,同样的效果下,延迟渲染确实不见得会是最佳的渲染管线了(延迟渲染主要的优势就是多光源支持成本低,假设通过精细化管理,收束住每盏灯的消耗从每个物件到只有表面的一层可见像素,可能延迟渲染的这一点优势就被很大的抵消了,不知道后面有没有详细的对比数据)。
这里是所有光照的计算消耗:
- 124盏灯,32个IBL(咋这么多,都是啥?)
- 总体消耗加起来是15.42ms(60帧有望,不知道还有没有其他消耗,也不知道同样的效果下,延迟渲染消耗咋样)
后面还可以进一步优化其性能:
1.减少cluster填充的时间消耗
- 对深度的分布做更为细致的处理,目前处理还是稍微有点简单
- 在cluster填充的时候,就可以做一些剔除,削减后续的计算消耗
- 针对VR可以做数据复用
锯齿问题的产生不能想着通过提升分辨率来解决,此外,HDR跟PBR会加剧锯齿问题。
据此问题的解决主要有三种思路:
- 增加采样数
- 后处理,如MLAA
- 在着色的时候处理(?)
多采样比如MSAA算法,也是有消耗的,shading消耗增加,同时只能处理形状锯齿,高光锯齿没有办法解决。
后处理则主要有如下三种,今天要介绍的TAA是基于UE的TAA算法优化而来。
这里对TAA的算法实现思路做了总结:
- 每一帧会对投影矩阵做一个随机偏移,使得画面在垂直于深度方向有一个抖动
- 将多帧的结果累加到一起
- 需要通过运动向量来追踪到某个像素在上一帧的位置,上面这个累加最理想的状况下是只发生在相同的世界空间位置,拿错了就会导致残影
- 为了把错误的数据筛选出来,还需要一套合理的算法
TAA作用在物件绘制完成之后,出于结果稳定性考虑,需要放在后处理之前(?下图是UE的渲染pass排序,可以看到,TAA是放在DOF之后的,为啥DOF跟Motion Blur会有前后的差别呢?),因此对于Bloom、DOF以及运动模糊等后处理带来锯齿是无能为力的
对于一些需要特殊处理的后处理,比如SSR以及体积光等需要通过多帧累加来实现降噪的应用情景,就特别适合使用TAA来提升品质。
8步抖动(每8帧重复一次抖动的offset)
TAA的运动向量其实是通过在shader中对前后两帧的MVP矩阵的变换结果作差得到的,由于只能存储一层数据,因此正常情况下我们其实是只记录不透明物体,但是如果需要的话(比如前面的半透效果过于重要,或者虽然是半透但实际上是按照不透绘制,比如前面说过的头发),我们也可以针对这类物体做单独开关,记录下透明物体的运动向量,这样带来的后果,就是半透背后的不透物体的运动向量数据就无法保存了,因此是否要开启,需要评估其中的得失。
因为前面的计算逻辑是两帧作差,所以为了能够得到准确的运动向量,除了需要记录前后两帧的MVP矩阵,还要同时记录前后两帧的动画数据:
- 布料、蒙皮数据
- 植被受风力影响的数据
- 顶点动画数据
为了得到有效数据,这里用了三种方案:
- 来自于UE TAA的相邻数据剔除方案
- 基于深度差异的剔除方案
- 基于速度相似性的剔除方案
相机只是旋转的情况下,前后两帧画面的像素基本上是一一对应的,但是如果移动的话,就会出现同一个物件在屏幕空间覆盖范围的变化,导致对应关系被打破(真的会有问题吗?为啥只有skin需要做这个处理?),所以在相机缩放(拉近拉远)的时候,需要相应降低TAA的力度(对应帧在累加时的权重),避免效果的异常。
整体消耗为1.14ms,确实低(不知道SSR消耗多少,推测可能高过TAA)。
TAA会引入一个问题,就是雨雪效果可能会被TAA模糊掉,这个特效消失的问题在UE的演讲中有对应的解决方案。
对于雨打在物体表面上的效果,则可以基于雨水的法线强度来调整TAA的强度,从而避免模糊掉不必要的细节,下面几张图做了生动的解释。
TAA的问题:
- 部分后处理会因此存在异常(比如DOF跟运动模糊,就会出现效果错位出圈的问题)
- 深度数据其实不需要抖动(不知道抖动了是否有其他的副作用?看下面的描述是,抖动了,但是却没有做TAA累加,所以导致颜色跟深度不匹配吗?)
- 轮廓线等正常的边界会被当成锯齿模糊掉
DOF的问题如何解决呢?
- 对深度也做TAA累加(从而解决颜色跟深度不匹配导致的DOF的抖动效果?)
- 通过对COC的尺寸进行缩小来减少泄漏(不知道具体是啥表现。。)
这里给了几张图,要全屏播放仔细观看才能发现问题,上一张图脸部边缘要光滑一些,这一张图边缘则像是有绒毛之类的效果,看起来应该是远处的DOF借了脸部的数据导致。
正常的DOF会进行深度剔除,模糊的时候,会刨除掉深度差异大的数据,但是这个算法在TAA对深度做了抖动之后,就会存在问题,将本来不应该采到的数据也采进去了,从而导致了类似的问题。
同样的,Motion Blur也是只对背景模糊,不对前景模糊,所以问题是相似的,解法也一样。
这里也介绍了运动模糊的一些细节:
- 一些模糊算法,其实都可以降分辨率来执行(其实DOF也是可以的吧)以提升效率
- 运动模糊覆盖的区域需要排除掉深度不连续的地方(难道不是前面把深度纳入TAA就行了?或者说,纳入TAA之后,依然会被认为需要参与到Motion Blur?所以Motion Blur需要单独处理?)
这里给了效果的对比图。
- TAA其实是可以很好的与checkboard rendering模式结合起来的,多帧混合+空间降分辨率,正好实现性能与效果的兼顾
这里解释一下,checkboard rendering(部分文章会简称为CBR)是彩虹六号的技术分享中介绍的一种性能优化技术,这种技术通过减少单帧需要shading的像素数目(采用类似MSAA的shading pattern,不过不同的是,MSAA是作用在单个像素的多个sample上,而这里是直接作用在像素上),之所以叫checkboard rendering,是因为真正参与shading的像素是类似于国际象棋棋盘上的黑色(或白色),而白色(或黑色)像素则是通过算法插值得到,具体可以参考【GDC 2016】Rendering Rainbow Six Siege。
- checkboard rendering在相邻像素之间的jitter pattern需要保持一致
- 4k分辨率对后处理来说是一个很大的性能负担, 所以这里的做法是将checkboard的resolve放在后处理之后
这里应该有个视频
在部分场景下,TAA的效果还有所不足,比如高光表面
着色锯齿通过增加shading次数的方法太耗了
通过对法线进行filtering(给了参考文章)来降低shading噪声,相当于移除高频细节只保留低频数据。
这里还有一个更高效的版本。
效果测试发现还是挺不错的,雨水的细节可以得到较好的保留。
这里给了效果比对图片。
除此之外,还有其他的一些可以借助时间的累加来提升效果的特性:Shadow(PCF)、HBAO(周边多像素的射线发射),SSR,SSSSS,体积光等
蓝噪声特点介绍:
- 在能量上不存在聚集的突刺,具有最小的低频组件(?)
- 参考Inside的渲染介绍
这里针对蓝白噪声做了比对不知道结论是啥。
之前有人提出过通过8taps的泊松圆盘pattern来控制shadow map的采样点(相邻)偏移以得到更好的性能与效果。
这里提出了一种新的方法,直接基于一张16x16的蓝噪声贴图数据来对相邻像素的采样偏移进行旋转,不知道跟前者比较起来收益在哪?
这里是具体的执行细节。
展示了效果。
同样的,对5S也描述了其实现细节,不过之前对5S的实现方案了解不多,这里就不展开了,后面有需要再回来补充。
HBAO的,通过对tracing射线的方向做随机处理,叠加TAA来提升效果。
这里说,按照上面的说法,是不能得到预期的效果的(为啥?)
这里采取的策略是增加一个grainy blur pass来优化HBAO的效果,Grainy blur是一种高效的模糊算法,只采样若干个相邻像素(极端情况下也可以只采样一个)做混合,通过uv抖动+TAA来提升品质,具体可以参考毛星云的高品质后处理:十种图像模糊算法的总结与实现。
这里是具体的效果对比图
实际效果比对。
SSR基于寒霜15年的talk与Surge中的SSR方案实现