For our game, I wanted strong direct lighting contrasted with smooth diffuse fill lights from the environment. NASA-photos-from-moon-style of lighting with single strong source and harsh, deep-black shadows wouldn’t work with our colorful and vibrant scenery. Great way to handle environment lighting without too much hassle and GPU cost are spherical harmonics. Rather complex math behind them is actually not needed for implementation. Simple explanation of spherical harmonics (SH in rest of this blog post) in context of light is that they can be used to represent lossy compression of diffuse lighting over a sphere. Diffuse lighting is by nature low-frequency and small errors won’t make big impact to final picture. SH’s allow for compact storage of distant light sources, similar to blurred (convoluted would be better option) environment cubemap.
Typical scene in our game has many lights: at least one strong directional light (sun), and another complementary for creating basic lighting setup. Explosions, lasers, energy shields are all lightsources as well. And you can have dozens of them on screen each frame. Pure forward-based rendering wouldn’t handle these number of lights easily. Today, deferred rendering is very popular. Cost of having “fat” buffers for storing different components needed for lighting calculation (position, normals, albedo, specular, etc.) is not too high, but switching to deferred does require some mayor changes to surface shaders and rendering pipeline. SH lights can be easily integrated into any forward rendering.
Basic idea for this technique is explained well on Tom Forsyth’s blog few years ago. Unfortunately, I couldn’t find actual post. If I recall correctly, they were using it for PS2 game. Anyway, it isn’t anything new, but it can be good supporting technique for forward-based renderers. It’s cheap, fast, and easy to implement once you have needed math ready.
For each object we have a fixed number of “real” lights: lights that are evaluated per pixel with full shading, correct specular highlights etc. Currently we have two directional lights (and in process of adding two point lights for close-range explosions and small lights). Closest/strongest light contributors are added as real lights to each object; rest of lighting is represented as SH, per object. For each light we compute single color and direction, based on positions of object and light, light direction, color and attenuation. Light is then added to per-object SH, on top of global “ambient” lighting. Result is a compact representation of (mostly) distant diffuse lighting for each object. We are using 3rd order SH, which is effectively 9 coefficient of 4 floats(color) of storage, resulting in error less than 10% compared to “full” diffuse lighting. SH lighting can be evaluated with few shader instructions in vertex shaders, or like we do, in pixel shaders taking into account normal maps and multiplying it with precomputed ambient occlusion (AO). End result is smooth environment lighting for a fixed cost. Local scene lights also look OK in most cases, especially when we have lot of small pieces of rock flying around.
Of course, there are some drawbacks. Lack of specular highlights is first thing to note, as SH stores diffuse illumination only. This can be addressed using environment cubemap created from same scene background to sample reflections from, but may not be as big issue for mostly rough surfaces. Highly reflective materials, on the other hand, are anyway defined mostly with reflections rather than diffuse illumination and will require proper reflections to look good. Adding more fully shaded lights helps, but we lose benefits of fast, fixed-cost lighting.
SH can also represent occlusion and two SH can be combined to generate occluded distant diffuse lighting. Interesting way to handle local AO, without using some of screen-space techniques (ambient occlusion) could be to use bunch of spheres and compute AO per pixel analyticaly, similar to Inigo Quilez’s sphere AO (also, seems that Naughty Dog is doing something similar for soft shadows of game characters on the environment in The Last of Us). As most of moving object in our game are relatively small, this may work great and add some nice shading for asteroids and other objects.
But on that topic some other time.