A high performance lighting system for 2D games

Topic will be started with the custom lighting system that allows for a lot 2D light sources even on low spec hardware. It was made in Unity but this approach should work in any game engine that lets you create procedural meshes.

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 author 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 example were rendered with a procedural mesh so it already had a texture lying around which can be used to store the light falloff in.

The laser sheet. The marked areas have the diffuse falloff used for all the laser lights.

For a given visual type of laser it 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 author 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.

The mesh is then rendered into a separate color buffer. In Unity author 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.

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. Author found that he 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.


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.


Leave a Reply