【GAD翻译馆】UE4中的角色伤口渲染

发表于2017-12-13
评论5 4.6k浏览

者: 刘超(君临天下) 审校:梁君(君儿)

在本周的早些时候,我敲击着键盘上的字符用于显示动态流血和伤口效果。今天我想要探讨一下关于该效果以及它是如何实现的。我将会谈论一些技术细节和一些可替代的方法。这些效果只是一个用于验证的概念,另外我将会试图找到一个成本更低的方法来替换纹理抛雪球算法进行目标渲染。所以让我们开始今天的学习吧!


在我们的游戏中使用了几个贴花的类型,像是子弹撞击身体之后,血花四溅,我们将血花抛洒在受伤的角色背后的墙上。这些贴花存储在和它们对应的组件上。但是当你试图在一个动画网格上做这个时,你需要注意贴在表面上的贴花看起来不太好看。因为有一天,我注意到这种玩家在未知的场景中滑动的操作,这些场景采用了传统的贴花手段,但是一个更加稳定的令人满意的解决办法是采用第三人称视角的游戏,这使得你可以不间断的看到你自己的角色的身体。采用使用很小的贴花这种技巧来解决,这种问题将变得难以被发现。以下是一个采用滑动贴花的比较夸张的例子:

我想要尝试为我们的角色上存在的这个问题找到一个解决方案,并且我受到了RyanBruckGDC示例的启发,通过使用渲染材质进行渲染目标的技术,将一个球形抛射在角色身上,该方法能够用于着色器上的伤口的掩码操作。这里是Ryan的基于伤害来实现的渲染对象。

然而这种效果比我们的预算要贵很多。它需要两个渲染对象(对于场景中的每一个角色而言),一个具有单独UV的网格(UE4的人体模型并不是采用单独的UV,需要在该引擎之外进行修改才能进行工作),此外在每一次击中角色时,需要在渲染目标上进行两次渲染,因此会导致有很高的运行成本(具有几十毫秒的运行成本)。如果你想要知道为什么需要进行两次调用,让我解释一下。

第一次的调用是非常直截了当的,因为你想要在你的角色的受伤的渲染目标上渲染一个抛射的对象。为了实现这样的效果,我们使用球状掩码来找到我们“攻击”的像素,然后在运行时进行渲染。因此Ryan对运动角色的像素在场景中的世界位置进行编码,再进行第二个渲染阶段,这样可以在进行球状掩码操作时进行采样。这里的问题是像素在每一帧的世界位置,尤其是对于动画网格,这意味着对于每一个新的攻击,我们需要首先重新渲染这个二次渲染目标来更新世界位置,然后我们才能进入到最终的渲染目标上。这对于一个纯粹的视觉噱头来讲,成本并不是非常有效的。我们需要找到一个更加便宜的方案。

优化渲染目标的方法

有一种方法可以优化这个技术,通过使用最近添加的预加载皮肤本地位置节点进行优化。这种方法通过预加载皮肤本地位置取代了之前我们进行纹理烘焙时采用的世界位置。通过这样做,我们仅仅需要在离线的时候捕捉一次位置即可(因为这个位置在运行时不会再发生变化)。为了捕获到参考姿势的位置,我制作了一个快速的蓝图以及Ryan的未包装材质的修改版本,捕捉场景并且将RT转换为静态纹理(你可以在随后的文章中看到它的运动版本)。这完全消除了在运行时需要进行第二次渲染的需求。每一次攻击,在我们应用至每一个角色时,我们将击中的位置(世界坐标系空间)转换至该网格的预加载皮肤位置的局部坐标系空间。在下一节,我们将会对如何将世界坐标系空间转换至预加载皮肤坐标系空间进行更加详细的解释。

