How I replaced animation with shader and ̶s̶a̶v̶e̶d̶ optimized the world

So one fine day our designer decided that the trees in Summer Catchers are boring. And he was actually right because they were totally static.
After full day of work he presented a brand new version of tree crowns which were steadily swaying in the light wind. A real breath of fresh air in the visual aesthetics of the game.

But then I checked how this was achieved. My breath was taken away.

Now the crown had a Mecanim animator with a huge animation. Literally huge. Every pixel petal in the animation above was animated with several properties: position (x, y, z floats), color (r, g, b, a floats). Here is a shorten abstract.

Shorten version of animation

Prepare yourself for a full version. Just imagine all the efforts required to make this manually!

Despite new crowns looked great they were barely usable: just 5-6 of them on a screen were giving a noticeable CPU load on PC. Some mobiles were just dying and asking for mercy. I had to do something with them since reverting back to static ones was not an option.

Since CPU was overloaded I had to put the work on GPU which can handle lots of equal tasks much better.

The first thing I did is that I simplified the hierarchy. Each petal of each 4-petal flower was a separate object with a sprite so their transparency could be animated separately.
I merged them in a single sprite. This reduced the number of game objects and simplified the hierarchy.

Hierarchy simplification

If you watch the animation above carefully you may notice that it’s actually pretty simple. It basically consists of two parts. The first one is a horizontal movement, the second one is alpha fading. Essentially both parts are sine waves. Different phase shifts applied to different animated parts make the overall animation effect be smooth and harmonious.
Both movement and fading are not a problem to be implemented in the shader. However what we need to simulate the original effect is to pass phase shifts of each animated part to the shader somehow.
Using a shader parameter for this purpose is not an option since in this case we’ll end up with a bunch of copies of the same material for each part. This is not workflow-friendly and will make Unity draw each part with a separate draw call.
So we have to bake the information about phase shift inside the vertices somehow. Unfortunately SpriteRenderer does not provide any functionallity to access the underlying mesh. We could use our own meshes and MeshRenderer but I thought that it’s an overkill. Because we have this guy:

SpriteRenderer Color parameter

Four floats! Which are free since we don’t use color for its designated purpose.
So I wrote a MonoBehaviour which sets random values to the components of the SpriteRenderer’s color. Some components can be kept untouched (e.g. in case we need original alpha or something).

As always in such situations I used a default shader from Unity’s archive as a starting point for my own shader.
Required properties:

Notice that since we use vertex color with another purpose we don’t need to output it from vertex shader since otherwise it would need to contain (1, 1, 1, 1) value which has no effect on the final color.
Also pay attention to the B-component. Its purpose is to indicate whether the animation is required at all. We don’t need neither position nor alpha animation for the large crown sprite but we want to use the same material to keep the number of draw calls as low as possible. So for that SpriteRender I set a 1.0 in color’s B-component and forbid SpriteRendererColorRandomizer overwriting it.
Vertex shader:

Nothing fancy in fragment shader. Except that I use uv-coordinates to modify the phase shift (offset). This is basically required for those 4-petal flowers I mentioned earlier. Without this nuance we’d have a whole flower fading entirely at once.
Fragment shader:

Full shader can be found here

Final result:

And the original one for comparison:

Since all sprites of the crown are batched and have the same material they are rendered in a single draw call. And since the shader is quite lightweight now we have an animated crown which is barely heavier than the static one and looks almost identical to the reference one.

Share Comments
comments powered by Disqus