November 25th, 2008

Light scattering with openGL

This article is based on [Mitchell] publication which was based on DirectX. In the follwoing, I describe and provide an openGL implementation of the technique allwoing realtime light scattering effect (god's ray).

Source code and video

Binaries and sources available for Mac and Windows. You will need GLUT, SDL and SDL_image to compile under XCode or Visual Studio Express.





High level description

The best description is by Kenny Mitchell in GPU Gems 3 but while you wait for your Amazon delivery, here is a little bit more informations: It's a 2D post-processing effect including three stages :

  1. Render offscreen with a FBO: the light source and the occluding objects, no shaders involved here. In order to save cycles, you can render to a lower resolution (factor 2 gives good results) and disable texturing/depth testing.
  2. Clean the depth buffer, render the scene normally to the framebuffer.
  3. Switch to Orthogonal projection and blend the FBO with the framebuffer, activating the shader in order to generate the "God's ray" effect .

High resolution screenshots

Highslide JS Highslide JS Highslide JS Highslide JS

Low level description

The keystone of the process is the shader which compute the final color by taking sample along the segment [current fragment - light position].
One way to do this is to calculate light position in screenspace on the CPU side and pass these value as an uniform variable. Two ways to do this:

Note: Of course the best would be to keep all the transformation matrix in the CPU instead of calling glGetDoublev. This means give up on GLU mmethods such as gluPerspective, glOrtho and gluLookAt...I will write an article about this later.

The fragment shader

The keystone as aforementionned, notice the NUM_SAMPLES const, the more sample taken, the less aliasing...and the more load on the GPU shaders units. A lot of variables can be tuned to adjust the effect:

    uniform float exposure;
    uniform float decay;
    uniform float density;
    uniform float weight;
    uniform vec2 lightPositionOnScreen;
    uniform sampler2D firstPass;
    const int NUM_SAMPLES = 100 ;

    void main()
    	vec2 deltaTextCoord = vec2( gl_TexCoord[0].st - lightPositionOnScreen.xy );
    	vec2 textCoo = gl_TexCoord[0].st;
    	deltaTextCoord *= 1.0 /  float(NUM_SAMPLES) * density;
    	float illuminationDecay = 1.0;
    	for(int i=0; i < NUM_SAMPLES ; i++)
                 textCoo -= deltaTextCoord;
                 vec4 sample = texture2D(firstPass, textCoo );
                 sample *= illuminationDecay * weight;

                 gl_FragColor += sample;

                 illuminationDecay *= decay;
         gl_FragColor *= exposure;



[Mitchell]: Kenny Mitchell "Volumetric Light Scattering as a Post-Process" GPU Gems 3 (2005).
[Mitchell2]: Jason Mitchell "Light Shaft Rendering" ShadersX3 (2004).



Fabien Sanglard @2008