Fast & beautiful 2D lighting in Unity
I posted a short tweet for #unitytips on how to create flashy 2D lighting effects with Unity and got some requests to clarify exactly how it was implemented, so I thought I’d try to run through the setup.
Update
As I’ve gotten a lot of questions on this article, and the fact that I never actually had time to finish the game the screenshots were taken from (who ever finishes their games right) I’ve cleaned up the code a bit and pushed it to GitHub. Now you can all get it and try it for yourselves, but please note that the project includes a lot of other stuff as well, such as:
- Map import from Tiled
- Dynamic room prefabs with variable exits
- Maze generation with simple path checking
If you have other questions about the code, you can open an issue on GitHub, or if you want to contribute something back to it, fork the repo and send me a Pull Request!
Now, let’s get back to the story!!!
In-game screenshot
Scene setup
1. Layers
To make this work we need two extra layers. I’m going to call them shadowcaster and lighting in this article. Like the names indicate all objects casting shadows should be set to the shadowcaster layer, and all objects receiving the shadows should be set to the lighting layer. In my setup I only have a single plane receiving shadows to make it as efficient as possible.
2. Objects casting shadows
For every sprite based object that should cast a shadow, create a 3D object with an adequate shape and position it behind the sprite (i.e further away from the camera on the z-axis). Try to make sure that all objects end at the same z-value as you want to add a plane acting as a floor later on (unless you want objects floating in the air). Since we’re not using this object for anything but casting shadows, remove all components but the MeshRenderer.
Remember that blockers that are higher than the position of the light source will not get their top lit by the light source which will make them dark in the light layer. To address this, either lighten up the material color of the 3D object, or create a second light source higher up that only affects objects on the shadowcaster layer.
Shadow caster setup:
- Layer: shadowcaster
- Cast Shadows: On
- Receive Shadows: Off
3. Creating the floor
The floor receiving the shadows can just be a simple plane. Again, make sure you only leave the MeshRenderer component for the object and that you position the plane behind the shadow casters on the z-axis.
Plane setup:
- Layer: lighting
- Cast Shadows: Off
- Receive Shadows: On
Make the plane a child of the main camera and make it the same size as the camera viewport. If you want areas not hit by the light to be completely dark, set the color of the assigned material to black. The lighter you make the base color of the plane the more subtle the shadow effects will become.
4. Add the lights
Create a point light (or any other light source of your choice) and position it on the z-axis so that the light is fully, or partially blocked by the objects you created in section 2.
Light setup:
- Culling mask: shadowcaster, lighting
5. Light camera
Now create a new camera and make it a child of your main camera. Make sure the camera has exactly the same settings in terms of orthographic size and so on as the main camera as otherwise the lighting texture won’t match the output of the main camera.
Camera setup:
- Culling mask: shadowcaster, lighting
- Render To Texture: On
6. Main camera
Now the main camera needs to blend the rendered texture from the light camera with it’s own output with a shader. Since I’m no shader expert I use the BlendImageEffect shader from SpriteLightKit by Prime31. You can find the repo here on GitHub:
https://github.com/prime31/SpriteLightKit
Camera setup:
- Culling mask: Everything except shadowcaster & lighting
7. Conclusion
I hope this gives some insight exactly on how I forced Unity to give me nice real time shadows in 2D. Even though I use it in a classic Zelda style top-down game the approach works just as well with platformer game.