Inigo Quilez   ::     ::  


At school we are all taught that gradients are the idea, concept, object and tool to be used for everything. Probably because it is a generic description of the local change of a function. Because of that, directional derivatives are regarded only as an intermediate concept to arrive to that of a gradient, and they are set aside for their lack of generality. And while there are good reasons to do so, we shouldn't forget that sometimes we don't want the general solution or implementation, but the specialized and optimized one. Like when you are painting some clouds.

Fast (realtime) dynamic lighting on volumetric clouds with directional derivatives

Say you are doing some realtime rendering of volumetric clouds, and that you need to do some lighting and shaping without scattering and self shadowing computations. You need to go cheap, but even extracting the gradient or normal from the cloud volume that you need to do your regular lambertian lighting is already expensive. You are probably evaluate your gradient by taking 4 or 6 samples of the volume, depending your implementation, only to then dot it with the light direction. Which works, but is very slow becaue evaluating your (possibly procedural) volumetric field 4 or 6 times is your bottleneck.

The idea

So now forget what your teacher told you about gradients and have a look to this article on the directional derivatives in the Wikipedia. In particular, look at this formula:

Now, if x was the point in space we are shading/lighting, and f was the volumetric field, then f(x) would be the density at that point we are shading, and ∇f(x) the gradient (or 'normal'). At the same time, if v was the light direction, then the right side of the equation ∇f(x)⋅v/|v| would be nothing but our regular N⋅L lambertian lighting... which according to the equation is equal to the directional derivative of the field taken in the direction of the light (left side of the equation)!

So basically, instead of extracting a general derivative in all possible directions and dot with the one direction of interest, you can measure the change (derivative) directly in that direction of interest. Or in other words, rather than taking 4 or 6 samples to extract a generic derivative or gradient, and then dot it with the light direction to do our lighting, we could simply sample the field no more than 2 times, at the current point and at a point a small distance away in the direction of the light (and divide by that distance of course). So, something that is 4 or 6 evaluations can be reduce to one. Since one evaluations has been already done for computing the opacity of the volume, we are now really doing two evaluations rather than 5 ot 7. Which is a massive speedup.

The code

To the left you can see the traditional way of doing your volumetric lighting based on gradients, which involves 7 evaluations per point. To the right you can see the new way of performing lighting which involves only 2 evaluations per point:

// function : R3->R1 is the volumetric density function // eps is the diferential unit, based on the current LOD vec3 calcNormal( in vec3 x, in float eps ) { vec2 e = vec2( eps, 0.0 ); return normalize( vec3( function(x+e.xyy) - function(x-e.xyy), function(x+e.yxy) - function(x-e.yxy), function(x+e.yyx) - function(x-e.yyx) ) ); } void render( void ) { // ... float den = function( pos ); vec3 nor = calcNormal( pos, eps ); float dif = clamp( dot(nor,light), 0.0, 1.0 ); // ... }
// function : R3->R1 is the volumetric density function // eps is the diferential unit, based on the current LOD void render( void ) { // ... float den = function( pos ); float dif = clamp( (function(pos+eps*light)-den)/eps, 0.0, 1.0 ); // ... }
The code not only is 3.5 times faster, but also smaller, which is great if you are doing some size-coding based demo or shader.

Of course, the drawback is that this only an advantage for a small number of light sources. Usually you'll want 2 light sources for clouds (the sun, and the sky dome). If you have 3 light sources or more, doing the traditional gradient-based lighting is more efficient again.

Here are some pictures that show the new directional derivative based lighting versus the gradient based, and also to no lighting at all, for comparison.

No lighting:

Traditional gradient based lighting:

Directional derivative based lighting (3.5 times faster):

Lastly, the technique can be used as well for solid surfaces of course, as long as they are defined by a volumetric funtion. As an example, the following interactive demo shows how to use this technique to get some lighting in a few bytes of code (click in the title to access the code):