揭秘ADAM | 渲染与着色

发表于2018-02-26
评论0 2.7k浏览

去年由著名电影《第九区》导演Neill Blomkamp与他的团队Oats Studios 使用Unity 2017创作的《ADAM》系列非常受人瞩目。我们已经对其制作过程进行了一系列的揭秘。

不少观众很好奇如何实时渲染出影片中那些炫酷效果?今天是新年的第一个工作日,我们将承前启后,为大家继续揭秘ADAM中的渲染与着色。

 

作者介绍

本文作者由Made with Unity团队的软件工程师John Parsaie与Unity技术美术总监江毅冰共同完成。John Parsaie在《ADAM》电影中,提供了诸如次表面散射、透明后期处理效果、Alembic图形集成等功能。我们已经非常熟悉江毅冰了,在加入Unity之前,她就职于顽皮狗、皮斯克等知名公司,她参与制作的作品包括《神秘海域4》、《怪物大学》、《汽车总动员2》等。

 

布景:按帧分解

在实时《ADAM》系列电影中所呈现的令人震撼的效果,是在Unity中使用大量组件制作而成的。在这篇文章中,我们将只关注Oats Studios是如何实现这些让人印象深刻的效果的。所以如果你想了解在某一帧中,这些艺术家所使用的自定义着色器,以及Unity 2017.1实时渲染的更多信息,请继续阅读!

 

本文所分析的《ADAM 3》中的一帧画面

 

本文中我们将用RenderDoc打开《ADAM 3》的一帧画面,为你剖析视觉效果背后的一些内幕。

 

说明:ReRenderDoc是个十分实用的帧分析和图形调试工具。RenderDoc已经与Unity的编辑器集成。想了解更多关于RenderDoc与Unity的信息请访问:

https://renderdoc.org/

https://docs.unity3d.com/Manual/RenderDocIntegration.html

 

渲染G-Buffer

《ADAM 2》与《ADAM 3》都是使用Unity 2017.1的延迟渲染路径渲染的。这意味着所有不透明的对象都会被渲染到一组缓冲区中,而这些缓冲区就被称为G-Buffer或Geometry Buffer。G-Buffer包含了渲染管线下实现光照计算所需的所有数据。

 

左下方的G-Buffer和 alpha通道

 

通过设置多个渲染目标(MRT),对显示的每个不透明对象,都可以在相同的绘制调用中将以下数据写入到G-Buffer的所有四个组成部分中。

 

  1. 漫反射颜色RGB/遮蔽(Occlusion)A(ARGB32)

    几何体的“固有颜色”和烘焙环境遮蔽。


  2. 镜面反射颜色RGB/平滑度A(ARGB32)

    Unity支持镜面和金属PBR工作流程。但在内部,这二个工作流程的输入内容最后写入缓冲区的信息是相同的。这是为了统一相同着色模型下的PBR输入,后面将会用到这个模型。


  3. 世界法线RGB/未使用 A (ARGB2101010)

    更高精度的缓冲区会用来存储每个像素所处的世界空间法线或表面的朝向。这个信息对于计算光源照度至关重要。


  4. 自发光,环境RGB /未使用A(ARGBHalf)

    自发光和环境GI被渲染到这个缓冲区中。之后,这个缓冲区也在管线后期中被用来收集反射和累积光照。根据用户设置,此缓冲区设置为LDR或HDR格式。《ADAM》就是用HDR光照渲染的。


注意:在管线中,使用自定义着色模型的透明和不透明物品的处理方式不同。

深度缓冲区和模板缓冲区

在渲染G-Buffer时,场景的深度也被渲染到相应的特殊缓冲区中。对于实时图形来说,深度信息的存储十分重要,它对我们在将场景投影到二维空间的过程中或之后保持对象的三维感至关重要。重建像素的世界位置也很必要,这稍后在延迟着色中会用到。更重要的是,这是我们耳熟能详且深受喜爱的实时高级后期处理特效的关键组成部分。


深度缓冲区(左)和 模板缓冲区(右)

 

模板缓冲区与深度缓冲区共享着相同的资源。在根据像素受到渲染的内容分类像素时,模板缓冲区尤其有用。我们在之后还可以使用这些信息来区分像素,并选择要怎样处理它们。在Unity中模板缓冲区被用作光剔除遮罩。在《ADAM》中,它也被用来标记呈现出次表面散射(SSS)的对象。

 

次表面配置缓冲区

对于次表面散射,渲染器也会在G-Buffer生成期间将索引写入额外的缓冲区,该缓冲区之后会用于查找来自次表面配置信息的重要数据。这个缓冲区还存储一个表示散射发生次数的标量。

 

次表面配置缓冲区:(R)配置索引  (G)散射半径

 

如上所述,这些重要数据来自用户在编辑器创建的次表面漫反射配置信息。这些用户自定义的配置信息决定了漫射光在高度半透明介质中的散射方式。

 

次表面漫反射配置信息

 

我们也可以通过这些配置信息来控制正向散射和透射率。例如:光线在穿透耳朵和鼻孔时的效果。所有这些信息都会发送到GPU并在之后读取。

 

