Hi, my name is Alexander Birke (Alexander Birke), I recently released my
first game on Steam called Laser Disco Defenders. It seems to me that it would be interesting to reveal some technical and design solutions included in the game. I will start with a proprietary lighting system that allows you to work with a variety of two-dimensional light sources, even on weak computers. LDD is created in Unity, but this approach will work in any other game engine that allows you to create meshes.
Why own lighting system?
The main mechanic of LDD is that every shot of the laser beam continues to bounce off the walls and can get into the player, so we called it "
bullet hell with our own hands." The visual style was inspired by the disco era and the bright dance floors of the time, so I wanted the lasers to be able to illuminate the surroundings. Lighting is also useful, as it helps the player to understand that a laser beam will soon be in the frame. The game was also released on the PlayStation Vita, so I needed a more productive solution than the standard Unity deferred lighting, which uses the draw challenge for each light source rendered in the scene. Up to 30 laser beams can easily be on the screen at the same time, the portable console cannot cope with such a load. I also wanted the lighting to match the shape of the laser beams. The laser beam is usually long and thin, so none of the standard Unity light sources (direction, point, spotlight) fit.
')
The point source on the left does not match the shape of the laser beam. On the right - the desired contour of light.
Other game elements also had to emit light, so I needed a simple lighting component placed on the game objects that the lighting system had to render.
Not only laser shots should be supported, but other light sources as well.
Procedural grids go to the rescue!
The main idea of ​​using procedural grids was to “imprint” the lighting data into the lighting buffer, which was then sampled in shaders. This is not different from the principle of deferred lighting, but my solution requires work only in 2D, so I could calculate all the lighting in a single draw of a draw thanks to the drawing of the illumination using a procedural grid.
The lasers in the game were rendered as a procedural grid, so I already had a texture that can be used to store a decrease in light intensity. I called it (pam-pam-pa-a-am!) ... laser table!
Laser table. From top to bottom: glow, the ends of the rays, the rays themselves. Marked areas have diffuse scattering for all laser light sources.
For the chosen visual type of laser, I needed the long parts of the beam, its ends and the glow that occurs when it hits the wall. In addition, this glow should hide the fact that each segment of the beam is rendered as a single quadrangular strip, so they are not very nicely superimposed on each other. This is a little trick, but visually everything looks good.
Grid generation scheme for a single beam. For each segment, two quadrilaterals (quad) of the ends of the beam and one more for a straight segment in the middle are needed. Light from the glow of the wall also need your quad. The same topology is used to render the laser itself.
To create the illumination of each laser, I take the positions of each segment and move their tops outwards, depending on how far the light can travel from the laser. Then, vertices for other light sources are created and added to the grid. In LDD, I only needed round sources, but you can easily add other shapes. In these round sources, the same diffuse glow from the laser table is used and rendered as quadrilaterals.
Visible and hidden lighting grid. Usually, the lighting grid is far below everything else in the game, so it is not displayed when rendering the scene. Here you can see lasers and spots-obstacles that cast light.
Then the grid is rendered in a separate color buffer. In Unity, I used a camera for this that has the same location and size as the main camera. Then it is rendered in RenderTexture using layers to exclude everything except the grid lighting.
The lighting grid and texture to which it is rendered.
After that, the color buffer is sent to the graphics processor as a normal texture, so it can be used from any shader. Were created their own shaders for sprites, particles and everything else that requires lighting. In the vertex shader, each vertex determines the position of its viewport, so it can be used as UV coordinates when sampling the lighting texture in the fragment shader. The most interesting are the backgrounds of the game. They use the alpha channel to determine the reflectivity of a particular texel. It really helps to add more depth to the game.
An example of a background texture in Laser Disco Defenders. The color channels are shown on the left, and the alpha channel used for reflectivity, on the right, to create dark cracks on that cave texture. There's still a space whale skeleton ... just because we can !
Optimization
The system contains a couple of optimization techniques. The main one is the truncation of the visibility pyramid of each light source before creating vertices for them. This reduces the amount of computation required in the processor and limits the number of vertices transmitted to the graphics processor. As you can see from the light grid and texture image, the light buffer has a much lower resolution than the game. I found out that it can be reduced tenfold from the game resolution without any artifacts. When reading a texture, bilinear sampling in shaders provides interpolation sufficient for the game. This reduces the amount of work on the fill used by the lighting system, leaving enough rendering resources to create a couple of visual effects on top of it.
Conclusion and further work
On the Vita console, the game provides a decent amount of FPS, which I am very proud of, given the visual clarity of the game. A good level of performance was noted in the reviews of the game for the PC. I hope you also agree that the lighting significantly enhances the psychedelic disco-style of the game.
The system is still limited by the fact that it cannot create shadows. You can add them, but at the cost of performance. If you decide to make shadows, you will most likely not be able to rotate the focus with the low-resolution lighting buffer. Another system can not provide reflection of lighting. They can be created with the help of another laser table using color channels representing directions, which are then “imprinted” into a separate direction buffer, but such a system is already more complicated than what I needed for the Laser Disco Defenders.
Hope you enjoyed the article. In the next post I will look at procedural level generation in detail, so stay with us!
Alexander manages the Bristol gaming studio Out Of Bounds Games. You can follow him on Twitter or on the company's Facebook page with the most corporate URL in the world. He also organizes Bristol Unity Meetup.
