Check out an article by Cory Spooner on how he used vertex shaders in UE4 to reduce draw calls and achieved natural-ish-looking plant
Check out an article by Cory Spooner on how he used vertex shaders in UE4 to reduce draw calls and achieved natural-ish-looking plant animation. Cory is a New Zealand based indie developer and has been working with Unreal Engine for over 16 years. Having previously worked on the Bioshock & Borderlands franchises, he recently shipped his solo title Swing Racers on iOS and is now finishing off Garden Wars, which he hopes will assist with funding for the next, much larger project. Garden Wars is currently on Steam Greenlight.
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…
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
скачанные файлы (1)
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:
скачанные файлы (2)
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
This was enough to be able to separate each section out in the plant material:
скачанные файлы (1)
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:
скачанные файлы (3)
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:
скачанные файлы (4)
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:
скачанные файлы (5)
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.