下一步

G-Buffer完成渲染后,复杂的场景几何计算就会被移到少数几个缓冲区上,在之后进行计算。这样做能使以后所有计算的性能消耗较为固定和可预测,这是因为光照计算在这时对于场景几何复杂性来说,完全是未知的。然而,在主要光照计算之前,还有一些关键的预备性步骤,这些步骤将在下面进行探讨。

  

环境反射光效果

通过使用G-Buffer中刚创建的数据,我们会对天空盒立方体贴图运行计算以获取环境反射信息。这个计算将粗糙度、法线、视角方向、镜面反射颜色等信息考虑在内,并且通过一系列的方程来从环境中产生符合物理规律的镜面反射效果。这些反射效果会被添加到自发光HDR缓冲区中。

 

环境反射光


阴影

到了这一步,几乎所有渲染器所需的初步工作都已经完成。于是,渲染器进入到延迟光照处理阶段,由阴影效果处理开始。

 

Unity在其定向光处理上使用了一种众所周知的技术,名为级联阴影贴图(CSM)。这个技术的原理很简单:距离越远,我们眼睛能看到的细节就越少,既然如此,为什么要把大量精力放在对计算机图形学中的远处细节的计算上呢? CSM便是基于这个事实,根据物体与摄像机的距离分布或级联阴影图的分辨率。


级联阴影贴图(左)和聚光阴影贴图(右)

 

在这个镜头中,定向光CSM实际上只用在环境几何体上,而画面中二名角色的光照则由一组聚光灯处理!有一些镜头是这样处理的,因为它给了Oats Studios的灯光师更多的灵活性,从而能突出镜头中的关键视觉效果。


屏幕空间阴影

 

我们还使用了一个名为“屏幕空间阴影”的技术,有时也称为“接触阴影”,它通过深度缓冲区中的光线步进来提供细节丰富的阴影。这种技术尤其重要,因为它能够捕捉摄影测量法构建的环境中颗粒状阴影的细节,这些细节有时甚至是CSM都无法捕捉到的。屏幕空间阴影和Unity的阴影贴图技术被一同用来“填补”漏光。

 

延迟着色

在以上步骤完成后,我们就有了足够的信息来对光照场景进行逐像素重构。

 

一次延迟光照处理中读取的所有信息

 

延迟光照处理过程将针对视图中的所有光照进行处理,每次都会将光照累积到HDR缓冲区。G-Buffer的内容是根据当前光照的信息(包括阴影贴图)来计算的。

 

在第一步,从深度缓冲区重建像素的世界空间位置,该位置后续被用于计算从表面点到眼睛的方向。这对确定依赖视角的镜面反射效果来说十分重要。阴影和其它光照信息(例如剪影等)也会被汇集成一个标量,用以计算衰减后的最终效果。接下来,所有表面数据都从G-Buffer中取出。最后,所有信息都被提交给我们的着色模型,这是一个基于物理的微缩焦双向反射分布函数(BRDF),进行最后的着色处理。

 

所有最终光照都会累积到不透明物体上,次表面对象除外

 

在这时,我们已经有了一个近乎完全着色的场景,但是那些白色的轮廓怎么办?如果你还记得的话,那些是我们在模板缓冲器中标记的用于次表面散射的像素,它们的着色处理尚未完成。

 

次表面散射

前面有简单地提到,次表面散射是漫反射光的散射和再现,多见于半透明介质中最典型的例子之一是皮肤。而且在所有非金属中都会有不同程度的次表面散射,只不过多数时候我们并不会注意到。

 

但是在实时计算机图形下,次表面散射究竟意味着什么呢?

 

散射距离比像素小(左) 散射距离比像素大(右)

 

这个问题的答案其实引出了一个更大的问题。上面的二个图都包含一个代表像素的绿色圆圈,以及位于其中心的入射光。蓝色箭头表示漫反射光线,橙色箭头表示镜面反射光线。左图显示所有漫射光线会在材质中散射,并在相同像素的边界内重新出现。由于几乎所有要渲染的材质都会出现这种情况,我们可以假设漫射光是从入射点发出的。

 

当渲染的材质对漫反射光线散射过多时,会出现问题,光线会重新出现在像素的边界之外,如右图所示。之前的假设在这样的情况下毫无帮助,我们需要探索更先进的技术来解决这个问题。

 

漫反射光(左) 镜面反射光(右)

 

遵循当前最先进的实时次表面散射技术,我们对光照应用了特殊的屏幕空间模糊。但是在此之前,我们必须确保将漫反射和镜面反射光照分离到各自的缓冲区中。为什么要这样做呢?回顾一下这些图表,你会发现镜面反射光会从表面立即反射,不参与任何次表面现象。所以我们至少应该在执行次表面光照估算之前,将它与漫反射光照分离。

 

在下图中,你可以更清楚地看到二种光照的不同效果。请注意,所有的镜面反射光都已与漫反射光完全分离,从而可以在完成左侧的辐射/漫反射缓冲区所需的工作的同时,不用担心损坏镜面反射的高频细节的完整性。

 

