Light shafts, light scattering, god rays – many names but they represent the same awesome effect that can be noticed during foggy day or in a dusty room.
In games they make levels more moody and atmospheric, but how are they made?


The final effect might look like that.

The trick is to calculate the illumination power that comes from the source of light, travels near the object that overshadows it and finally reaches the pixel which color is currently calculating. Let’s say that it is very cheated light casting reduced to 2D.

To do this the occlusion of the scene is needed:

Occlusion of scene.

Occlusion of scene.

The black area is the light-blocking object. It’s black, because it complately blocks the light. The white circle behind is the source of the light. The color of the background is the ambient color of the light that is emited from the source (it is not white, because it would be too bright).

Every pixel of this image has to be sampled and put on the vector that moves from the light source to every pixel of the image. It might look more understandable when the occlusion scene is sampled 10 times:

Occlusion samplet 10 times.

Occlusion sampled 10 times.

As you can see the pixels of the occlusion image (not only the teapot, but the whole one) are sampled and put on the lines that go from the light source to the pixels of the final image. It’s like putting samples of the whole occlusion image inside ot the pyramid that goes from the light source to the screen, but flattened to 2D.

When we use more samples the whole scene starts to look like proper scattering.

Occlusion sampled 100 times.

Occlusion sampled 100 times.

To get that nice fading the samples summation is controlled by attenuation coefficients: weight and decay. The whole equation looks like that:


This migh looks scarry, but in the fragment shader code it looks simpler:

And that’s it! The scattering from the occlusion image is done. The only thing left is to apply it to the normal scene. I’ve done this in this same fragment shader at the end:

The last tricky part is to get the light source position on the screen. It can be obtained by transforming its world position to the clip coordinates using view projection matrix.

As you could notice I used a 2d texture array in that fragment shader. I stored there an occlusion image and the image of normal scene for final rendering. It means that for the scene with light shafts there are 3 render passes needed.

ls_passesYou can check the working application on github (Visual Studio 2015/2017 solution)