Using vertex shaders to animate and reduce drawcalls

Today I’m going over how I used vertex shaders in UE4 to reduce draw calls from a potential ~2500 down to ~500, and achieve natural-ish-looking plant animation while I was at it.

The unique challenge

There are a lot of individual plants and fruit etc on screen at once, all animating at different times and stages... and I wanted to be able to run well even on mobile devices, so it required some creative solutions.

Up to 762 plants can sprout individually across 127 tiles and animate at any time, along with individually animated fruit and flowers...

Up to 762 plants can sprout individually across 127 tiles and animate at any time, along with individually animated fruit and flowers...

There are 127 tiles on the screen that can be populated. The tile itself is a single drawcall, but:

  • On each tile you have 6 sections of plant that can sprout individually
  • With flowers that can appear on each plant (and must match the plants’ animation)
  • And up to 6 fruit, that also need the ability to be controlled individually
The six plants that can appear on each tile + flowers/fruit

The six plants that can appear on each tile + flowers/fruit

If I went ahead and just made it without any thought, I’d already be somewhere close to 2500 drawcalls! And that’s for just the populated tiles alone, let alone including environment dressing, special fx and UI. That’s probably 5-6 times what you really want on a lower-end mobile device. However:

  • In this case I’ve used all unlit materials (so that I could get the cartoony look I wanted)
  • Everything is made from static meshes (no skeletal animation)
  • There is no masking or translucency (the leaves and flowers are cut out so that I could use opaque materials, because in my experience the extra vertices spent cutting it out are well worth the saving you get by not having masked materials, unless you went super-crazy on the cutting out)

What this means is (as far as the GPU is concerned at least) I could afford to up the drawcalls a bit from a normal lit environment. So I said to myself that 1000 drawcalls would be a good limit.

Reducing drawcalls thanks to the vertex shader

In order to get those drawcalls down a bit, I thought the first thing to address is the foliage - the foliage on one tile needs to be one drawcall, even though it kinda sprouts up piece by piece. This is where the vertex shader animation, or “world position offset” material input, comes in. I can use the vertex shader to make the plants appear separate, even though they are the same mesh.

First, to do the basic opening animation, I wanted something more than just “expanding from a point”... so I went for a cheap equivalent of a “morph target”. That is, I stored the vertex positions of the plants’ pose in spare UV sets. I wrote a script to get the positions of the animated plants at certain keys and bake them into the UV set. Then in the material, I can lerp very cheaply between the imported pose, and the pose stored in the UVs:

Animating the vertices from the imported pose... to the pose stored in the UVs

Animating the vertices from the imported pose... to the pose stored in the UVs

Note: you could store the pose in vertex color instead of UVs, but I was already using that to store the “pivot positions” of each section of plant, for other shader purposes.

Now, in order to animate them open one by one, I gave each section of plant a unique 0-1 value in a spare UV set:

 
The plant piece with a value of 0.0 sprouts first, 1.0=last

The plant piece with a value of 0.0 sprouts first, 1.0=last

 

This was enough to be able to separate each section out in the plant material:

 
Using those UVs to separate the pieces of plant we want

Using those UVs to separate the pieces of plant we want

 

After that, they could be animated one at a time to match the “population” of that tile:

Sprouting plants one by one

Sprouting plants one by one

SO, now instead of 762 potential drawcalls for the foliage, there is only 127.

Since the flowers and fruit can pop up on any of these sections of foliage, I essentially did the same thing to those too (using the UVs to store the pose and separate each bit) - so each tile ended up having:

  • One drawcall for the tile itself
  • One drawcall for the foliage
  • One drawcall for the flowers
  • One drawcall for the fruit

Bringing 2413 drawcalls down to 508. With the environment dressing, FX and UI on top of that, it would rarely breach the 1000 drawcall limit I’d set. Success!

Improving the animation

So I had the plant, flowers & fruit animating, but it all still looks very rigid.

To make the animation seem more natural, I added some shader math to give the leaves some “bounce” - each leaf rotates back and forth about it’s base in a “random” direction (as I mentioned earlier, the vertex color holds the pivot point of each plant section). The plant blueprint animates the amount of “bounce” at the same time it opens each piece of plant, so the movement can be nicely faded out:

Adding "bounce" that fades in and out

Adding "bounce" that fades in and out

This “bounce” shader math is also reused a lot e.g. when bees touch or leave the plant, or when the plant is poisoned or frozen.

I also added a special directional “sway” to the flower when a bee lands on it. The plant blueprint calculates the direction that the bee is landing from, and sends that direction to the vertex shader. In order for the bee to look like it is attached (the bee is also a static mesh using vertex shader animation), that vertex shader logic was also put into the bee material, and gets animated by the plant blueprint as well:

Bees and flowers with the same vertex shader animation

Bees and flowers with the same vertex shader animation

Conclusion

So in the end, we have some basic animation that is fit for the purpose (it happens very quickly and each plant is quite small on screen), all controlled by animating just the two material parameters, and is done at about 20% of the cost of doing everything individually. Well worth the effort.