private void ComputeProjections(List <SSObject> objects,
                                        SSLightBase light,
                                        Matrix4 cameraView, Matrix4 cameraProj)
        {
            if (light.GetType() != typeof(SSDirectionalLight))
            {
                throw new NotSupportedException();
            }
            SSDirectionalLight dirLight = (SSDirectionalLight)light;

            // light-aligned unit vectors
            Vector3 lightZ = dirLight.Direction.Normalized();
            Vector3 lightX, lightY;

            OpenTKHelper.TwoPerpAxes(lightZ, out lightX, out lightY);
            // transform matrix from regular space into light aligned space
            Matrix4 lightTransform = new Matrix4(
                lightX.X, lightX.Y, lightX.Z, 0f,
                lightY.X, lightY.Y, lightY.Z, 0f,
                lightZ.X, lightZ.Y, lightZ.Z, 0f,
                0f, 0f, 0f, 0f
                );

            // Find AABB of frustum corners in light coordinates
            Matrix4 cameraViewProj = cameraView * cameraProj;
            SSAABB  frustumLightBB = SSAABB.FromFrustum(ref lightTransform, ref cameraViewProj);

            bool   shrink      = false;
            SSAABB objsLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity);

            #if true
            // (optional) scene dependent optimization
            // Trim the light-bounding box by the shadow receivers (only in light-space x,y,maxz)
            FrustumCuller cameraFrustum = new FrustumCuller(ref cameraViewProj);

            foreach (var obj in objects)
            {
                // pass through all shadow casters and receivers
                if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f ||
                    !obj.renderState.visible || !obj.renderState.receivesShadows)
                {
                    continue;
                }
                else if (cameraFrustum.isSphereInsideFrustum(obj.worldBoundingSphere))
                {
                    // determine AABB in light coordinates of the objects so far
                    shrink = true;
                    Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform);
                    Vector3 rad             = new Vector3(obj.worldBoundingSphereRadius);
                    Vector3 localMin        = lightAlignedPos - rad;
                    Vector3 localMax        = lightAlignedPos + rad;
                    objsLightBB.UpdateMin(localMin);
                    objsLightBB.UpdateMax(localMax);
                }
            }
            #endif

            // Optimize the light-frustum-projection bounding box by the object-bounding-box
            SSAABB resultLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity);
            if (shrink)
            {
                // shrink the XY & far-Z coordinates..
                resultLightBB.Min.Xy = Vector2.ComponentMax(frustumLightBB.Min.Xy, objsLightBB.Min.Xy);
                resultLightBB.Min.Z  = objsLightBB.Min.Z;
                resultLightBB.Max    = Vector3.ComponentMin(frustumLightBB.Max, objsLightBB.Max);
            }
            else
            {
                resultLightBB = frustumLightBB;
            }

            // View and projection matrices, used by the scene later
            viewProjFromLightAlignedBB(ref resultLightBB, ref lightTransform, ref lightY,
                                       out m_shadowViewMatrix, out m_shadowProjMatrix);

            // Now extend Z of the result AABB to cover shadow-casters closer to the light inside the
            // original box
            foreach (var obj in objects)
            {
                if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f ||
                    !obj.renderState.visible || !obj.renderState.castsShadow)
                {
                    continue;
                }
                Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform);
                Vector3 rad             = new Vector3(obj.worldBoundingSphereRadius);
                Vector3 localMin        = lightAlignedPos - rad;
                if (localMin.Z < resultLightBB.Min.Z)
                {
                    Vector3 localMax = lightAlignedPos + rad;
                    if (OpenTKHelper.RectsOverlap(resultLightBB.Min.Xy,
                                                  resultLightBB.Max.Xy,
                                                  localMin.Xy,
                                                  localMax.Xy))
                    {
                        resultLightBB.Min.Z = localMin.Z;
                    }
                }
            }

            // Generate frustum culler from the BB extended towards light to include shadow casters
            Matrix4 frustumView, frustumProj;
            viewProjFromLightAlignedBB(ref resultLightBB, ref lightTransform, ref lightY,
                                       out frustumView, out frustumProj);
            Matrix4 frustumMatrix = frustumView * frustumProj;
            FrustumCuller = new FrustumCuller(ref frustumMatrix);
        }