The frustum can be represented by a triangle which cover the whole visible scene from the actual camera.
You split this frustum and for each split you compute the orthographic projection.
To have stable cascade you can compute the radius and set this radius on width and height of the orthographic projection.
If you use the SDSM algorithm to find the tighter cascade from the depth buffer, you don't need to use stable cascade, that produce wasted space.
To compute the radius you simply need to use trigonometry because the frustum can be represented by a triangle.
Here the code to compute the radius :
// Tangent values.
float TanFOVX = tan(0.5f * FieldOfViewRadian * AspectRatio);
float TanFOVY = tan(0.5f * FieldOfViewRadian);
// Compute the bounding sphere.
Vector3 Center = CameraPos + CameraForward * (Near + HalfDistance);
CornerPoint = CameraPos + (CameraRight * TanFOVX + CameraUp * TanFOVY + CameraForward) * Far;
float Radius = (CornerPoint - Center).Length();
Here the code to compute the AABB :
// Scale needed to extract frustum points.
float scaleXInv = 1.0f / cameraProj(1, 1);
float scaleYInv = 1.0f / cameraProj(2, 2);
// Matrix needed to transform frustum corners into light view space.
Matrix cameraViewToLightProj = cameraViewInv * lightViewProj;
// Compute corners.
Vector3 corners[8];
float nearX = scaleXInv * nearZ;
float nearY = scaleYInv * nearZ;
corners[0] = Vector3(-nearX, nearY, nearZ);
corners[1] = Vector3( nearX, nearY, nearZ);
corners[2] = Vector3(-nearX, -nearY, nearZ);
corners[3] = Vector3( nearX, -nearY, nearZ);
float farX = scaleXInv * farZ;
float farY = scaleYInv * farZ;
corners[4] = Vector3(-farX, farY, farZ);
corners[5] = Vector3( farX, farY, farZ);
corners[6] = Vector3(-farX, -farY, farZ);
corners[7] = Vector3( farX, -farY, farZ);
// Compute corners in light space.
Vector3 cornersLightView[8];
for(int i = 0; i < 8; ++i)
cornersLightView[i] = cameraViewToLightProj.Transform(corners, 1.0f);
// Compute the AABB.
ComputeAABBFromPoints(cornersLightView, 8, outMin, outMax);