GPU Instancing : Dynamic mesh-based Shadows
Working on projects for mobile platforms brings many hurdles concerning the hardware's capabilities and
the limits of the systems you can build.
One of these challenges was that only one directional light could cast shadows at a time. This was a
problem because I wanted multiple dynamic objects to cast shadows simultaneously.
After tinkering around the idea and my brilliant designer outright challenging me that I couldn't do it,
I decided to take a stab at it with murderous intent.
Step 1: The Idea
Shadows must be generated by multiple sources, but we can't, so we don't use lights.
Shadows are produced when light is blocked by an object, which can be represented as projecting
the object onto the plane from the perspective of the light source.
Bingo! Projecting an object or the visual representation of the object, its mesh, onto a plane
from the "light" source would give us a region blocked by the object or a shadow.
Idea competed, onto writing the code, i.e., banging my head against my desk to get the math
right.

Step 2: The Math
The math was quite simple once all the head-banging stopped. Project the mesh's vertices onto a
plane from the perspective of the light source.
The points were beautifully projected with a simple, elegant combination of dot
products, translations, and rotations with some vector math.

Step 3: The Shader
The shader I wrote had a vertex and fragment function. The vertex function projected the mesh's
vertices onto a plane from the perspective of the light source, and the fragment function
rendered the shadow.
With the shadow being rendered as a black mesh, the shadow was cast onto the plane, and the
object was rendered as if it was blocking the light source.
Another issue I needed to address was the rotation of the original mesh. Fiddling around with
quaternions, I realized that they are complete nightmare fuel. I eventually figured out the
calculations required to perform the rotations and Voila!

Step 4: The Optimization
The system worked well but took up a draw call for every shadow instance. This is not much of a
problem until the number of units and lights increases.
The solution was to use GPU instancing, which allows the GPU to render multiple instances of the
same mesh with a single draw call.
I LOVE INSTANCED SHADERS AND GRAPHICS DRAW CALLS!!!
Using a material property block, I could pass all the information for each light and other
settings to the shader, which would render the shadows for each object.
This improved the performance significantly, allowing for multiple objects to cast shadows at
the same time.

Step 5: The Bragging and Some Additions
It works! I must add that it is very well, even on mobile platforms.
The only issue was that the shadows had a hard, uniform darkness. There was no organic blending,
which shadows do.
MORE MATH! This time, however, it was a bit more straightforward.
I could calculate the distance between the vertex and its original positions to determine how
far away it must be, which could directly affect its intensity.
With a couple of exponent and power functions, creating a smooth and, more importantly,
controllable shadow fade was achieved.



Performance in a small test with a few objects and lights.

This was a complete success. I was able to have multiple objects casting shadows at the same
time. The performance was also quite good, with the real-time shadows generated. I was able to
have 30 units with 8 lights casting shadows at 200+ fps, with each unit having ~3000
triangles. On a mobile device, which is why I started working on this system, the system allowed
30-40 units with animations and 8 lights to run at 60+ fps in under 2ms.
One caveat is that each instance of a shadow for the object is a copy, meaning that (n+1) meshes
are being rendered for each object for n light sources. Luckily, the system uses GPU instancing
to render the meshes, meaning the performance hit is minimal.
Using this technique sparingly and when necessary is still advisable, preferably with meshes
with a low vertex count.
I am cleaning up some of the code and will post a demo soon. Stay tuned!
Thank you game-assets85 for the running man.