Stable Cascaded Shadow Maps - Sphere Based Bounding

Hiya again guys,

I have a niggling problem with my CSM. The middle an far cascades noticeably “swim” as you move around. It’s not really noticeable on the close one but i’m that’s probably just due to the filtering and the factor it has such a high quality map to work from.

Anyway I have 3 cascades and i’m using a bounding box for each. They all overlap with they near clip and this works fairly nicely but I was looking at the csm sample that some nicely ported over to monogame. The problem is I can’t get the sphere based bounding boxes working at all for me. My debug rendertargets for all the cascades are just blank. If I switch my camera to be the shadow light camera (the sun) it seems fine (and it works using my bounding box method so i think that’s fine). I THINK i’m just calculating the bounding box (using a sphere) incorrectly some how.

Could someone help me make this method use sphere’s instead please:

  public void GenerateCSMOrthoSlice(float pfarClip)
        {
            Vector3[] frustumCornersWS = new Vector3[8];
            Vector3[] frustumCornersLS = new Vector3[8];
            BoundingFrustum viewFrustum = new BoundingFrustum(_Camera.CameraView * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, _Camera._aspectRatio, 10, pfarClip));
            frustumCornersWS = viewFrustum.GetCorners();


            Vector3 frustumCentroid = new Vector3(0, 0, 0);
            for (int i = 0; i < 8; i++)
                frustumCentroid += frustumCornersWS[i];
            frustumCentroid /= 8;

            lightsView = Matrix.Identity;
            lightsViewProjectionMatrix = Matrix.Identity;

            ShadowLightPos = frustumCentroid + (SunlightDirection * 100);

            ShadowLookAt = frustumCentroid;

            ShadowLightView = Matrix.CreateLookAt(ShadowLightPos, ShadowLookAt, new Vector3(0, 1, 0));

            Vector3.Transform(frustumCornersWS, ref ShadowLightView, frustumCornersLS);

            Vector3 mins = frustumCornersLS[0];
            Vector3 maxes = frustumCornersLS[0];
            for (int i = 0; i < 8; i++)
            {
                if (frustumCornersLS[i].X > maxes.X)
                    maxes.X = frustumCornersLS[i].X;
                else if (frustumCornersLS[i].X < mins.X)
                    mins.X = frustumCornersLS[i].X;
                if (frustumCornersLS[i].Y > maxes.Y)
                    maxes.Y = frustumCornersLS[i].Y;
                else if (frustumCornersLS[i].Y < mins.Y)
                    mins.Y = frustumCornersLS[i].Y;
                if (frustumCornersLS[i].Z > maxes.Z)
                    maxes.Z = frustumCornersLS[i].Z;
                else if (frustumCornersLS[i].Z < mins.Z)
                    mins.Z = frustumCornersLS[i].Z;
            }

            float diagonalLength = (frustumCornersWS[0] - frustumCornersWS[6]).Length();
            diagonalLength += 2;    //Without this, the shadow map isn't big enough in the world.
            float worldsUnitsPerTexel = diagonalLength / (float)4096;

            Vector3 vBorderOffset = (new Vector3(diagonalLength, diagonalLength, diagonalLength) - (maxes - mins)) * 0.5f;
            maxes += vBorderOffset;
            mins -= vBorderOffset;

            mins /= worldsUnitsPerTexel;
            mins.X = (float)Math.Floor(mins.X);
            mins.Y = (float)Math.Floor(mins.Y);
            mins.Z = (float)Math.Floor(mins.Z);
            mins *= worldsUnitsPerTexel;

            maxes /= worldsUnitsPerTexel;
            maxes.X = (float)Math.Floor(maxes.X);
            maxes.Y = (float)Math.Floor(maxes.Y);
            maxes.Z = (float)Math.Floor(maxes.Z);
            maxes *= worldsUnitsPerTexel;

            ShadowLightProjection = Matrix.CreateOrthographicOffCenter(mins.X, maxes.X, mins.Y, maxes.Y, -maxes.Z - 500f, -mins.Z);
            lightsView = Matrix.CreateLookAt(ShadowLightPos, ShadowLookAt, new Vector3(0, 1, 0));
            lightsViewProjectionMatrix = lightsView * ShadowLightProjection;
        }

