Tag Archives: Crytek

Light Propagation Volumes

I've finally finished my lab course last week - thanks to my supervisor Matthäus G. Chajdas - you can read his blog here -, it wasn't your usual lab course with work sheets and boring homework, instead I've been allowed to implement a nice paper about a Global Illumination approximation algorithm called (Cascaded) Light Propagation Volumes. It's been developed by Crytek and you can find more information (including some presentations and videos) on their server. (Note: this is an implementation of the I3D paper, not the earlier SIGGRAPH one.)

Sponza scene (direct + indirect lighting w/ occlusion)

Sponza scene (only indirect lighting w/ occlusion)

Sponza scene (ony direct lighting)

Sponza scene (boosted indirect lighting w/ occlusion)

Sponza scene (boosted indirect lighting w/o occlusion)

The algorithm approximates global illumination by rendering the light into a reflective shadow map, injecting it into a volume (using a spherical harmonics representation) and propagates the light flux in this volume (hence the name of the algorithm) and taking into account occlusion as possible extension.

The whole algorithm is physically motivated but corners everywhere, of course, to be more efficient. The paper also contains a few errors and doesn't explain everything needed to implement it in great detail (like eg the solid angles of the side faces), so I've written two documents detailing the mistakes I've found and the additional calculations I've performed.

You can find the mistakes here (including suggested corrections) and the full annotations document here.

Finally I've also uploaded the whole prototype (including my code licensed under the FreeBSD license and the media files) here - it's 68 MB big (and it's been compressed with 7zip with a compression mode that might not be supported by WinZIP. The Sponza model is from Crytek, too. You can download the original model and textures here.
The project uses DirectX 10.1 and by default it won't run in DirectX 10, because it uses a texture format that is deprecated in D3D10 but supported again 10.1 (BGRA). See the comment by FatGarfield for the location that needs to be changed for it work in DX10, too. (However red and blue will be swapped then.)

I haven't implemented cascaded LPVs and I also use only one light/RSM and only inject its depth into the occlusion volume, but the results already look very nice in my opinion.

Stay tuned for more :-)
Cheers,
Andreas

Rotation of Low Order Spherical Harmonics

I'm currently working at university on implementing Light Propagation Volumes. The paper makes extensive use of spherical harmonics while the implementation uses the first two bands.

Below is a visualization of the first 4 bands of the SH basis functions (created using Mayavi):

sh0to3

The first 4 bands of the spherical harmonic basis functions

As you can see the first two bands are 4 functions, so 4 coefficients to store which conveniently fits into one RGBA texture.

One of the main transformations that is performed in the LPV paper is the rotation of the spherical harmonics representation of a clamped cosine lobe (that represents surface lighting) onto a normal vector direction.  It took me a while to figure out, but actually it's quite easy, which is why I write about it :-)

The analytical presentation of the first four base functions is simple:

S_0 \left( x, y, z \right ) = \frac{1}{2 \sqrt{\pi}}
S_1 \left( x, y, z \right ) = - \frac{\sqrt{3}}{2 \sqrt{\pi}} y
S_2 \left( x, y, z \right ) = \frac{\sqrt{3}}{2 \sqrt{\pi}} z
S_3 \left( x, y, z \right ) = - \frac{\sqrt{3}}{2 \sqrt{\pi}} x

To evaluate lighting with SH for some direction v, you first determine the coefficients/weights of the SH basis functions and then sum them up.

 L = \sum_i s_i \, S_i \left( v \right )

Let's assume we know the coefficients  s^z_0, s^z_1, ... of the clamped cosine lobe around the z axis, then we can determine the lighting in direction v for the cosine lobe around the normal n by transforming it into the space where the normal coincides with the z axis (ie rotate n onto the z axis):

 L = \sum_i s^z_i \, S_i \left( R_{n \to z} \, v \right )

where  R_{n \to z} is a rotation matrix that rotates n onto z.

