First of all, If you want to learn a lot of things about how spherical harmonics work, you should defenitly read the paper written by Robin Green. This is an awesome paper describing the things you need about spherical harmonics. I always wanted to learn how to use spherical harmonics and now it's done! :) The visuals of this demo are not so impressive but I really find the math inside very powerfull!
On this page, I will talk about spherical harmonic lighting but I will not go into the details. For this, you should better read Green's paper  which does this efficiently (and I don't want to copy his sentences). My goal was only to learn spherical harmonics and to provide a nice c++ class that everyone could easily use and improve.
Download source and executable
Spherical harmonics is a technic which allows developers to approximate a spherical function. In our case, we want to approximate an evironment map, i.e. a map that tell us the amout of light coming from all direction over a point in space. Projecting a spherical function into "spherical harmonics" will results in a vector of scalar C. This vector represents how much the function is like each basis function . To reconstruct the approximated function, you only need to sum all these basis functions weighted by their corresponding coefficient. The precision of spherical harmonics is represented by the number of band B used and will result in the use of B² basis functions (The size of C). The more bands you use, the better the approximation. However, too many basis functions used will increase the storage and computation cost.
The kick ass part of spherical harmonics representation is that you can compute the integral of the multiplication of two approximated spherical functions using a simple dot product between the two vector of coefficient representing them! Yeah!! :D
Spherical harmonics lighting
How can we use spherical harmonics for lighting? Well, for example, to render a scene lit by an environment map! So here I will only talk about diffuse lighting since it is simple due to the fact that it does not depend on the point of view in the scene, as compared to specular.
First, let's consider the fact that we have an environment map encoded in a spherical harmonics vector E. Second, consider we want to lit a 3D object with that environment map. We need to compute the diffuse brdf for each point on the surface of this object and store it as a spherical harmonic vector. For the sake of performance, we will compute this brdf only for each vertex. A simple diffuse brdf is a spherical function which for a vertex of normal n is dot_product(d,n) where d is the sampled direction. We project this BRDF function into a vector of spherical harmonics coefficients B. Finally, for each vertex, we compute the final lighting by simply computing the integral of E*B. As presented in the previous section, this is just a dot product! The result is visible on the next Figure.
Lighting of a scene when occlusion is not taken into account during spherical harmonics coefficients computation.
T he sphere on the left represents the environment map.
Ok, that is interesting but not very impressive. A way to improve this is to take into account the occlusion level of each vertex. To this aim, for each vertex, the function B become B=dot_product(d,n)*occlusion(d). Where occlusion(d) return 0 if the environment is not visible in this direction from the vertex position or 1 if it is visible. This can be done by ray-tracing the polygons of the object/scene. In my case, I did not take into account a drop-off factor but you could. The result are visible on the next Figure: the environment and the B function of each vertex are projected into shperical harmonics having 3 bands. So the environment map and each vertex store a vector of 9 scalar. You can notice that when rotating the environnement map vector, shadows appear automatically thanks to the occlusion information encoded into the coefficient vector of each vertex.
Lighting of a scene when occlusion is taken into account during spherical harmonics coefficients computation.
For each picture, the sphere in the background represents the environment map.
The next Figure show the difference between taking of not taking into account occlusion information for each vertex (using 3 bands).
For each picture, the sphere on the right represents the environment map.
On the left: spherical harmonics vectors represent the diffuse cosinus lobe only.
On the right: spherical harmonics vectors represent the diffuse cosinus lobe weighted with the occlusion:
Automatic self shadow appears.
- So far, I have only presented gray environment. However, you can approximate colored environments by approximating it with 3 coefficient vector, one for each of the color components RGB, as shown in the next Figure. You can also project more complex environment than those I present here.
A scene rendered with a colored environment map approximated using spherical harmonics.
Per vertex storage is good for performance but what if you don't want to tesselate your scene? What if you want better quality? In this case, you should have a look to spherical harmonics lightmap . These lightmaps encode the light coming from all directions over a surface element (surfel) in the 3D space. It can be used with a static environment map and with global illumination effect for nice looking render as in Halo 3 .
(BRDF*Occlusion) evaluation on the GPU
I wanted to evaluate the spherical function B of each vertex using the GPU because I love doing things on the GPU ;).
During the initialization, I compute a 1d texture Stex storing sampling directions in RGB color using a 16 bits floating point format. Using my method, I render the object in a cube map Ctex once for each vertex (only during initialization!). I clear the color in white (non-occluded) and render the object in black (occluded) directly giving me the occlusion information. In fact, to take into acount old graphic cards (no geometry shader support to render in cubemap) I use a virtual cube map (for more detail about virtual cube map, read  or send me a mail).
The algorithm consists in three steps:
- Render Ctex for current vertex position. To handle near clip plane, the center of projection is slightly move along the normal.
- Render to an FBO the sampling result Restex of B for current vertex using Ctex for occlusion information and Stex for sampling directions and dot product with current vertex normal (the BRDF).
- Read Restex back to the CPU to project B into a spherical harmonics vector.
Using this method, the sampling of the spherical function B ofeach vertex is done in parallel for each direction on the GPU.
Note on source code
I believe that my code on spherical harmonics can be used easily by every one. Concerning rotations, code is provided to rotate 2 bands spherical harmonics vector around each axis. The rotation for any number of band is only implemented for rotation around the X axis.
If you have any questions, do not hesitate to send me an email (see the main page of my website).
If your are interested in improving this demo, here are some possible future works:
- Implement the spherical harmonics vectors dot product in a vertex shader. I haven't done this in my demo because the number of band used can be changed and the vertex program must be changed each time accordingly.
- Implement spherical harmonic lightmap .
- Implement general rotation of spherical harmonics vectors for any number of band on the X and Y axes. (only general rotation on the Z axis is implemented right now)