What I tried (which doesn’t work at all was:

 public void GenerateCSMOrthoSlice(float pfarClip)
        {

            bool StabilizeCascades = true;
            Vector3 minExtents = Vector3.Zero;
            Vector3 maxExtents = Vector3.Zero;
            Vector3[] frustumCornersWS = new Vector3[8];
            Vector3[] frustumCornersLS = new Vector3[8];
            BoundingFrustum viewFrustum = new BoundingFrustum(_Camera.CameraView * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, _Camera._aspectRatio, 10, pfarClip));
            frustumCornersWS = viewFrustum.GetCorners();


            Vector3 frustumCentroid = new Vector3(0, 0, 0);
            for (int i = 0; i < 8; i++)
                frustumCentroid += frustumCornersWS[i];
            frustumCentroid /= 8;



            // sphere based  cascade
            if (StabilizeCascades)
            {
            
            // This needs to be constant for it to be stable
            var upDir = Vector3.Up;

            // Calculate the radius of a bounding sphere surrounding the frustum corners
            var sphereRadius = 0.0f;
            for (var i = 0; i < 8; ++i)
            {
                var dist = (_frustumCorners[i] - frustumCentroid).Length();
                sphereRadius = Math.Max(sphereRadius, dist);
            }

            sphereRadius = (float)Math.Ceiling(sphereRadius * 16.0f) / 16.0f;

            maxExtents = new Vector3(sphereRadius);
            minExtents = -maxExtents;
            }



            lightsView = Matrix.Identity;
            lightsViewProjectionMatrix = Matrix.Identity;

            //Vector3 sunlightdirection = new Vector3(0.21f, 0.11f, -0.5f);
            ShadowLightPos = frustumCentroid + (SunlightDirection * 100);


            //ShadowLookAt = _SLLookAt;
            ShadowLookAt = frustumCentroid;

            ShadowLightView = Matrix.CreateLookAt(ShadowLightPos, ShadowLookAt, new Vector3(0, 1, 0));

            Vector3.Transform(frustumCornersWS, ref ShadowLightView, frustumCornersLS);

            Vector3 mins = frustumCornersLS[0];
            Vector3 maxes = frustumCornersLS[0];
            for (int i = 0; i < 8; i++)
            {
                if (frustumCornersLS[i].X > maxes.X)
                    maxes.X = frustumCornersLS[i].X;
                else if (frustumCornersLS[i].X < mins.X)
                    mins.X = frustumCornersLS[i].X;
                if (frustumCornersLS[i].Y > maxes.Y)
                    maxes.Y = frustumCornersLS[i].Y;
                else if (frustumCornersLS[i].Y < mins.Y)
                    mins.Y = frustumCornersLS[i].Y;
                if (frustumCornersLS[i].Z > maxes.Z)
                    maxes.Z = frustumCornersLS[i].Z;
                else if (frustumCornersLS[i].Z < mins.Z)
                    mins.Z = frustumCornersLS[i].Z;
            }



            float diagonalLength = (frustumCornersWS[0] - frustumCornersWS[6]).Length();
            diagonalLength += 2;    //Without this, the shadow map isn't big enough in the world.
            float worldsUnitsPerTexel = diagonalLength / (float)4096;

            Vector3 vBorderOffset = (new Vector3(diagonalLength, diagonalLength, diagonalLength) - (maxes - mins)) * 0.5f;
            maxes += vBorderOffset;
            mins -= vBorderOffset;

            mins /= worldsUnitsPerTexel;
            mins.X = (float)Math.Floor(mins.X);
            mins.Y = (float)Math.Floor(mins.Y);
            mins.Z = (float)Math.Floor(mins.Z);
            mins *= worldsUnitsPerTexel;

            maxes /= worldsUnitsPerTexel;
            maxes.X = (float)Math.Floor(maxes.X);
            maxes.Y = (float)Math.Floor(maxes.Y);
            maxes.Z = (float)Math.Floor(maxes.Z);
            maxes *= worldsUnitsPerTexel;

            lightsView = Matrix.CreateLookAt(ShadowLightPos, ShadowLookAt, new Vector3(0, 1, 0));
            lightsViewProjectionMatrix = lightsView * ShadowLightProjection;
            ShadowLightProjection = Matrix.CreateOrthographicOffCenter(mins.X, maxes.X, mins.Y, maxes.Y, -maxes.Z - 500f, -mins.Z);

            if (StabilizeCascades)
            {
                ShadowLightProjection = Matrix.CreateOrthographicOffCenter(minExtents.X, minExtents.Y, maxExtents.X, maxExtents.Y, 0.0f, pfarClip);


                // Create the rounding matrix, by projecting the world-space origin and determining
                // the fractional offset in texel space
                var shadowMatrixTemp = lightsViewProjectionMatrix;
                var shadowOrigin = new Vector4(0.0f, 0.0f, 0.0f, 1.0f);
                shadowOrigin = Vector4.Transform(shadowOrigin, shadowMatrixTemp);
                shadowOrigin = shadowOrigin * (4096 / 2.0f);

                var roundedOrigin = Round(shadowOrigin);
                var roundOffset = roundedOrigin - shadowOrigin;
                roundOffset = roundOffset * (2.0f / 4096);
                roundOffset.Z = 0.0f;
                roundOffset.W = 0.0f;

                ShadowLightProjection.M41 += roundOffset.X;
                ShadowLightProjection.M42 += roundOffset.Y;
                ShadowLightProjection.M43 += roundOffset.Z;
                ShadowLightProjection.M44 += roundOffset.W;
            }

            //
            //lightsView = Matrix.CreateLookAt(ShadowLightPos, ShadowLookAt, new Vector3(0, 1, 0));
            //lightsViewProjectionMatrix = lightsView * ShadowLightProjection;
        }