2021年3月8日

使用顶点着色器制作动画并减少绘制调用draw

今天我将讲解如何使用UE4中的顶点着色器将绘制调用从大约2500减少到500,并且让植物的动画效果看上去很自然。

独特的挑战

屏幕上同时出现了很多叶子和果实,它们各自处于不同的动画时间和阶段……我希望可以在移动设备上良好运行,所以需要一些有创造性的方法。

在127个瓦块(tile)上最多存在762棵植物,它们在不同的时间开花结果……

屏幕上最多有127个瓦块可以有植物。瓦块本身是一个单独的绘制调用,但是

每一个瓦块上有6株可以单独生长的植物

以及每株植物上可以出现的花(必须要配合植物的动画效果)

且最多有6颗果实,我们需要单独控制每颗果实

每个瓦块上可能出现的六株植物以及它们的花和果实

如果我不经任何思考继续工作的话,那么已经达到了约2500个绘制调用。而这只是对于有植物的瓦块而言的,此外还有环境外表,特殊的fx以及UI。这个数字几乎是你希望在低端移动设备上达到的5-6倍。但是

在此例中我使用的材质均为无光照的材质(所以我可以得到想要的卡通效果)

所有的物体都是使用静态网格物体做成的(没有骨骼动画)

没有遮罩或半透明度(除去叶子和花瓣以便我可以使用不透光的材质,因为在我的经验中不使用遮罩材质节省下的资源足够你使用额外的顶点将它们除去,除非你对于剪除的态度超级疯狂)

这意味着(至少在考虑到GPU的情况下)我可以稍微提升一点普通光照环境中的绘制调用。所以我认为1000是个不错的上限了。

使用顶点着色器降低绘制调用

为了稍微降低绘制调用的次数,我认为首先要考虑的是树叶——即使一个瓦块上的树叶是一片一片地生长,它需要一次绘制调用。这时我们需要使用顶点着色器动画,或者叫“世界位置偏移量”材质输入值。我可以使用顶点着色器使植物看上去是分开的,即使它们使用的是相同的网格物体。

首先,为了做动画的开始,我希望不仅仅是“从一个点向外拓展”……所以我寻找“变换目标”的一个简单的等价物。就是说,我将植物不同姿势的顶点位置存储在空闲UV集中。我写了一段代码来得到特定关键帧下动画中植物的位置然后将它们烘焙到UV集中。接着在材质中,我可以在导入的姿势和存储在UV集中的姿势之间简单地做线性插值。

实现从导入的姿势……到存储在UV中的姿势的顶点动画

注意:你可以使用顶点颜色代替UV来存储姿势,但是我已经使用它来储存植物每个部分的“基准点位置”以便其它着色器使用。

现在,为了逐个对它们实现动画效果,我在空闲UV集中为植物的每个部分添加一个独特的0-1的值。

值为0的植物部分先出现,1=最后出现

这足够区分植物材质中的每个部分了

使用那些UV来区分出我们想要的植物部

之后,可以一次进行一个动画来匹配那个瓦块的树叶。

一片一片地产生叶子

那么,相较于之前的762次树叶的绘制调用,现在只需127次。

由于花朵和果实可以出现在这些树叶的每个部分,我基本上对它们也做同样的处理方法(使用UV来储存姿势然后将各部分分开)——结果每个瓦块拥有了:

一次对于瓦块自身的绘制调用

一次对于树叶的绘制调用

一次对于花朵的绘制调用

一次对于果实的绘制调用

将2413次绘制调用减少为508次。加上环境外观,FX和UI也几乎不可能达到我设置的1000次最大值。成功!

改进动画

虽然我现在有了植物,花朵和果实的动画,但是它们看上去有些僵硬。

为了让动画看上去更自然些,我在着色器中加入了一些数学内容使树叶有一点“弹性”——每片树叶绕它的基准点来回以“任意”方向旋转(如我之前提到的,顶点的颜色存储的是每个植物部分的基准点)。植物蓝图在打开植物的每片树叶的同时完成“弹跳”数量的动画,所以动画会慢慢消失:

添加逐渐增强和减弱的“弹跳”

这个“弹跳”着色器的数学内容被多次重复使用,例如当蜜蜂接触或者离开植物的时候,或者当植物有毒或者冰冻的时候。

我还加入了当一只蜜蜂落在叶子上时,叶子会朝特定方向进行摇摆的功能。植物蓝图计算了蜜蜂降落的方向,然后将那个方向传递给顶点着色器。为了让蜜蜂看上去它是附在叶子上的(蜜蜂也是一个使用顶点着色器动画的静态网格物体),顶点着色器的逻辑也被复制到了蜜蜂材质中,然后由植物蓝图实现动画:

顶点着色器动画相同的蜜蜂和花朵

结语

那么最终我们得到了一些符合我们要求(时间很快且屏幕上的每株植物都很小)的基本动画效果,只使用了两个材质参数进行控制,且成本仅为单独做所有工作的20%。这个努力是值得的。