Unity 5 - BLACKSMITH深度分享之独特的角色阴影

2015年09月10日 12:33 0 点赞 0 评论 更新于 2025-11-21 18:59

项目背景与问题初现

去年秋天一个阳光明媚的早上,我们终于迎来了期待已久的“维京挑战者”。当时它只是一个早期草稿,光头且没有皮肤纹理和材质,但我们依然为其加入项目而兴奋不已。我们立刻给它添加材质并放入场景,它看起来非常炫酷,不过有些地方却显得怪异。有人提出疑问:“这家伙真的有接收动态阴影吗?”实际上它确实接收了动态阴影,但并非我们期望的效果。整体来看,它仿佛没有收到任何影子,阴影不仅看起来奇怪,还参差不齐。

问题所在

经过对着色器(shader)的快速研究,问题的真相浮出水面:不仅角色一半的身体完全丢失了阴影,而且角色阴影呈现出块状的诡异感。

你可能会好奇,Unity 5 采用了全新强大的软阴影过滤算法,为何还会出现这种情况?我们的一些场景最远达 3000 米,并且我们希望从那么远的距离投射阴影到视维的每一个像素上。为此,我们竭尽所能地调整了级联分裂和阴影偏移,在近距离和离镜头非常远的情况下,效果看起来都不错。然而,我们从未遇到过在如此宏大的场景中添加一个离相机很近且具有诸多细节的角色的情况。一方面,我们缺乏在角色身上产生软阴影所需的分辨率;另一方面,为确保场景美观所设置的深度偏移过大,导致角色一半的身体被推出了阴影范围。毕竟,一块皮带在距离它 1cm 的盔甲上投射软阴影,和一块中等尺寸的岩石在地面上投射软阴影的情况是不同的。

为了解决这个问题,我们考虑将 Quality Setting 里阴影级联(Shadow Cascades)的第一级移动到非常靠近相机的位置,但这样会浪费一级可用的阴影级联,而且该方法仅在角色非常靠近相机时才有效,不过这也能解决当前遇到的问题。

解决方案

我们决定采用一种比较老派的技术来解决这个问题。我们提出:“如果仅仅针对角色另外渲染一个额外的阴影贴图呢?”说干就干,编写了两百行代码后,游戏引擎呈现出了理想的画面。

此时,我们有足够的数据来实现任何华丽的阴影过滤效果。但由于时间有限,我们没有对最佳过滤进行微调,而是采用了类似于 Nvidia 光导开关[1]的简单距离感知采样方案。此外,我们还设置了一个选项,用于捕捉人物对焦领域之外的阴影投射数据,确保静态世界的阴影能够投射到动态角色上。这些投影会被投到阴影渲染镜头最近平面上,以保证在软阴影滤波方案中具有最大的拦截 - 接收距离。

接下来的问题是如何将上述工作集成到渲染管线中。经过一番思考,我们发现有一种非常简单的方法可以重写常见 Unity shader 所用的阴影方法。经过多次尝试,最终简化为只需添加两行额外的代码,就可以为任意着色器添加这种独特的阴影支持。需要注意的是,为确保重写正常运作,“include”部分必须写在其他的 engine includes 之前。这里可能会有一些误导,因为我们使用了一个 include 文件来覆盖真正复杂的部分。若有人想做类似的事情,本质上需要关注两个条件判断:第一个条件判断决定是否启用这种独特的阴影,以及当前是否正在呈现一个定向光;第二个条件判断则决定能否与其他类型的投影光源和平共处。

福利展示

经过一番努力,我们得到了令人满意的结果。下面展示的是同一个调试场景在打开和关闭这种“独特阴影”技术时的不同画面,老派技术最终获胜!

总结

需要注意的是,和绝大多数其他渲染功能一样,在项目中增加更多的阴影贴图是有代价的。阴影贴图会占用更多的显存,渲染它们会产生额外的绘图调用(draw call),同时也增加了着色器对带宽使用和运算性能的需求。

在 The Blacksmith 中,我们对系统进行了设置,根据角色距离相机的距离来决定是否启用这种独特的阴影技术。只有在人物占据画面大部分的镜头中,才会付出额外的渲染成本。对于普通游戏而言,玩家正常游戏过程中可能很少出现角色如此靠近相机的情况,可能只有在近摄特定的过场动画时才需要启用该技术。

为了更好地单独演示这一功能,我们将其抽离到了一个小的演示项目中,你可以从 Asset Store 获取它:https://www.assetstore.Unity3d.com/en/#!/content/39921。该演示项目包含一个最基本的场景,用于模拟一些距离相机非常近的角色细节出现在宏大的户外场景中。即使第一级层阴影级联覆盖仅 1%的阴影距离,运用了我们“特殊阴影技术”的维京挑战者和运用常规级联阴影技术的维京挑战者之间,依然存在显著差异。

作者信息

洞悉

洞悉

共发布了 3994 篇文章