Why

We have a big presision problem for shadows with directional lights.
Directional lights have no range. Then how do you define the shadowmap?

There is no problem for spotlights or pointlights they have a range!

Thus to make sure we have shadows where we want them, we take the range so that our whole camera frustum is inside it.

As you can see, we need to define a very big region for a directional light! Even a 2048*2048 can’t give good results.

= multiple shadowmaps for one light
One 2048*2048 == 4 * 1024*1024!

External image

Here we have 4 shadowmaps. Each defining a part of the viewfrusum. The farther we look the more unpresise it gets, but that’s less of a problem because it is far away.

Here is a false color picture of how it looks ingame
Green = second
Blue = third
Lightblue = fourth

External image

How

• Calculate shadow view matrix form a direction
• Divide camera frustum in n-parts (4 is a good number)
• Calculate shadow projection matrix for each part
• Calculate new frustum
• Transform new frustum in shadow view space
• Find min and max
• Create orthographic projection matrix
• Render the final image using the n-shadowmaps

Implementation

This matrix is the same for every cascade

Get the shadow look vector, make sure it is normalized and points away from the lightsource

```vec3 shadowLook(-normalize(pDirectionalLight->getDirection()));
```

Calculate the up vector for the light:

• Take a random up vector
• The up has to be perpendicular to the look vector => dot product must be 0, if the dot product == 1 then the vectors are parallel to each other
• Now to get the up vector perpendicular to the look vector we first have to calculate the right vector, we do this with a cross product. If you crossproduct two vectors, you get a vector which is perpendicular to the other two. Bad things happen when you supply two vectors which are parellel to each other.
• By now crossproducting the look and the right vector we get the up vector!
```vec3 up(vec3::up);
up = vec3::forward;
```
Now we create the view matrix like this (position, lookat, up)
```mat44 mtxShadowView(mat44::createLookAtLH(pCamera->getPosition() - shadowLook, pCamera->getPosition(), up));
```

2. Divide camera frustum

We take four parts between the near and far plane of the view frustum. You have to tweak this!
It got decent results by taking  fixed points, but you could also make everything relative to the camera.

What do we need:

`mat44 getProjection(const Camera* pCamera, const mat44& mtxShadowView, float nearClip, float farClip);`

Clear enough on with the calculation

Calculate new frustum

We need all the 8 points. This picture will explain how we calculate them:

External image

Or not.

The camera is the view camera.
Our goal is finding w/2 and h/2

By adding the lookVector * Far to the camera position we get the center point of (P5, P6, P7, P8)
In topview we can easily calculate w/2 with a tan!

```tan(FOV/2) = W/2  /  Far
<=> W/2 = tan(FOV/2) * Far```

Now we need H/2 this looks hard, but it is much easier:

`H/2 = W/2 / aspectRatio`

We do the same for the nearplane and then we compose them to from the 8 points of the frustum

```float wFar = farClip * tan(pCamera->getFov()),             //half width //fov in pCamera is actually Fov/2
hFar = wFar / pCamera->getAspectRatio();         //half height
float wNear = nearClip * tan(pCamera->getFov()),        //half width
hNear = wNear / pCamera->getAspectRatio();     //half height

std::vector frustumPoints;
frustumPoints.reserve(8);
//Far plane
frustumPoints.push_back(pCamera->getLook() * farClip + pCamera->getUp() * hFar - pCamera->getRight() * wFar);
frustumPoints.push_back(pCamera->getLook() * farClip + pCamera->getUp() * hFar + pCamera->getRight() * wFar);
frustumPoints.push_back(pCamera->getLook() * farClip - pCamera->getUp() * hFar - pCamera->getRight() * wFar);
frustumPoints.push_back(pCamera->getLook() * farClip - pCamera->getUp() * hFar + pCamera->getRight() * wFar);
//Near plane
frustumPoints.push_back(pCamera->getLook() * nearClip + pCamera->getUp() * hNear - pCamera->getRight() * wNear);
frustumPoints.push_back(pCamera->getLook() * nearClip + pCamera->getUp() * hNear + pCamera->getRight() * wNear);
frustumPoints.push_back(pCamera->getLook() * nearClip - pCamera->getUp() * hNear - pCamera->getRight() * wNear);
frustumPoints.push_back(pCamera->getLook() * nearClip - pCamera->getUp() * hNear + pCamera->getRight() * wNear);
```

4. Min/Max

To calculate the projection rectangle

```vec3 minP(mtxShadowView * pCamera->getPosition()), maxP(mtxShadowView * pCamera->getPosition());
std::for_each(frustumPoints.cbegin(), frustumPoints.cend(), [&](const vec3& point)
{
vec3 p(mtxShadowView * (point + pCamera->getPosition()));
minP = minPerComponent(minP, p);
maxP = maxPerComponent(maxP, p);
});
```

5. Ortho Matrix

```mat44::createOrthoLH(minP.x, maxP.x, maxP.y, minP.y, min(minP.z, 10), maxP.z);
```

GLSL 150 core

```uniform mat4 mtxDirLight0;
uniform mat4 mtxDirLight1;
uniform mat4 mtxDirLight2;
uniform mat4 mtxDirLight3;

int sampleRange = 4;

float shadowCheck(in vec3 position, in sampler2DShadow sampler, in mat4 lightMatrix, in float bias)
{
vec4 coord = lightMatrix * vec4(position, 1.0f);
coord.xyz /= coord.w;
if (coord.x < -1 || coord.y < -1 || coord.x > 1 || coord.y > 1 ||
coord.z < 0)
return 0.0f;

//NDC -> texturespace
coord.x = (coord.x + 1.0f) / 2.0f;
coord.y = (coord.y + 1.0f) / 2.0f;
coord.z = (coord.z + 1.0f) / 2.0f - bias;

//Percentage close filtering
for (int x = -(sampleRange/2); x >= sampleRange/2; ++x)
for (int y = -(sampleRange/2); y >= sampleRange/2; ++y)
shadow += textureOffset(sampler, coord.xyz, ivec2(x, y));

}
void main()
{
...
if (position.z < 25)
{
}
else if (position.z < 50)
{