来自生存儿童的图形和渲染技巧

我们想要实现一些视觉上有趣的东西。我们的目标非常艺术化,但我们也希望在性能方面做到非常便宜,因为我们一开始并不知道我们将使用什么样的设备能力。
项目的第一部分只是视觉探索——我们有一个艺术模型,用来展示我们想象中的艺术风格。其中一部分是非常风格化的照明设置,包括自定义阴影。
我们选择了通用渲染管线(URP),因为它在各种设备上的性能记录非常好,并且相对容易创建我们需要的任何新功能,以达到游戏的视觉目标。渲染的画面在前向模式下非常接近原始URP,因为游戏主要只有一个光源,太阳。我们在这里和那里有一些修改,比如自定义阴影、环境光遮蔽和其他几个自定义渲染特性,但总体上它在屏幕上是原始URP。

最大的补充是对着色器的支持,以适应艺术方向的非常特定的外观,因为我们需要修改光照计算的方式。制作自定义着色器并不是特别新鲜,然而我们编写了自己的自定义Shader Graph目标,以确保任何人都可以贡献。使用程序集定义引用使我们能够添加项目特定的Shader Graph目标,而无需拥有完全自定义的URP版本。这让我们能够坚持使用原始URP,只需我们的本地Shader Graph目标,这对我们的项目非常有效。
我们的目标之一是拥有动态照明——我们希望能够更改照明颜色、强度等。这意味着我们不能轻易地使用光照贴图烘焙照明信息,因此我们将错过一些通过烘焙反弹照明/全局光照获得的照明细节。我们需要考虑不同的方法来平衡高视觉质量和良好性能与动态照明方法,因为这通常更昂贵。这使我们最初使用了光探针,并且更依赖环境光遮蔽(AO)来帮助固定物体。

因为我们知道全局照明对这个项目非常重要,所以我们最初实现了一个自定义解决方案,可以在运行时更新光探针。但当我们转向Unity 6时,团队真的想切换到自适应探针体(APVs),因为视觉质量明显优于我们在性能影响相当的情况下拼凑的系统。当你有机会从某种好的东西升级到某种真正好的高质量且高性能的东西时,你就会切换。
海洋主要基于Unity URP演示项目船只攻击,但外观更具风格化。我们真正想做的事情之一是让岛屿和水中的其他元素产生波浪。这通常通过使用深度缓冲区根据距离计算海岸线来实现——但我们实际上没有海岸线,我们有一个Whurtle岛。

在Whurtle岛上,你有一个突然的下降,并且没有足够的深度衰减来实现效果,特别是考虑到水下的地形。我们想到的最佳主意是使用签名距离场,或SDF——它基本上是一个编码物体签名距离的纹理,或者在我们的情况下,是海岸线。这样,我们可以在距离海岸线一定距离处开始波浪,然后使用正弦波和一些扭曲纹理来赋予其有趣的外观。
最后,我们有一个编辑器工具,根据四个设定的水位高度烘焙海岸线的签名距离。然后我们在它们之间进行了一些混合和插值,以粗略估计海岸线实际的位置,因为大多数关卡中的水位根据玩家的进度而变化。我们依赖这个预烘焙的SDF信息来实现几种不同的效果,从调整海浪高度到添加泡沫、波浪和光斑。

对于视觉交互,从顶部视图渲染一个胶囊,围绕我们需要跟踪位置的任何物体,如玩家、可携带物体、工具等,渲染到一个RenderTexture中。该纹理基于世界空间,随着玩家的相机移动而滑动。
我们从胶囊的中心生成一个偏移(红色、蓝色),以及世界空间高度信息(绿色)。在 alpha 通道中,我们存储强度的衰减值。然后不同的着色器使用这个值来创建效果,例如植物弯曲、水面上的动画涟漪,或稍微加深地形以创建非常柔和的阴影效果。

为了性能优化,我们使用了深度预通道,在正常渲染物体之前填充深度缓冲区,减少由于早期深度测试拒绝而导致的渲染这些物体的成本。

我们在自定义通道中单独处理抖动物体,因为我们需要根据它们的状态和哪个玩家在查看它们来以不同的方式渲染它们。它们在一个不同的 GameObject 层中,该层在渲染器的透明层掩码中被排除,因此不会自动渲染,这意味着我们需要在自定义通道中渲染它们。我们使用 MaterialPropertyBlocks 为物体设置单独的值,并应用模板标记抖动的物体,以便稍后可以模糊这些部分。然而,由于这会破坏 SRP 批处理,我们需要限制其使用。我们决定仅在需要时应用 MaterialPropertyBlocks,并在完成后将其移除,恢复物体到可批处理状态。
最后,我们有一个完整的通道,专门处理如何将该特定层渲染到深度缓冲区。接下来,我们在深度缓冲区上应用模板,以标记哪些像素是我们正在淡出的物体的一部分,然后在进行抗锯齿时使用。
我们艺术风格的一部分是让阴影有颜色,并沿着阴影的方向有渐变。为了实现这一点,我们从一个渲染特性生成了一个自定义的屏幕空间纹理,该纹理会在世界空间中采样阴影图,但也会在XZ平面上向前查看以确定阴影混合值。这类似于在软阴影中使用的PCF滤镜,但仅在一个方向上。这被渲染成一个缩小的纹理,大小约为屏幕的四分之一,然后我们在三种颜色之间混合阴影颜色。

不幸的是,URP提供的SSAO并不完全适合我们的需求。虽然这是一个移动友好的实现,但为了达到我们想要的效果,我们需要将半径值设置得相当高,这占用了我们帧预算的相当一部分(约4毫秒)。相反,我们重用了旧的后处理堆栈v2包中的MSVAO实现,并进行了些许更改以提高效率并整合我们的阴影颜色。

生存儿童具有您在URP中期望的标准渲染通道(不透明、天空盒、透明),但我们还有一个额外的通道来处理我们的抖动对象,就在不透明通道之后。在这里,我们实际上会渲染我们的抖动几何体,因为该层中的几何体不会在不透明通道中渲染。我们在此通道中还进行深度相等测试,以确保我们仅在预填充深度缓冲区的地方进行渲染。

对于抖动的对象,我们需要禁用它们的环境光遮蔽,因为MSVAO将深度缓冲区中的“孔”视为遮蔽而导致的伪影。

在场景渲染后,我们应用抗锯齿。不幸的是,被抖动的区域会干扰算法(SMAA),导致视觉伪影。为了避免这种情况,我们需要单独处理这些区域。被抖动的区域(由模板确定)会被模糊,产生这些区域的 alpha 混合效果,然后在未抖动的区域处理 SMAA。在某些情况下会跳过这一过程,但我们最终得到了一张干净的最终图像,准备进行后期处理。
我们尽可能地保持我们的 后期处理效果 便宜,仅使用了一点色调映射、辉光和颜色校正。
在某个时候,我们在后期处理中使用了 URP 的模糊来软化 UI 后面的游戏,但后来我们用更便宜的 Kawase 模糊渲染特性替换了它。我们的 UI 系统建立在 UGUI 上,并进行了少量自定义渲染以实现渐变效果。