漫反射缓冲区(左)镜面反射缓冲区(右)

 

基于由自定义次表面配置信息创建并发送而来的扩射内核,屏幕空间模糊效果近似于这种次表面散射现象。

 

多个次表面配置信息可被用在不同的材质上

 

通过在计算结束时将漫反射光和镜面反射光相结合,我们便把之前的问题给解决了!像这样的技术在估算像素到达范围外的散射时是非常有效的。此时场景中的所有不透明对象现在都被着色了。

 

场景中的所有不透明对象都已被完全着色

 

接下来是渲染屏幕空间反射(SSR)、天空盒、屏幕空间环境遮蔽(SSAO)和透明度。下面你可以逐步观察这些过程的变化。

 

对SSR、天空盒、SSAO和透明度进行的渲染


运动模糊的重要性

运动模糊在影片中的确起到了关键的作用。虽然它提供了可以控制影片质量的另一个微妙维度,但对某些镜头来说它是一把双刃剑。

 

运动向量纹理(左)用运动向量计算的运动模糊(右)

 

当然,渲染运动模糊需要渲染器了解运动本身。这些信息通过首先渲染初步的运动向量纹理而来。这个缓冲区是通过计算屏幕空间中当前顶点位置和先前顶点位置之间的差值,产生用于计算运动模糊的速度。

我们还做了一些额外的工作,以正确地从Alembic流获得运动向量。

 

后期特效

应用后期处理特效之前和之后的效果

 

我们终于来到了最后的后期处理过程。在这一步中,最终色彩分级、ACES色调映射、晕影、泛光和景深合成到一起,生成一个接近完成的图像。但是,Marian的防护面罩去哪了?

 

Marian的防护面罩

在使用到该技术的行业中,处理实时图形的透明效果是一个老生常谈的问题。诸如景深、屏幕空间反射、运动模糊和环境遮蔽等效果的主干部分都需要从场景深度进行一些空间感知/重建,但是对于被透明物体覆盖的像素,要怎么处理呢?你将需要二个或更多的深度值!

 

首先将所有不透明的对象渲染到场景中,然后是一个针对透明对象列表所特有的自后向前的特殊正向的绘制向处理过程,将每一遍个绘制处理阶段混合到帧缓冲,但而不写入深度缓冲。这样做可以尽可能地规避这个问题,对于大多数对象,比如角色的眉毛或香雾,这样的处理效果还不错。

 

Marian的防护面罩带有一些不现实的反射效果,无AO,DoF不正确,无运动模糊

 

然而从上面的例子可以看出,我们不能在Marian的透明防护面罩上规避这个问题,因为这个防护面罩在影片超过一半时间中都有出现。针对这种情况,我们需要某种替代方案。

 

最终产品生成阶段的解决方案是将透明对象延迟到二个完全着色帧之间的合成阶段中进行渲染。正如前文所说,第一帧包含了除了防护面罩外的所有对象。在第一帧渲染完成后,G-Buffer和深度被放置到第二帧的第二个渲染阶段中,其中防护面罩被渲染为不透明对象。

 

防护面罩的透明处理被延迟到二个完全着色帧之间的合成阶段

 

在第二帧的第二个后期处理过程中,通过使用原始帧的G-Buffer和深度的内容,我们可以成功地获得防护面罩的正确的SSR、SSAO、运动模糊和景深效果。要使面具回到原始帧,只需根据防护面罩的alpha进行合成,这个alpha将会因为运动模糊或景深效果而被模糊。

 

应用和没有应用这个技术的画面。请注意右边预期的遮蔽效果

 

通过对Marian的防护面罩进行这样的处理,我们将它很好地整合到了画面中,如上面所示。你会注意到右图中有正常的SSR和AO。虽然这并不是透明问题的全方位解决方案,但是这种技术解决了原来的极端情况,并为透明对象提供了完整的后期处理支持。


画龙点睛:火焰

Oats Studios将室内的火焰系统运用得很好,完全提升了影片的画面质量。通过在时间轴中进行动画和排序,这些镜头光晕被添加到图像帧画中,产生最终的图像。

 

最后的着色帧以及准备添加在上面的镜头光晕 


成品效果

至此,你已了解我们所提及的所有东西是如何在运行时进行渲染的。


Marian拿着石头靠近她的人质哥哥

 

总而言之,图形团队为适应制作需求做出了一些有趣的选择,而逐帧分解不仅是理解这些选择的极好方法,也是一个可以从中获取有用信息并应用到你自己项目的宝库。

 

未来展望

Unity计划在2018年为每个用户奉上增强的图形功能,例如影片中使用的次表面散射。我们称之为可编程脚本渲染管线(SRP),它是Unity 2018 beta版提供的一套新API,用户可以用它自定义渲染器。我们还将发布SRP的模板实现,称为高清渲染管道(HDRP),这是个包含次表面散射等新功能的现代渲染器。《ADAM》中使用的次表面散射是从HDRP直接移植到Unity 2017.1的现存渲染器中的。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引