【GDC2018】The lighting technology of Detroid:Become Human

今天要学习的是《底特律变人》在GDC2018上分享的他们的渲染技术,按照管理做一个总结:

  1. 所有的光都是有衰减有位置的(包括方向光),场景会被分割成一个个的SceneZone,每盏灯会标注需要影响哪些SceneZone来优化性能
  2. 针对一些特写镜头,设计了Close-up的light/shadow
  3. 阴影用的PCF,同时采用了Shadowmap Atlas,可以根据距离选择分辨率,以自适应的方式调控阴影的效果
  4. 阴影还做了cache,点光可以只更新单一的face,方向光的阴影是CSM方案,通过通过TAA来优化锯齿表现,最多四级(大部分情况下只需要两到三级),每一级是1440分辨率,depth是16bits的格式,通过自适应的算法来调整Shadow的voluem
  5. 距离稍远的光源会转成静态阴影,只需要64分辨率即可,最多可以支持1024盏静态阴影灯
  6. 针对近景处,采用了单独的shadowmap实现contact/self shadow,覆盖附近10m范围内的部分物件(动态物件,标注为需要绘制的物件)
    7.总体阴影内存消耗为276M,每帧只需要更新1520个shadowmap,每帧花费为1.53.5ms
  7. 通过存储min/max的tile depth,针对体积光的漏光问题做了处理
  8. 间接光采用的是纯粹的probe方案,通过virtual offset来解决其漏光问题,同时数据结构是基于稀疏八叉树来存储的,总体内存消耗比较低。
  9. 采用的GI方案可以通过烘焙多套数据的方式实现GI的动态变化(TOD、开灯等)
Page 1.
Page 2.

内容分为三部分:

  1. PBR的设计(这里先跳过)
  2. 直接光照的设计
  3. 间接光照的设计
Page 3.
Page 4.
Page 5.
Page 6.
Page 7.
Page 8.
Page 9.
Page 10.
Page 11.
Page 12.
Page 13.
Page 14.
Page 15.
Page 16.
Page 17.
Page 18.
Page 19.
Page 20.
Page 21.
Page 22.
Page 23.
Page 24.
Page 25.
Page 26.
Page 27.
Page 28.

所有的光源都是punctual的(有位置的),按照光源的介绍

所谓的punctual lights指具有位置(location)的光,区别于平行光。但是punctual lights没有形状、尺寸,和真实世界的光源不同。术语"punctual"和守时没有关系,而是来自拉语punctus,意思是"point",指从单个位置发光的光源

平行光应该被剔除这个行列,底特律的平行光跟我们日常理解的平行光是不一样的:

将平行光设计为有位置的,覆盖某个volume box的,且光强会随着距离而衰减的

Page 29.

直接光照:

  1. 衰减是默认按照距离平方来计算的
  • 美术同学可以手调参数,通过这种方式伪造一个大尺寸的光源
  • 通过调整衰减半径可以控制性能,虽然会导致能量守恒规律的破坏,这里给出了一个参考的方案(应该是有一些技巧在里面)
  1. punctual光源在高光计算的时候,得到的光强会过高
Page 30.

面光源看起来可以提供一些帮助,但实际上因为性能与开发周期的问题,并没有用上。

最后采取的策略是调整材质的粗糙度来解决上述问题(高光问题?)

Page 31.

为光源设计了一个自定义近平面的能力,可以实现一些特殊的需要。

通过给每盏灯标注其影响的Scene Zone来优化消耗。

Page 32.

为了优化一些特写镜头的表现,这里设计一套close-up lighting的方案,大致意思就是针对镜头的移动曲线,对场景的布光做特殊的设计与处理,使得效果跟电影的效果相匹配。

主要的成本就是人力投入高。

Page 33.

close-up lighting是一套单独设置,单独生效的布光逻辑,在启用的时候,会替换正常普通的光照排布,其阴影也是单独设计的,同时还会对GI产生贡献。

Page 34.
Page 35.

效果差异还是挺明显的,主要作用是优化角色光照。

就是不知道这类的场景多不多。

Page 36.

阴影采用的是PCF,PCSS性能消耗有点高(寄存器压力)最后只用在了某些局部场景。

Page 37.

阴影贴图采用了Atlas方案,类似VT,目的是根据需要调整Shadowmap的Size,同时避免频繁的分配与释放。

这里会根据到相机的距离来决定每个光源的阴影贴图尺寸

Page 38.

