Volumetric Light Scattering in three.js
Recently I wanted to use volumetric lighting for a sketch in three.js. In the past I have used Thibaut Despoulain’s (WebGL) Volumetric Light Approximation in Three.js with some updates to get it running in the latest version of three. This time I wanted to dig in and try an implementation directly from the original source, Kenny Mitchell’s Volumetric Light Scattering as a Post-Process in GPU Gems 3.
The general technique remains the same across all the implementations:
- Render the scene, with the light source as white and all occluding objects as black, to a texture.
- With a shader, apply a parameterized radial blur to that texture to create the lighting effect
- Additively blend that result over a render of the normally lit scene and display on screen
All the magic comes from the shader. In the Gems article Mitchell gives a detailed explanation of the math used ( most of which I don’t understand ) and how he ends up, finally, at his solution to
…estimate the probability of occlusion at each pixel by summing samples along a ray to the light source in image space. The proportion of samples that hit the emissive region versus those that strike occluders gives us the desired percentage of occlusion…
He then adds a series of parameters to this summation to control the effect produced.
- exposure controls the overall intensity / brightness.
- decay controls the fall off from the light source.
- density controls the separation between samples. If the density factor is increased, the separation between samples is decreased, resulting in brighter light shafts covering a shorter range.
- weight provides a secondary, fine grained control over the brightness.
Each of these becomes a uniform in the shader that can be used to manipulate the effect. For my implementation I also included a uniform to adjust the number of samples along the ray, up to a max of 100. Both to see how the effect is built up and to tune the performance. A higher number of samples makes for a more expensive shader and slower frame rates.
To improve performance Mitchell recommends downsampling the effect by rendering it at a smaller scale than the screen and the blurring the result. For the sake of clarity I’m doing the scaled rendering but leaving out the blur.
Additionally, if you don’t like the streaking artifacts that are produced he recommends reducing the contrast in the occlusion scene. In his example images the sun is a mid gray sphere.