Point Light Shadow Optimization
When first implementing shadow mapping in our seventh game project at the Game Assembly, I did not expect it to tank on the framerate as much as it did. I soon came to realize how expensive it is to render every face in a cube-map for every point light in a scene. And so I came up with the following solutions to remedy this.
First thing I did was implementing a staple in deferred rendering, grid-based light culling. I chose a 4x8 grid covering the screen.
As the game we were working on had a first person camera, with lights that had a fairly large radius, the number of culled away lights per pixel was often low and did not give any massive performance gains. The light culling was more of a help as a way of knowing what lights were of interest to any shadow rendering.
When created a point light is given one of three different priority types: static, stepped dynamic and fully dynamic. Depending on their current state as active or not (on/off), static and stepped dynamic will be selected for shadow rendering.
A static light is queued for shadow rendering when it is switched from inactive to active.
A stepped dynamic light is queued whenever it is inserted into the light grid, given that it is active.
A light that is fully dynamic is added to a vector containing the ids of all fully dynamic lights. The lights in this vector are rendered every frame so long as they’re active. This priority was added as a necessity due to the game having a flashlight the player could toggle. If the flashlight was made stepped dynamic it would have resulted in noticeable stuttering in the shadows.
Every frame the shadow renderer will fetch the next queued light to render, meaning only a single point light shadow is rendered each frame. Every frame all lights are set as active/inactive if the player is currently in their corresponding room. This means whenever the player enters a room all static lights will be rendered over the following frames, and the stepped dynamic lights will begin continuously rendering.
The actions taken to optimize shadow rendering were successful in greatly improving the performance of the game. They allowed the final product to have multiple light sources that cast shadows. The solution worked perfectly with the game we were making, having very few moving objects and animated meshes isolated to specific locations.
It was however very extreme. Allowing only a single light source to have its shadows rendered each frame resulted in shadows that looked more and more choppy the more (stepped) dynamic lights in frame. The system did give a static performance cost yet with a large potential in decline of visual quality.
An idea I had was to have each point light have a static and a dynamic shadow map.
The static shadow map would follow the rules of the static priority but only rendering items that did not move and were not animated, such as environment and props. Whilst the dynamic one would have items that are mobile or animated rendered onto it every frame.
This would mean to somehow make these distinctions between rendered items which the engine did not support.