Shadowmap也做了cache,只会在场景物件有更新的(动态)的时候需要刷新,对于点光来说,可以逐个面来决定要不要更新,而不需要针对六个面做同时更新。

此外,通过对近平面处的阴影做一定的调整来提升其精度(不知道具体是咋做的?)

Page 39.

方向光用的CSM:

  • PCF+TAA
  • 基于抖动等方式实现等级之间的过渡
  • 最多四级,每级最多1440分辨率,精度是16bits,大部分场景只需要两到三级
  • CSM的划分是程序化实现的(不知道是什么算法)
Page 40.

为了优化阴影的消耗,这里会在一定距离下停止阴影的更新,转成静态阴影:

  1. 用了一张2048的贴图,每个静态阴影只占用64,可以达到最多1024盏光源有静态阴影
  2. 方向光的静态阴影贴图是8192,覆盖整个地图
Page 41.

近景处的阴影还需要一些细节处理:

  1. 添加contact & self shadow
  2. 额外增加两张shadowmap,每张尺寸为1536
  3. 由美术同学控制那些物件需要参与到这两张贴图的绘制中来
Page 42.

参与close-up shadow的物件选取逻辑:

  1. 10m半径内所有被标注为需要参与绘制的物件
  2. 蒙皮的模型?

(光源视角下)这类shadow的远近平面需要匹配上对应阴影的覆盖范围

Page 43.

处于frustum之外的投影物则直接投影到近平面上(远平面的说明超出承影范围了,不用考虑)

Page 45.
Page 46.

角色阴影的品质看起来确实有很大的提升。

Page 47.

这里是整体的阴影贴图的内存预算,总计消耗约276M(作为对比,UE的VSM默认有两张16k*4k用来存放vsm page的大RT,总共512MB)

Page 48.
Page 49.

整体性能:

  • 每帧平均需要更新15~20个shadowmap,这里没有做数量限制,所以极端情况可能会飚得非常高
  • 前面展示的视频中,每帧花费1.5~3.5ms(看起来是可以接受的,当然,这里是PS主机)
  • Close-up Shadow的花费则跟具体的场景、视角有关,可快可慢,当场景中有树木这种需要做大量alpha test的物件的时候,消耗会高一些(为啥?)
Page 50.

体积光的渲染,采用的是Unified volumetric lighting方案(给了参考文献):

  • 针对光照的覆盖深度做了适配
  • 使用checkboard rendering方案
  • 加了TAA降噪
  • 可以受到直接光或者间接光probe的影响
  • 在烘焙GI的时候,需要考虑fog的影响(没理解错吧?这个具体咋做),以实现对多次散射的模拟
Page 51.

当某个物件比较细,尺寸小于两个采样点的间距,就有可能导致漏光

Page 52.

这里给了解决方案:

  • 每个tile存储场景的最大最小深度
  • 在进行逐射线采样的时候需要判断当前点是否是已经被遮挡了
  • 在采样的时候添加一个深度的偏移(否则就会出现上图中间小图这种边缘模糊的效果)
Page 53.
Page 54.

路灯都是体积光实现的,17盏灯,大概1.8ms

Page 55.
Page 56.
Page 57.

之前研发Beyond:Two Souls的时候采用的是上图描述的方案,静态物件跟动态物件做了分别处理,但是研发底特律的时候希望用同一套方案兼顾所有的物件。

Page 58.

基于Probe的方案有如下细节:

  • 适合用于IBL(那就不只是烘焙Diffuse,而是要烘焙高光cubemap了)
  • 会在离线烘焙GGX NDF(需要通过重要性采样规避光斑),GGX是高光BRDF计算中的一项,取决于场景的粗糙度以及法线跟微表面法线的夹角,不知道这里突然提到这一点的目的是啥?
  • 美术同学最好能够控制,不知道这里说的控制是指摆放位置吗?
Page 59.

Diffuse Probe Grid的问题:

  • 漏光,暂时没有完美解决方案,有众多的人对此做了研究,这里给了两个有意思的参考方案
  • 插值的不规律:虽然效果影响不明显,但是在底特律这边会存在问题(啥问题?)
Page 60.

常见的解决漏光的方案是拒绝掉被遮挡的probe,但是这种做法会导致上图所示的瑕疵。

最终底特律采用了其他的方案

Page 61.

