I took a few hours break from working on the animation system (working on gui, fixing some nasty memory bugs and bugs related to shadows) during last days to code a light-shaft effect. It was one of the effects I always wanted to add to the nGENE because it looks cool, allows to build the feel & atmosphere and besides is pretty easy to implement. However, up to now I could hardly spare any time to do it.
But without further ado let's move to the technique I used (proposed by Jason Mitchell in ShaderX).
Light-shafts are simply visible beams of light sometimes referred under the name of God rays. This phenomenon is caused by light being scattered by particles present in the air like for instance dust. It looks a bit like a smoke or fog.
This is volumetric effect for sure. There are several common ways of implementing volumetric effects. One of the most popular ones which I used in this case is to slice volume into several parallel planes and project some textures onto them to make light-shaft look more convincing (other approaches use 3D textures). Number of planes will influence performance and the quality of the final effect. These planes are rendered perpendicular to the eye vector in such a way that they fill whole view space light-shaft bounding volume.
At first a set of vertices is created filling 1 x 1 x 1 unit cube. Then view space bounding volume of light-shaft is found. Then in the vertex shader by using following trick:
float4 stretchedMaxBounds = float4(maxBounds, 0.0f);vertices are made to fill whole light frustum. minBounds and maxBounds are boundaries of light frustum bounding box.
float4 pos = float4(minBounds, 0.0f) * IN.position + (stretchedMaxBounds * (1.0f - IN.position));
In pixel shader all scattering related calculations are then performed. The first thing to do is to add light attenuation to make shaft disappear as it goes away from the light source. Regular equation 1 / (distance * distance) works pretty well here.
We could finish at this stage actually but the light shaft would look a bit unnatural. So we're adding a couple of things (by applying textures with projective texture coordinates to our volume slices) to make it better:
- Gobo/cookie - a pattern to be applied to all planes,
- Animated noise - what makes all the light shaft in nature so beautiful is that as the particles are moved by the air the light shaft seems to be moving,
- Shadow - shadow should limit the area where the light-shaft is visible i.e. only pixels not in the shadow should appear foggy.
It is also good idea to use clipping planes to discard those pixels which are not in the light frustum but still are in its bounding volume. Planes to use are actually light frustum planes.
Gobo also known as cookie is a 2D texture applied to each of the planes. Typically it is used to make shaft disappear in a certain area of the frustum so it serves similar purpose as shadow described later on. The difference is that shadow uses dynamic shadow map whereas cookie is a predefined pattern (for instance cross or flowers) and unless for shadow we do not test for 'z' value to make a decision of occluding pixel or not.
Here is an example cookie texture:
By adding animated noise we can achieve impression of moving air what causes particles to move. By animated noise I mean a typical noise texture being projected using time-changing texture coordinates.
Here we use light shadow map. In nGENE each light shaft can be assosiated with 'real' light source and then utilize it shadow map during rendering. The shadow map is used to hide light shaft where it should be invisible i.e. where there are occluders on the way from light source to a given pixel.
Below you can see light-shafts in action:
There are still some bugs and issues but they will be pretty easy to get rid of.