The idea is to expand  S_i \left( R_{n \to z} \, v \right ) and rewrite it in terms of  S_i \left ( v \right ) .

Before doing this, let's first take a look at the coefficients of the clamped cosine lobe:

\begin{align*} 
s^z_0 &=\frac{ \sqrt{ \pi } }{ 2 }\\ 
s^z_1 &= 0\\ 
s^z_2 &= \sqrt\frac{ \pi }{3}\\ 
s^z_3 &= 0\\ 
\end{align*}

The y and x direction are 0 because the cosine lobe is centered isotropic around the z axis:

So let's look at the expanded version of this formula if  r_1^T ,  r_2^T ,  r_3^T are the row vectors of the matrix,
 v=\bigl(\begin{smallmatrix} 
x\\ 
y\\ 
z 
\end{smallmatrix}\bigr) and  R_{n \to z}=\left(\begin{smallmatrix} 
r_1^T\\ 
r_2^T\\ 
r_3^T 
\end{smallmatrix}\right ) , then:

 L = \sum_i s^z_i \, S_i \left( R_{n \to z} \, v \right ) = \sum_i s^z_i \, S_i \left( \left(\begin{smallmatrix} 
r_1^T \, v\\ 
r_2^T \, v\\ 
r_3^T \, v\end{smallmatrix}\right ) \right )
\begin{align*} L &= s^z_0 \, c_0\\ 
&+ s^z_1 \, (-c_1) \, r_2^T \, v \\ 
&+ s^z_2 \, c_1 \, r_3^T \, v\\ 
&+ s^z_3 \, (-c_1) \, r_1^T \, v 
\end{align*}

Since  s^z_1 = 0 and  s^z_3 = 0 :

 L = s^z_0 \, c_0 + s^z_2 \, c_1 \, r_3^T \, v = s^z_0 \, c_0 + s^z_2 \, c_1 \, r_{31} \, x + s^z_2 \, c_1 \, r_{32} \, y + s^z_2 \, c_1 \, r_{33} \, z

  L = s^z_0 \, S_0 \left ( v \right ) - s^z_2 \, r_{32} \, S_1 \left ( v \right )+ s^z_2 \, r_{33} \, S_2 \left ( v \right ) - s^z_2 \, r_{31} \, S_3 \left ( v \right )

Now the question is: what is the third row of  R_{n \to z} ? If we look at the inverse matrix instead:  R_{z \to n} , we can immediately see that its third column has to be n, because  R_{z \to n} \, \bigl(\begin{smallmatrix} 
0\\ 
0\\ 
1 
\end{smallmatrix}\bigr) = n by construction. Since rotations are orthogonal matrices, the inverse is the same as the transposed, so we can deduce that the third row of  R_{n \to z} is the same as the third column of  R_{z \to n} ,  that is: n. Thus with  n = \bigl(\begin{smallmatrix} 
n_x\\ 
n_y\\ 
n_z 
\end{smallmatrix}\bigr) we get:

  L = s^z_0 \, S_0 \left ( v \right ) - s^z_2 \, n_y \, S_1 \left (  v \right )+ s^z_2 \, n_z \, S_2 \left ( v \right ) - s^z_2 \, n_x  \, S_3 \left ( v \right )

So the SH coefficients of the clamped cosine lobe along n are:

 
s^n_0 = s^z_0 = \frac{ \sqrt{ \pi } }{ 2 } \\ 
s^n_1 = - s^z_2 \, n_y =  -\sqrt{ \frac{ \pi }{3} } \, n_y \\ 
s^n_2 = s^z_2 \, n_z = \sqrt{\frac{ \pi }{3} } \, n_z \\ 
s^n_1 = - s^z_2 \, n_x = - \sqrt{\frac{ \pi }{3}} \, n_x

This is it :-)

Cheers,
Andreas

PS: a few screenshots from the LPV project:

GPUPropCopy 0616
noLPV
LPV32P128C

noLPV_2LPV32P128C_2