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.