这些成本花费了一定的成本,但是我对于为了创建一个角色而采用了一个独特的渲染目标这一点仍然不满意(如果你正在为示例创作一个部落的射击者,这个渲染目标能够真正的起到作用),并且在每一次击中时使用代价比较高昂的DrawMaterialToRenderTarget操作。我在我的装有GTX850M显卡的个人笔记本电脑上进行了性能测试,间隔1.6-4.5ms进行一次击中,这个量级是十分巨大的,尤其当这些操作不在你的控制中时,在单个帧中会发生多少次?记住这一点,该效果完全是一个为了好看的噱头,而不应该成为我们渲染预算的主要成本。

寻找一种替代方案

所以我彻底放弃了渲染目标方案,而是尝试尽力使用SphereMasks来实现这个效果。由于使用了球形掩码,这将会限制击中的数目,因此你能够向着色器中保证添加固定的计算成本。这里需要进行一些优化操作,例如如果该着色器没有接收到击中的请求或者直到在接收到一个击中请求时才对该着色器进行了交换操作,那么使用分支操作来去除球形掩码带来的成本。目前这个操作并非是必要的,因为着色器仍然在我们的“预算”之内。我认为3-5次的击中应该是比较合适的,因为任何额外的击中将会确保在第一个位置已经杀死了敌人(除非你在谈论一些终极boss角色),无论如何,这仅仅是该方案开始的一个垫脚石而已。

就像是原始的RT效果一样,为了让球形掩码和一个运动的网格能够稳定的工作,我们需要使用相对位置来放置球形掩码。当击中一个角色时,我们将击中的世界坐标系下的位置转换至相对坐标系下的位置(使用我们从伤害事件的初始位置获取的BoneName信息),你可以首先对当前我们击中的骨头进行一次逆向变换,然后使用相同骨头的相对位置转换将该位置进行变换。

这里你可以看到击中位置(绿色部分)应用了变换的可视化效果,从当前位置转换至被击中的骨头位置。蓝色线条对应了相对位置的转换。紫色线条是为了帮助指出两者的不同之处。

在相对位置的示例中(上述第二幅图),击中位置已经位于正确的空间中,因此你可以看到蓝色和绿色的线条是重叠的,在Z轴方向进行射击时,尽管因为他们存在相同的偏移,这里没有紫色的线条,因为在变换到可视化效果时,没有发生任何变化。

在第一幅图像中,你可以看到我是如何从原始的击中位置转换至我们的相对位置的。现在我们有了一个固定的位置,不会发生变化,我们将该位置放入着色器中,该着色器同时使用了预先皮肤位置来遮挡球形掩码。为了支持多重打击,当一个新的击中发生时,我们在每次使用时对参数名称进行递增操作,例如HitLocation_1, HitLocation_2(这些名称必须在使用之前保存在材质中)。

流血动画处理

相对于渲染目标的方案,采用球形掩码的最大的好处在于,该方法允许我们随着时间的不断推移进行修改数据,而不需要在RT中进行连续的绘制,这将会带来更多的计算成本。例如,随着时间的推移,很容易对球形掩码进行缩放操作,使得它看起来好像血液正在穿过角色的衣服一样。

其它有趣的事情是可以根据受到的伤害对掩码进行缩放操作,使得血液随着时间的流逝而变干或者在进过足够长的时间之后伤口进行褪色来表示该伤口“已经痊愈”。

结论

对于视觉上的改进还有很大的空间,但是基本的技术都是固定不变的。在上面给出的例子中,相对于我们从球形掩码得到的简单的球形,我通过添加了一个HeightLerp来获得了一个看起来更加有趣的降落的效果。

这种技术相对于原始版本具有更加稳定的成本,在击中数量方面不再受到限制。我们不再需要为每一个敌人提供一个渲染目标,此外由于我们仅设置了一些材质参数来实现这个效果使得我们不再有很高的渲染成本。使用渲染目标方案仍然是一个可行的方案,如果运行成本不是问题的话,那么这是在游戏过程中动态的修改角色状态的一个很好的办法。我感觉还有很多需要讨论的内容,例如伤口的消失,网格的变形等等!但是可能我们重新换个时间再来讨论吧


【版权声明】

原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权。

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

标签: