Light scattering with openGL
This article is based on [Mitchell] publication which uses DirectX. In the following, I describe and provide an openGL implementation of the technique allowing 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.
Usage:
- Move with arrow keys.
- Look with the mouse.
- Pause with space
- Exposure can be modified via W and S keys.
- Decay can be modified via E and D keys.
- Weight can be modified via R and F keys.
- Density can be modified via T and G keys.
- FBO size...you will have to recompile ;) !
Overview
Pros:
- Easy to setup.
- Gorgeous result.
Cons:
- Consum a lot of fragment horse power, especially with a high # of samples.
- Cannot re-use the precomputation to cast Shadows (like in [Mitchell2]).
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 :
- 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.
- Clean the depth buffer, render the scene normally to the framebuffer.
- 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
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:
- You can use the gluProject method.
- "Manually", with the following code:
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:
- Number of samples: The more the merrier.
- Decay
- Density
- Weight
- Exposure
- Occluding object FBO rendering resolution (firstPass texture resolution)
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; }
References
[Mitchell]: Kenny Mitchell "Volumetric Light Scattering as a Post-Process" GPU Gems 3 (2005).
[Mitchell2]: Jason Mitchell "Light Shaft Rendering" ShadersX3 (2004).