Hello :-). Today we are going to talk about shadows in my engine. So, why shadows are very useful in video games?

Simply because they let to improve strongly the realism of one scene.One exemple with, and without shadows.  The first one is better, right?
So, how can we draw shadows in real times?
You have two way to do it.

The first way, which we don’t talk really today, is the Stencil Shadow Volume. The main idea hide behind this concept, is you detect the silhouette, and extend the light ray, thus, you can know if you are in the shadow or not. If you want more explanations, this is some links to improve your knowledge about Stencil Shadow Volume.

I don’t use shadow volume because they don’t allow soft shadowing, and they are more complicate than shadow mapping.

The second way is : Shadow Mapping. We are not going to talk soft shadow mapping yet, only hard shadow mapping, even if soft shadows improve more realism, but they are more complicate too.

So, what is the main idea behind Shadow Mapping ?
It is a multi pass algorithm.
In the first pass, you render the scene from the point of view of the light, and in the shadow map, you store the distance of mesh from the point light position.
In the second pass, you render the scene from the point of view of Camera, and you compare the current distance with the shadow map distance to know if you are in the shadow. If Shadow map distance is bigger, you are not in shadow, else, you are.
If you want to use a point light shadow, you have to render 6 times the scene from de light point of view in all directions. It’s my own code to render in a cube map.

static vec3 const look[] = {vec3(1.0, 0.0, 0.0),
vec3(-1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, -1.0, 0.0),
vec3(0.0, 0.0, 1.0),
vec3(0.0, 0.0, -1.0)};

static vec3 const up[] = {vec3(0.0, -1.0, 0.0),
vec3(0.0, -1.0, 0.0),
vec3(0.0, 0.0, 1.0),
vec3(0.0, 0.0, -1.0),
vec3(0.0, -1.0, 0.0),
vec3(0.0, -1.0, 0.0)};

vec3 position = (matrix * vec4(mPosRadius.xyz(), 1.0)).xyz();

glMemoryBarrier(GL_UNIFORM_BARRIER_BIT);

mat4 projection = perspective<float>(M_PI / 2.0, 1.0, 1.0, mPosRadius.w);

for(u32 i = 0; i < 6; ++i)
{
device->clearDepthColorBuffer();

mat4 projectionView = projection * lookAt(position, position + look[i], up[i]);

synchronize();
} It’s the Cube shadow map of my scene.

You have to apply a little bias, else, you can have some little issues ^^. You can see many little artefacts, even if it’s beautiful, it’s not a realistic thing :D. You can see a little bit aliasing.

Many solutions exist to avoid this aliasing. We are going to talk about variance shadow map.

I remind you esperance and variance $E(x)=\int_{-\infty}^{+\infty}xp(x)dx$ and $Var(x)=\int_{-\infty}^{+\infty}x^2p(x)dx$

Variance can be approximated by $\displaystyle 0.25\left(\left(\frac{\partial f}{\partial x} \right)^{2} + \left(\frac{\partial f}{\partial y} \right)^{2}\right)$

So, my code to store data into variance shadow map is :

moments.x = distance(position, positionRadius.xyz) / positionRadius.w;
float dx = dFdx(moments.x);
float dy = dFdy(moments.x);
moments.y = 0.25 * (dx * dx + dy * dy);

And to compute shadow factor :

vec2 moments = texture(depthCube, lightToVertex).xy;
float variance = max(moments.y, 0.005);
float diff = dist - moments.x;
float p = variance / (variance + diff * diff);
return smoothstep(0.5, 1.0, 2 * (p - 0.5)); No aliasing now :).

The next time, we will talk about reflections with environment map.

See you soon :).

References :