title: Cascaded Shadow Map - Orthographic Projection Issues
body: <p>I am attempting to upgrade the shadow map rendering in my OpenGL game to the Cascaded Shadow Map method described in <a href="http://ogldev.atspace.co.uk/www/tutorial49/tutorial49.html" rel="nofollow noreferrer">this OGLdev tutorial</a>. Everything works fine except at certain camera angles the shadow map doesn't cover the entire view frustum.</p>
  
  <p><a href="https://www.youtube.com/watch?v=QGJEbx1Zp4w" rel="nofollow noreferrer">There's a quick video of the problem here</a>, where I toggle between the final rendered scene and the middle shadow slice texture. You can see that the shadows of the player are cut off too early in the shadow texture (white view).</p>
  
  <p>Each shadow map is rendered into its own FBO and sampled later when performing post-processing (simple fragDepth > textureDepth). The code is as follows:</p>
  
  <pre><code>const int SLICE_AMOUNT = 3;
  Matrix4[] lightViewSpaces = new Matrix4[SLICE_AMOUNT];
  Matrix4[] shadowProjections = new Matrix4[SLICE_AMOUNT];
  OrthoInfo[] orthoInfo = new OrthoInfo[SLICE_AMOUNT];
  
  const float zNear = 1;
  const float zFar = 256;
  
  Vector3 up = new Vector3(0, 1, 0);
  Vector3 LightDirection = new Vector3(-1, 1, 1).Normalized();
  
  float[] cascadeEnds = new float[SLICE_AMOUNT + 1]
  {
      zNear,
      32f,
      64f,
      zFar,
  };        
  
  Vector3[] frustumCenters = new Vector3[SLICE_AMOUNT];
  
  
  public void RenderShadowShader()
  {
      CalculateOrthographicFrustums();
  
      // Create shadows maps at each view frustum slice
      for (int i = 0; i &lt; SLICE_AMOUNT; i++)
      {
          // Bind the shadow map FBO for this slice
          Gl.BindFramebuffer(FramebufferTarget.DrawFramebuffer, CurrentCollection.shadowFBO[i]);
  
          var o = orthoInfo[i];
  
          // Combine the light view projection and orthographic projection                                   -200 for leniency
          shadowProjections[i] = lightViewSpaces[i] * Matrix4.CreateOrthoProjection(o.l, o.t, o.r, o.b, o.n - 200, o.f - o.n);
  
          Gl.Viewport(0, 0, (int)screenWidth, (int)screenHeight);
          Gl.Clear(ClearBufferMask.DepthBufferBit);
          Gl.Enable(EnableCap.DepthTest);
          Gl.Disable(EnableCap.CullFace);
  
          // Render scene objects to the shadowFBO depth buffer
          RenderShadowMeshes(shadowProjections[i]);
      }
  }
  
  // Convert each view frustum slice to light space
  void CalculateOrthographicFrustums()
  {
      // Get the world view projection from player camera
      Matrix4 Cam = Matrix4.CreateLookAt(player.headPosition, player.headPosition - player.LookAtVector(), up);
      Matrix4 worldViewProj = Cam.Inverse();
  
      var aspectRatio = screenHeight / screenWidth;
      var tanHalfHFOV = (float)Math.Tan(Math.PI * (parent.FieldOfView / 180.0) / 2.0);
      var tanHalfVFOV = (float)Math.Tan(Math.PI * (parent.FieldOfView / 180.0) * aspectRatio / 2.0);
  
      for (int i = 0; i &lt; SLICE_AMOUNT; i++)
      {
          float xn = cascadeEnds[i] * tanHalfHFOV;
          float xf = cascadeEnds[i + 1] * tanHalfHFOV;
          float yn = cascadeEnds[i] * tanHalfVFOV;
          float yf = cascadeEnds[i + 1] * tanHalfVFOV;
  
          Vector4[] frustumCorners =
          { 
              // Near face
              new Vector4(xn,   yn, cascadeEnds[i], 1.0),
              new Vector4(-xn,  yn, cascadeEnds[i], 1.0),
              new Vector4(xn,  -yn, cascadeEnds[i], 1.0),
              new Vector4(-xn, -yn, cascadeEnds[i], 1.0),
  
              // Far face                 
              new Vector4(xf,   yf, cascadeEnds[i + 1], 1.0),
              new Vector4(-xf,  yf, cascadeEnds[i + 1], 1.0),
              new Vector4(xf,  -yf, cascadeEnds[i + 1], 1.0),
              new Vector4(-xf, -yf, cascadeEnds[i + 1], 1.0)
          };
  
          // Store the frustum corners in light space
          var frustumCornersLS = new Vector4[8];
  
          // Transform frustum corners to world space
          for (int j = 0; j &lt; 8; j++)
              frustumCorners[j] *= worldViewProj;
  
          frustumCenters[i] = GetFrustumCenter(frustumCorners);
  
          // Create light view projection from from world-space frustum center
          lightViewSpaces[i] = Matrix4.CreateLookAt(frustumCenters[i] + LightDirection, frustumCenters[i], up);
  
          // Convert frustum corners to light-space
          for (int j = 0; j &lt; 8; j++)
              frustumCornersLS[j] = frustumCorners[j] * lightViewSpaces[i];
  
          // Calculate AABB of light-space frustum
          GetMinMax(frustumCornersLS, out Vector3 min, out Vector3 max);
  
          // Store AABB in orthoInfo
          orthoInfo[i].r = max.X;
          orthoInfo[i].l = min.X;
          orthoInfo[i].b = min.Y;
          orthoInfo[i].t = max.Y;
          orthoInfo[i].f = max.Z;
          orthoInfo[i].n = min.Z;
      }
  }
  </code></pre>
  
  <p>The part of this that that I think may be the problem is the conversion of the frustum corners to light space. Whilst it gets the location of the shadows right, it doesn't seem to cope when I change the camera angle:</p>
  
  <pre><code>// Create light view projection from from world-space frustum center
  lightViewSpaces[i] = Matrix4.CreateLookAt(frustumCenters[i] + LightDirection, frustumCenters[i], up);
  </code></pre>
  
  <p>Any help with this would be greatly appreciated. Happy to provide more images and code if need be.</p>