Hi my name is Alexander Birke and I launched my first game on Steam last week called Laser Disco Defenders. I thought it would be interesting to cover some of the technical and design solutions that went into the game. I will start with the custom lighting system that allows for a lot 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.
To the left, using point lights doesn’t fit the shape of a laser beam very nicely. On the right you can see the desired contour of the light.
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.
Other parts than the lasers casts of lights so this needed to be supported as well.
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!
The laser sheet. The marked areas have the diffuse falloff used for all the laser lights.
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.
Diagram of how the mesh was generated for a single laser. Each segment needs a quad for each end and a quad for the straight segment in the middle. The light from the impact flare needs it’s own quad as well. This is the same topology used to draw the laser itself.
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.
Showing and hiding the light mesh. Normally the light mesh is far behind everything else in the game, so it doesn’t show when viewing the scene. Here you can see lasers and some deadly blobs casting off light.
The mesh is then rendered into a separate color buffer. In Unity I did this 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.
The light mesh and the texture it’s rendered into.
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.
Example of a background texture in Laser Disco Defenders. To the left is the color channels and to the right is the alpha channel used for reflectivity which allows for dark crevices in this cave texture. There’s also a space whale skeleton because... we could!
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 will hopefully agree the lighting adds a lot to thepsychedelic 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!
Alexander runs Bristol based studio Out Of Bounds Games. You can follow him on Twitter or the facebook page for the company which has the most corporate URL ever. He also organizes the Bristol Unity Meetup.