Blog Archive
New Shadows Bring More Realistic Effects to Humanoids
Archive Creators Developers News
ROBLOX is all about iteration.
Whenever we work on any features – including all rendering features – we usually have a vision for the future, like how ROBLOX games will look on different platforms in a few years – and then we take bite-sized pieces of this vision and execute it. Shadows are no exception.
Since time immemorial, ROBLOX supported character shadows via a technique known as stencil shadows. This resulted in nice hard-edged shadows that followed the character geometry perfectly. However, the technique had several issues – it was not very fast, it was not very robust – on some character meshes you could see strange inverted shadows – and, most importantly, it was hard to integrate it with our lighting solution.
When we shipped dynamic lighting using our voxel system, the discrepancy between the stencil shadows and other rendering systems started becoming more and more apparent. For example, stencil shadows resulted in “double-shadowing” – a character standing in a shadow of a wall still casts shadow on the floor so you see something that is not realistic, and frequently hurts gameplay.
Thus we decided to ship a version of character shadows using “blob shadow” technology where we would analytically compute the shadow of a character using an ellipsoidal approximation of its shape. You can read more about this here.
There were a lot of nice properties of the new system – it did not require additional geometrical processing of character meshes (that is error prone and somewhat resource intensive given the details of our rendering system), it was easier to integrate with our lighting so we could get rid of double-shadowing, and it was easy to do level of detail by rendering fewer shadows on low quality levels. Also the outline of the shadows was no longer sharp, which matched our voxelized shadows better.
Unfortunately, we lost the silhouette definition of the character – which is especially apparent if a character moves or wears fancy gear or hats.
Almost a year ago now I spent some time during Hack Week to work on a few rendering improvements, and one of them was a new shadow system. I literally hacked together a new shadow solution that was designed to fix the issues of blob shadows but keep all their benefits.
After we shipped other parts of that hack week demo, it was time to productize the character shadow work. Now what you have to realize is that the projects we do on Hack Week frequently are prototypes, designed as experiments where you have to get the desired output result very quickly, without concerning yourself with performance, compatibility with all platforms ROBLOX runs on, all features we support etc.
When I started working on the final implementation, I quickly discovered that the algorithm I used during Hack Week does not work. If you look carefully at the video with lighting features from hack week, you may notice that the shadow outlines look a bit strange – almost as if they get sharper towards the edges instead of softer… This is a result of some equations that were completely incorrect but got semi-decent looking output. It turned out that without these hacks the algorithm I was using just did not work in our scenario.
I shelved this work and did not really know how to progress, so I switched to some other projects. However, a few weeks later I had an idea of a different algorithm – that was slighly more complicated to implement but it seemed like it should work. As I started developing this I got really excited since, while there were some additional artifacts, there were also very nice properties of the new algorithm:
- It was fast.
- It got pretty good results while not using a lot of memory.
- A property shared with the previous algorithm was that we can easily vary the distance at which you see the character shadows so that on higher quality levels the shadow distance is larger.
- Best of all, it worked on all mobile devices!
Now, let me explain the last point a bit more. When we develop rendering features, our favorite algorithms are the ones that are scalable – meaning you can alter the input parameters so that the required performance drops at some quality loss – but that are compatible with a full range of graphics hardware. This means that there is a nice progression from low quality to high quality on desktop, and that we can use the lower quality versions on mobile without implementing a custom solution. Sometimes we ship features that just don’t work the same way on mobile – for example, new water reflections – but in general we prefer code that works everywhere.
The new shadow algorithm was exactly like that. It required a bit of optimization and some quality reduction for mobile devices, but the shadows look similar on mobile and run the same code, modulo some different constants.
As a result, you now have shadows that integrate well with our voxel lighting system, preserve the silhouette of the character and whatever he or she is wearing, are soft-edged instead of hard-edged, and have performance that we are comfortable with on low end and can easily trade for higher quality if you have a good GPU.
If you’re a game developer, you’re probably wondering if you can use the shadows on other elements on your scene. Right now the shadows are applied to “humanoid parts” – as in, descendants of Models with Humanoid instances inside them. Note that due to limitations of the new algorithm we have to disable *receiving* shadows for objects that cast shadows – so you can’t just enable these shadows for your entire level. They will automatically apply to players and NPCs and monsters you may have in your game if they use Humanoid instances – additionally it may make sense to use them for other small dynamic objects in your game. Don’t go overboard though – there is some cost to using Humanoids.
There are still some edge cases where the new shadows don’t look as good as they could have, and we’ll continue improving on them, as well as on other parts of ROBLOX graphics. This is what iteration is all about, after all.