This article explains how I went about rendering shadows for all of the plants in Garden Wars. Shadows seem like such a small thing but having them there makes a big difference to how polished the game looks.
The unique challenge here:
- There are technically 762 plants (6 per tile) needing shadows at once
- The shadows need to be moving as the plants move
- I needed the game to run on mobile and it’s already pushing GPU limits there
- The game is using all unlit materials (to get the cartoony look), and unlit materials don’t cast or receive shadows
So I needed to think a little creatively to get some nice crisp dynamic shadows happening. I also needed to leave some room for environment shadows as well (which will be a separate article).
Making things easier first
Step one was actually reducing the number of drawcalls needed for the plants, so I came up with a way to have the plants on a tile appear to be individual, yet be part of the same drawcall. So that brought it down to 127 plants to cast shadows for, which is a lot more reasonable, yet still far too much for mobile if you were using the conventional shadow casting system.
Fortunately, there are a number of things that work in my favour here as well:
- The game is made up of tiles, and each tile knows about the plants that are on it. It knows the plant’s rotation, what stage of growth it’s at, and when the plant is moving.
- The game camera is orthographic and never moves
- The light is only ever coming from the top left direction
- Plants are only ever going to cast a shadow onto a dirt tile.
All of these things help make the solution simpler.
What I decided to do was pre-render the foliage shadows into a texture, which I can incorporate into the dirt material. But there’s a little more to it, because the foliage sprouts in sections... so each section of the plant needs it’s own shadow that can be turned on. Starting to get more complicated again.
Now, I don’t want to incorporate SIX shadow textures into the dirt material (one for each section of plant on the dirt). I need to keep what I’m adding here as cheap as possible on GPU, since the dirt will be taking up most screen space for the majority of the game. So I needed to separate the shadows for each piece of plant, and still keep it to one texture somehow...
I decided to do this by separating the plant shadows into different grayscale values in the texture:
The reasons I did it this way were:
- It’s cheap (and I can store it in the alpha channel of an existing texture, so I won’t be adding a new texture sample to the dirt material at all - win!)
- The plant pieces always grow out in the same order (so overlapping shadow values don’t matter)
- I already had this 0-1 “Animation” parameter in the plant blueprint, that controlled the plant growing stages - so all I needed to do was have the shadow grayscale values for each plant stage MATCH the value that already grows the plant to that stage, if you know what I mean (more on that in a second).
Next came the material logic that adds the shadows as the plant grows. Here is the dirt material, getting the shadow it needs with a custom material function:
Note the “Anim” input in the function - that is controlled by the plant blueprint and basically specifies which sections of plant have grown. I use that value to separate out the bit of plant shadow I need from the texture, which the function then passes back to the dirt material. Here is what the shadows now like if I scrub the “Anim” value from 0 to 1.
Improving the shadows
So now I have the shadow coming in piece by piece as the plant animates, but there’s still a couple of things to be taken care of.
The plants can be spawned at any rotation on this tile. So to make the shadows match, I have to rotate our shadow texture to match the rotation of the plant that’s on this tile. To do this, I get the rotation of the plant (in radians) from the plant blueprint:
And pass that to the dirt material so I can rotate the UVs for the shadow texture sample:
Note that I have put the UV rotation logic through one of the Custom UVs inputs. The rotator node is quite expensive and I need to keep these shadows as cheap as possible. So what the Custom UVs input does is transfer the load of rotating UVs onto the vertex shader rather than the pixel shader (so it’s doing these calculations just 6 times - once for each vertex on the tile - instead of god-knows-how-many times for every pixel). This saves 5 pixel shader instructions.
Lastly, I just want to offset the shadow a bit (so that it looks like it’s getting cast from the top left). That’s done by simply adding a set value to the shadow UVs. Then I give it the ability to shake around, so that it can move as the plant moves and keeps the illusion of real shadows:
Note the “BounceAmount” and “BounceTime” parameters - those parameters are animated by the plant blueprint at the same time as it animates it’s leaves.
And that’s it! In the end we have achieved some nice crisp, dynamic, animating shadows for all our leaves, for a measly 7 pixel shader instructions total... which I think is well worth it!