/// <summary> /// Determines the size of the frustum needed to cover the viewable area, /// then creates an appropriate orthographic projection. /// </summary> /// <param name="light">The directional light to use</param> /// <param name="mainCamera">The camera viewing the scene</param> /// <param name="minZ"></param> /// <param name="maxZ"></param> protected OrthographicCamera CalculateFrustum(DirectionalLight light, ICamera mainCamera, float minZ, float maxZ) { // Shorten the view frustum according to the shadow view distance Matrix cameraMatrix; cameraMatrix = mainCamera.ViewInverse; //mainCamera.GetWorldMatrix( out cameraMatrix ); for (int i = 0; i < 4; i++) { splitFrustumCornersVS[i] = frustumCornersVS[i + 4] * (minZ / mainCamera.FarClip); } for (int i = 4; i < 8; i++) { splitFrustumCornersVS[i] = frustumCornersVS[i] * (maxZ / mainCamera.FarClip); } Vector3.TransformCoordinate(splitFrustumCornersVS, ref cameraMatrix, frustumCornersWS); // Find the centroid Vector3 frustumCentroid = new Vector3(0, 0, 0); for (int i = 0; i < 8; i++) { frustumCentroid += frustumCornersWS[i]; } frustumCentroid *= (1 / 8.0F); // Position the shadow-caster camera so that it's looking at the centroid, // and backed up in the direction of the sunlight float distFromCentroid = MathHelper.Max((maxZ - minZ), Vector3.Distance(splitFrustumCornersVS[4], splitFrustumCornersVS[5])) + 50.0f; Matrix viewMatrix = Matrix.LookAtRH(frustumCentroid - (light.Direction * distFromCentroid), frustumCentroid, new Vector3(0, 1, 0)); // Determine the position of the frustum corners in light space Vector3.TransformCoordinate(frustumCornersWS, ref viewMatrix, frustumCornersLS); // Calculate an orthographic projection by sizing a bounding box // to the frustum coordinates in light space 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; } } // Create an orthographic camera for use as a shadow caster const float nearClipOffset = 100.0f; OrthographicCamera lightCamera = new OrthographicCamera(mins.X, maxes.X, mins.Y, maxes.Y, -maxes.Z - nearClipOffset, -mins.Z); lightCamera.View = viewMatrix; if (RenderDebug) { game.LineManager3D.AddCenteredBox(frustumCentroid - (light.Direction * distFromCentroid), 1.0f, new Color4(0, 1, 0)); game.LineManager3D.AddLine(frustumCentroid - (light.Direction * distFromCentroid), frustumCentroid, new Color4(1, 1, 0)); game.LineManager3D.AddCenteredBox(frustumCentroid, 1.0f, new Color4(1, 0, 0)); game.LineManager3D.AddViewFrustum(new BoundingFrustum(mainCamera.ViewProjection), new Color4(1, 0, 1)); game.LineManager3D.AddViewFrustum(frustumCornersWS, new Color4(1, 0, 0)); Vector3[] temps = new Vector3[8]; Matrix tm = Matrix.Invert(viewMatrix); BoundingBox bb = new BoundingBox(mins, maxes); Vector3[] temps2 = bb.GetCorners(); Vector3.TransformCoordinate(temps2, ref tm, temps); game.LineManager3D.AddViewFrustum(temps, new Color4(0, 1, 0)); game.LineManager3D.AddViewFrustum(new BoundingFrustum(lightCamera.ViewProjection), new Color4(1, 0, 1)); } return(lightCamera); }
/// <summary> /// Renders a list of models to the shadow map, and returns a surface /// containing the shadow occlusion factor /// </summary> /// <param name="modelList">The list of models to render</param> /// <param name="depthTexture">Texture containing depth for the scene</param> /// <param name="light">The light for which the shadow is being calculated</param> /// <param name="mainCamera">The camera viewing the scene containing the light</param> /// <returns>The shadow occlusion texture</returns> public void UpdateShadowMap(RenderPrimitives renderDelegate, DirectionalLight light, ICamera mainCamera) { context.ClearDepthStencilView(shadowMapDsv, DepthStencilClearFlags.Depth, 1, 0); context.ClearState(); context.OutputMerger.SetTargets(shadowMapDsv); // Get corners of the main camera's bounding frustum Matrix cameraTransform, viewMatrix; viewMatrix = mainCamera.View; cameraTransform = Matrix.Invert(viewMatrix); BoundingFrustum frustum = new BoundingFrustum(mainCamera.ViewProjection); frustum.GetCorners(frustumCornersWS); Vector3.TransformCoordinate(frustumCornersWS, ref viewMatrix, frustumCornersVS); for (int i = 0; i < 4; i++) { farFrustumCornersVS[i] = frustumCornersVS[i + 4]; } // Calculate the cascade splits. We calculate these so that each successive // split is larger than the previous, giving the closest split the most amount // of shadow detail. float N = NumSplits; float near = mainCamera.NearClip, far = mainCamera.FarClip; splitDepths[0] = near; splitDepths[NumSplits] = far; const float splitConstant = 0.95f; for (int i = 1; i < splitDepths.Length - 1; i++) { splitDepths[i] = splitConstant * near * (float)Math.Pow(far / near, i / N) + (1.0f - splitConstant) * ((near + (i / N)) * (far - near)); } // Render our scene geometry to each split of the cascade for (int i = 0; i < NumSplits; i++) { float minZ = splitDepths[i]; float maxZ = splitDepths[i + 1]; lightCameras[i] = CalculateFrustum(light, mainCamera, minZ, maxZ); renderShadowMap(renderDelegate, i); } // We'll use these clip planes to determine which split a pixel belongs to for (int i = 0; i < NumSplits; i++) { lightClipPlanes[i].X = -splitDepths[i]; lightClipPlanes[i].Y = -splitDepths[i + 1]; lightViewProjectionMatrices[i] = lightCameras[i].ViewProjection; //lightProjectionMatrices[i] = lightCameras[i].Projection; //lightCameras[ i ].GetViewProjMatrix( out ); } }