Inigo Quilez   ::     ::  


Spheres are awesome in may ways. One of them is that they allow for analytical solution to many problems, such as that of computing approximated but plausible soft shadows. Having a closed form for the soft shadow computation rather than having to resort to sampling (of a shadowmap or the scene though raycasting) is convenient. It is fast and noise free, and it is stable. The interest of spherical shapes might seem limited, but there's a lot spheres can approximate shape wise. I have successfully used this in real film production. And of course, they are also natural bounding volumes for more complex geometry, so having fast analytical ways to compute properties is actually very valuable.

Analytical soft shadows in action

The idea

The idea is simple. For a given point being shaded, a sphere in space and a directional light source (not an area light), see if the ray travelling from the point in question ro in the light direction rd does hit the sphere or misses it, and if it misses it, by how much. The closer the ray was to hit the sphere the darker the shadow will be (the penumbra). There's an observation though: the farther this point of closest approximation is form the receiving point ro, the less intense the shadow will be. In other words, in this simplistic model the darkness of the shadow depends on two parameters: the closest distance from the ray to the sphere's surface (which is perpendicular to the ray direction rd) and the distance from ro at which this closest distance event happens. If we call these d and t, then the soft shadow will be proportional to their ratio d/t. See diagram to the right of this text. This method will create sharp shadows near the contact between occluder and ocludee and softer shadows as this distance increases (hence the "plausible" attribute in the technique).

Configuration for our plausible soft shadow


All we need to do is computing d and t. Clearly d is simply the distance from the ray to the sphere's center minus the the radius of the sphere. Getting the closest distance between a ray (line segment) and a point is as easy as projecting the point into the line and seeing how far it landed, and t is the distance from that point to the origin ro.

Interestingly, this can be rewritten in terms of the parameters needed to solve the usual ray-sphere intersection, b, c and the discriminant h. The code below is an implementation of this technique:

float sphSoftShadow( in vec3 ro, in vec3 rd, in vec4 sph, in float k ) { vec3 oc = ro -; float b = dot( oc, rd ); float c = dot( oc, oc ) - sph.w*sph.w; float h = b*b - c; float d = -sph.w + sqrt( max(0.0,sph.w*sph.w-h)); float t = -b - sqrt( max(0.0,h) ); return (t<0.0) ? 1.0 : smoothstep( 0.0, 1.0, k*d/t ); }

In this case the parameter k controls the sharpness of the shadow penumbra. Higher values make it sharper. The smoothstep() function is there just to smoothen then transition between light and shadow.

Resulting analytical soft shadow

Alternative formula for soft shadow

Alternative formula

The above code is super efficient if you compare it to stocastic raycasting. However somtimes "super efficient" is not efficient enough. One way to make the code above faster is removing the square roots. I created the alternative approximation below which produces less physically correct shadows, but still plausible as in the sharpness of the shadows depend on the distance between the object producing the shadow and that receiving it.

float sphSoftShadow( in vec3 ro, in vec3 rd, in vec4 sph, in float k ) { vec3 oc = ro -; float b = dot( oc, rd ); float c = dot( oc, oc ) - sph.w*sph.w; float h = b*b - c; return (b>0.0) ? step(-0.0001,c) : smoothstep( 0.0, 1.0, h*k/b ); }

Another example of analytical soft shadows

Here's a link to the two versions of the soft shadows above in action running live: