The lighting system in Laser Disco Defenders
Hey so Laser Disco Defenders is now out on Steam and in fact it’s also getting a physical release!
Now where the game is out I thought it would be interesting to go more into detail with different aspects of how the game was made. I will start with the custom lighting system that allows for 40+ 2D light sources even on low spec hardware. LDD was made in Unity but this approach should work in any game engine that lets you create procedural meshes.
Why a custom lighting system?
The central mechanic in LDD is that every laser beam fired keep bouncing around and can hit the player, hence why we call it a self inflicted bullet hell. Since the visual style is inspired by disco and the colorful dance floors of the time I wanted the lasers to cast light into the environment. It also has the added benefit that it hints to the player a laser is about to enter the frame before it becomes visible.
The game has also been released for PlayStation Vita, so I needed a solution that was more performant than Unity’s standard deferred lighting, that incurs a draw call for each light drawn to the screen. There can easily be 30 lasers on the screen at the same time in the game so this would not be feasible to run on a handheld console. I also wanted the lighting to fit the shape of the laser beams. Since a laser beam is typically long and thin, none of the standard light types in Unity (direction, point, spotlight) would be a good fit.
Other parts of the game also needed to give off light so I needed a simple light component I could put on game objects that the lighting system would then render.
Procedural Meshes to the rescue
The basic idea is to use a procedural mesh to “stamp” the lighting data into a light buffer that is then sampled in shaders. This is not that different from how deffered lighting works, however this solution only needs to work in 2D so I can get this done in one draw call for all my lights at once by using a procedural mesh to draw the light.
The lasers in the game were rendered with a procedural mesh so I already had a texture lying around I could use to store the light falloff in. I call this the dum dum duum… laser sheet!
For a given visual type of laser I needed the lengthy bits of the laser, the end caps and the larger flare that appear wherever they hit a wall. On a side note those flares are there to hide that each beam segment are just rendered out as a simple quad strip so they don’t overlap very nicely. It’s a bit of cheat but it works visually as well.
For creating the light for each laser I take the positions of each segment and expand their vertices outwards based on how far the light from the laser shall reach. Then other light sources have their vertices created and added to the mesh. For LDD I only needed circular lights but other shapes could easily be added as well. These circular lights use the same diffuse flare from the laser sheet and are rendered out as quads.
The mesh is then rendered into a separate color buffer. In Unity I did with a camera that has the same position and size as the main camera. It then renders into a RenderTexture using layers to exclude everything but the light mesh.
This color buffer was then sent to the GPU as a uniform texture so it could be used from any shader. Custom shaders were created for sprites, particles and anything else that needed lighting. In the vertex shader each vertex is also assigned it’s viewport position so that can act as uv coordinates when sampling the light texture in the fragment shader. The backgrounds in the game are the most interesting. Here the alpha channel is used to determine how reflective a given texel is. This helped a lot with adding more depth to the game.
The system features a couple of optimizations. The main one is frustum culling each light source before the vertices for it is created. This both reduces the amounts of CPU computations needed, and also limits the amount of vertices that has to be streamed to the GPU. As you might be able to see in the above GIF the light buffer is also at a much lower resolution than the game. I found I could reduce it to be just 10% the size of the game’s resolution without having any artifacts. When the texture is read in the shaders bilinear sampling gives enough interpolation for this to still work out. This reduces the amount of fill rate the lighting system uses which gave me enough rendering juice left to run a couple of image effects on top.
Conclusions and further work
On Vita the game hits very respectable frame rates which is something I’m quite proud off given the visual fidelity of the game. The good level of performance has also been noted in PC reviews. As you can see below the lighting adds a lot to the psychedelic disco vibe of the game.
Some current limitations of the system is that it doesn’t support shadows. This is something you can likely add in with a cost to performance . If you want shadows it will also mean you probably can’t do the trick with the low resolution light buffer. One thing it doesn’t allow for is specular reflections. This could be achieved by having another laser sheet that uses the color channels to represent directions which are then stamped into a separate direction buffer but this is beyond the scope of what I needed for Laser Disco Defenders.
I hope you enjoyed the article. Next post will go more in depth with the procedural level generation so stay tuned!