I have already integrated an algorithm to find all possible shadow casters for a directional light, in and around the camera frustum. I then calculate the area that creates the tightest fit around the casters and create the orthogonal projection matrix from that.

This works, as long as the light is pointing straight up or down. If it’s pointing in any other direction, the shadows are stretched, and the objects end up being rendered outside of the cascade texture maps. This video should make clear what I mean:

At the top you can see the cascades. The only shadow caster in this scene is the tree. Ignore the self-shadowing, that’s a problem for another time.

At around 10 seconds in I start changing the direction of the light. You can immediately tell by the cascade maps, that the object is being cut off.

My question is: How to I adjust my projection matrix to fit the entire shadows, if the light is pointing at an angle?
I’m assuming I need to transform either the orthographic matrix, or the points it is created from, with the view matrix, I’m just unsure how.

The light source direction determines the MODELING matrix for your light source. On top of that you need to determine what your ortho light frustum is going to be (i.e. left/right/bottom/top/near/far clip planes) – i.e. what your projection transformation is. There are a number of schools of thought on the “best” way to do this. Typically the best method involves one in which you end up with as little “shadow texel crawling” as possible, which either means 1) you need to have a more shadow resolution than you need in all cases (difficult to guarentee), or 2) you work to ensure that the coverage of shadow texels in your scene appears not to change, at least for shadows cast by static objects.

I’d recommend reading Michael Valient’s article in ShaderX6 (Stable Rendering of Cascaded Shadow Maps). This talks about a method to fit your light frustum around bspheres you fit around the view frustum splits to minimize shadow edge crawling/flickering artifacts.

It looks like from your example that you may be trying to tight-fit your light/shadow frustum to the casters which may cast into your scene. You can definitely do this, but consider what happens to shadow quality when a new shadow caster moves into/out of your light frustum. Is this a problem for you? If not, then it looks like you just need to work on ensuring that your light-space LRBTNF clip planes (i.e. your light-space “box”) encompass the entire bounds of your casters in light space.

Also consider what happens when casting shadows when you depth-test against a point which is out of the light frustum (i.e. outside the rectangular parallelipiped of space over which the casters are sampled into the depth map). For receivers which lie beyond the bounds of the shadow frustum far plane, you may find that you want clamp_to_edge behavior.

I’ve bought the book and tried to implement their algorithm, but I’m still having a bit of a struggle.

The article says you have to find the minimum enclosing circle/sphere(MEC) of the cascade frustum, and generate the view and projection matrix from the center of the MEC and the light direction:

My code for generating the view-projection matrix now looks like this:

std::vector<glm::vec3> trapezoid(4);
for(unsigned int i=0;i<numCascades;i++)
{
Frustum &frustum = m_frustums[i]; // The cascade frustum
glm::vec3 &ftr = frustum.points[FRUSTUM_POINT::FAR_TOP_RIGHT];
glm::vec3 &fbl = frustum.points[FRUSTUM_POINT::FAR_BOTTOM_LEFT];
glm::vec3 &nbl = frustum.points[FRUSTUM_POINT::NEAR_BOTTOM_LEFT];
glm::vec3 &ntr = frustum.points[FRUSTUM_POINT::NEAR_TOP_RIGHT];
trapezoid[0] = ftr;
trapezoid[1] = fbl;
trapezoid[2] = nbl;
trapezoid[3] = ntr;
glm::vec3 center;
float radius;
Seb::Calculate(trapezoid,center,radius); // Generates the MEC from the frustum's trapezoid
glm::mat4 matView = Matrix::LookAt(center -dir,center,glm::vec3(0,1,0)); // 'dir' is the light direction; 'center' the center of the MEC (Parameters are the same as for glm::lookAt)
float diameter = radius *2.f;
m_frustums[i].projection = glm::ortho(0.f,diameter,diameter,0.f,0.f,1000.f); // width and height = diameter of the MEC, near and far plane are arbitrary values for now
m_frustums[i].viewProjection = m_frustums[i].projection *matView;
}
[...]

It doesn’t work, but I don’t know what’s wrong. (Shadow casters aren’t rendered to the shadow maps properly.)

How would I actually do that? I thought I could just transform the min/max bounds around my shadow casters by the view matrix of the cascade, but I suppose it’s not that simple?