这里将probe数据用稀疏八叉树来存储:

  • 用一个volume来指定覆盖范围
  • 采用自适应的算法来控制probe的布局与密度
  • 会给美术同学一个工具来控制probe的布局:
  1. 密度zone(控制密度?)
  2. 在物件或者墙体周围,基于程序的体素化结果来调整probe的分布
Page 62.

八叉树上每个顶点对应于一个probe,也就是空间中的任意点,都可以找到与之匹配的8个probe。

漏光问题的解决是通过对probe的位置做一个(虚假的)偏移来实现。

Page 63.

这里也设计了一个probe attractor,用于将probe朝着这个volume聚拢,这里有两个作用:

  1. 提升probe的有效密度
  2. 将probe沿着墙壁布局,可以更好的应对漏光问题
Page 64.

这里还设计了一个probe repulsor(推开、拒绝),用于隔绝掉内部(或外部)的无效probe

Page 65.

再来看看偏移算法:

  • 可以解决绝大部分的漏光问题
  • 这个偏移是在离线烘焙的时候实现的
  • 会考虑原始的grid位置

上面的小图貌似没有很好的说明具体的偏移方案,只是描述了说当(绿色)probe距离墙面有一段距离的时候,可能会选择墙外的红色probe,这时候就会出现漏光;而左侧的红色probe,由于是紧贴墙面的,所以就不会有问题(所以是在运行时自动选择最近的8个probe来做插值吗?)

Page 66.

如果某个地方存在漏光(如何检测到?),就在这个地方对grid做细化分拆,并通过将probe靠近surface来规避。

当然,也有其他的规避方式,这是因为漏光比漏暗要更为明显。

Page 67.

这里给了一个例子来介绍通过修改颜色来对比效果,但是好像不是很能看出来。

Page 68.

当高密度跟低密度probe并存的时候,就会出现有的box的顶点并不能很好的匹配相邻边的密度,这时候会需要通过插值来补齐probe,从而保障下面的二叉树子节点都是满的。

Page 69.

如上所述,我们的稀疏八叉树可以大概用类似的二叉树结构来类比

Page 70.

我们存储的话,只需要存储上面的叶子节点(对应于8个corner的probe数据)的数据,每个节点存储8个probe,但是这种做法由于相邻probe的共用,会有大量的冗余。

Page 71.

这里尝试以3x3x3为一个单位来存储,冗余会少一点。

这里的疑问是,为啥不单个probe的存储呢?这是因为,我们需要拿到各个probe的相邻关系,直接存储单个probe的话,可能就不方便拿到其相邻probe的数据,而最终的计算则是需要拿到8个相邻probe做插值来得到结果的。

有没有其他的方式来优化或者表达相邻关系呢?这里有一个前提条件,那就是GPU没有指针的概念,相邻关系必须要通过数组的索引来访问。

Page 72.

Diffuse只需要低频数据,因此经常会使用SH来存储,这里使用的是2阶的SH,即每个probe只需要占用四个系数,此外,这里也提了一个Geomerics重建算法,不知道其作用是否是提升2阶SH表达的精度。

最终针对每个颜色通道,都需要一张RGBA 16F的贴图(这里的RGBA对应于四个系数),而总共场景需要24055个probe,按照前面的存储方法,大概需要105x105个节点,每个节点存储9个probe,因此总计是3MB的内存消耗。

Page 73.

在使用的时候,由于不需要剔除,因此直接使用硬件的线性混合算法即可。

Page 74.

而从世界坐标到采样节点对应数据的索引的转换,则是通过一个hash算法来实现,只需要O(1)的复杂度。

Page 75.

基于这种做法,还有如下的好处:

  1. 可以很方便的实现GI效果的切换,切换可以用于灯的开闭等开关的支持
  2. 可以实现渐变的切换,如TOD,窗帘的拉开等
Page 76.

这里给了一个演示效果

Page 77.

在切换的时候,需要注意一点,那就是需要避免Scene Zone之间数据的硬切,比如从室内到室外,解决方案是:

  1. 在切换的SceneZone中设计一段走廊
  2. 在走廊中的动态物件需要同时采样两套GI数据
  3. 基于到不同SceneZone的距离来加权
Page 78.
Page 79.

这里给了一个演示视频

Page 80.

大多数的静态物件都是出于同一个SZ中的,对于门窗等同时处于室内外的物件,则需要做特殊处理,同时采样两套(其实是不是可以根据相机位置,决定采样哪一套?)

Page 81.

需要手动标注这类物件

Page 83.
Page 84.

这里是结论

Page 87.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容