Unique Character Shadows in The Blacksmith
It was bright and sunny morning. It must have been some time last fall, when we finally got the message we had all been waiting for: a Challenger has arrived!
Granted, it was still an early draft; bald, clean-shaven, unskinned and without proper materials set up – but we were still excited to finally have him in the project. “Somebody puh-lease slap some materials on him and put this bad boy into the scene!”
And we did. And it was awesome. Except… something didn’t quite look right. “Uhm.. is this guy really set up to receive dynamic shadows?”. He was. But not quite the kind we were expecting.
Most of him didn’t actually seem to receive any shadow whatsoever, and what we did identify as shadowed was very weird and jagged looking.
A few quick shader hacks later, and the ugly truth revealed itself; not only was half the character missing shadows altogether; what little was there was indeed looking blocky and buggy.
‘Why was this happening?’, you might wonder at this point. After all, doesn’t Unity 5 sport this brand new and shiny soft-shadow filtering algorithm? It does, and it looks great. It so happened, though, that some of our scenes had view distances as far as 3,000 meters, and we’d decided we wanted shadows cast from and onto every single pixel in the view frustum. Now, naturally we had tweaked the cascade splits and shadow biases as best we could, and the shots generally looked really good both near the camera and far, far away.
What we hadn’t really tested properly until now was adding a very detailed character, very close to the camera in such a large-scene setup. Not only did we lack the resolution to produce soft, shadowy goodness on the character; the depth bias required for the scene to look good was so large that it pushed half the character out of shadow. Turns out casting soft shadows from a tiny leather strap, onto a piece or armour 1cm away, isn’t quite the same case as having a medium sized rock cast soft, stable shadows onto the ground. Who’d have thunk, right?
So we needed a solution to this predicament. We could have moved the first cascade really, really, really close to the camera, but that would have cost us a full cascade on the scenery. It would also only have worked in the scenes where the characters were really, really, really close to the camera, which would have solved enough of our problematic shots.
Instead, we decided to apply a bit of old-school technology to the problem. “What if we just explicitly render an additional shadow map focused on the character?”. Said and done. Two hundred lines of code later, the engine was generating these for us to play with:
At this point, we were able to generate enough data to apply pretty much any kind of fancy schmancy shadow filtering we felt like. We didn’t really have a great deal of time to spend fine tuning the best possible filtering, though, so we decided to go for a simple distance-aware sampling scheme similar to Nvidia’s PCSS.
Although not present in the shots shown here, we also had an option for capturing data from shadow casters outside the character’s focus area; e.g. to still have the static world cast shadows onto these dynamic characters. Such casters were projected onto the near-plane of the shadow rendering camera, ensuring they would always have the maximum blocker-to-receiver distance in the soft-shadow filtering scheme.
The only problem remaining at this point was how to integrate this nicely into the rendering pipeline. After some pondering and head-scratching, it turned out there was a very simple way to override the shadow functions used by nearly all common Unity shaders. After a few iterations, we eventually simplified it to only requiring two additional lines of code to add unique shadow support to any shader:
#pragma multi_compile _ UNIQUE_SHADOW UNIQUE_SHADOW_LIGHT_COOKIE
Note that the include has to go before any other engine includes for the override to work correctly. Obviously, this is slightly misleading as we just hid the actual override complexity behind an include file, so for anyone else looking to do something similar, here’s essentially what it boils down to:
#define SHADOW_COORDS(i) SOME_OTHER_COORD(i)
#define TRANSFER_SHADOW SOME_OTHER_TRANSFER
#define SHADOW_ATTENUATION(i) SOME_OTHER_ATTEN(i)
The conditions in our case are whether unique shadows are enabled, and whether we’re currently rendering a directional light or not. This second condition allows the feature to co-exist peacefully with other types of shadow casting light sources.
So after all this hard work, did we end up with satisfactory results? Here’s the same debug view again, this time toggling unique shadows on and off:
Old-school tech for the win!
One aspect to keep in mind is that – like most other rendering features – adding more shadow maps to your project doesn’t come for free. The shadow maps take up additional video memory, and there’s extra draw calls required to render into them, as well as increased bandwidth usage and computation power required to use them in your shaders.
For The Blacksmith, we set the system up so that we could dynamically toggle unique shadows on and off depending on how close to the camera the actors needed to be. Thus, we only paid the extra rendering cost in the shots where the characters occupied a large portion of the screen. For a game where the player might never get very close to the camera in normal gameplay, one could imagine only enabling this feature for close-ups in selected cut-scenes.
To better demonstrate this feature in isolation, we’ve put together a small demo project which you can get from the Asset Store. There’s a very basic scene included, meant to illustrate the problem case of having fine character detail near the camera in a large outdoors environment. Even with the first cascade covering only 1% of the total shadow distance, there’s a fairly radical difference between the uniquely shadowed challenger on the left, and the cascade shadowed challenger on the right.
3 CommentsSubscribe to comments
Comments are closed.