public void SetFieldOfView2Test() { PerspectiveViewVolume frustum = new PerspectiveViewVolume(); frustum.SetFieldOfView(MathHelper.ToRadians(60), 16.0f / 9.0f, 1, 10); PerspectiveViewVolume frustum2 = new PerspectiveViewVolume { Near = 1, Far = 10 }; frustum2.SetFieldOfView(MathHelper.ToRadians(60), 16.0f / 9.0f); Assert.AreEqual(frustum.Near, frustum2.Near); Assert.AreEqual(frustum.Far, frustum2.Far); }
public void SetFieldOfViewTest() { PerspectiveViewVolume frustum = new PerspectiveViewVolume(); frustum.SetFieldOfView(MathHelper.ToRadians(60), 16.0f / 9.0f, 1, 10); Assert.IsTrue(Numeric.AreEqual(-2.0528009f / 2.0f, frustum.Left)); Assert.IsTrue(Numeric.AreEqual(2.0528009f / 2.0f, frustum.Right)); Assert.IsTrue(Numeric.AreEqual(-1.1547005f / 2.0f, frustum.Bottom)); Assert.IsTrue(Numeric.AreEqual(1.1547005f / 2.0f, frustum.Top)); Assert.AreEqual(1, frustum.Near); Assert.AreEqual(10, frustum.Far); Assert.IsTrue(Numeric.AreEqual(2.0528009f, frustum.Width)); Assert.IsTrue(Numeric.AreEqual(1.1547005f, frustum.Height)); Assert.AreEqual(9, frustum.Depth); Assert.AreEqual(16.0f / 9.0f, frustum.AspectRatio); Assert.IsTrue(Numeric.AreEqual(MathHelper.ToRadians(91.492843f), frustum.FieldOfViewX)); Assert.IsTrue(Numeric.AreEqual(MathHelper.ToRadians(60), frustum.FieldOfViewY)); }
public void SetFieldOfViewException5() { PerspectiveViewVolume frustum = new PerspectiveViewVolume(); frustum.SetFieldOfView(MathHelper.ToRadians(60), 16.0f / 9.0f, 1, 0); }
public override void Render(IList <SceneNode> nodes, RenderContext context, RenderOrder order) { if (nodes == null) { throw new ArgumentNullException("nodes"); } if (context == null) { throw new ArgumentNullException("context"); } int numberOfNodes = nodes.Count; if (numberOfNodes == 0) { return; } context.ThrowIfCameraMissing(); context.ThrowIfSceneMissing(); var originalRenderTarget = context.RenderTarget; var originalViewport = context.Viewport; var originalReferenceNode = context.ReferenceNode; // Camera properties var cameraNode = context.CameraNode; var cameraPose = cameraNode.PoseWorld; var projection = cameraNode.Camera.Projection; if (!(projection is PerspectiveProjection)) { throw new NotImplementedException( "Cascaded shadow maps not yet implemented for scenes with orthographic camera."); } float fieldOfViewY = projection.FieldOfViewY; float aspectRatio = projection.AspectRatio; // Update SceneNode.LastFrame for all visible nodes. int frame = context.Frame; cameraNode.LastFrame = frame; // The scene node renderer should use the light camera instead of the player camera. context.CameraNode = _orthographicCameraNode; context.Technique = "Directional"; var graphicsService = context.GraphicsService; var graphicsDevice = graphicsService.GraphicsDevice; var savedRenderState = new RenderStateSnapshot(graphicsDevice); for (int i = 0; i < numberOfNodes; i++) { var lightNode = nodes[i] as LightNode; if (lightNode == null) { continue; } var shadow = lightNode.Shadow as CascadedShadow; if (shadow == null) { continue; } // LightNode is visible in current frame. lightNode.LastFrame = frame; var format = new RenderTargetFormat( shadow.PreferredSize * shadow.NumberOfCascades, shadow.PreferredSize, false, shadow.Prefer16Bit ? SurfaceFormat.HalfSingle : SurfaceFormat.Single, DepthFormat.Depth24); bool allLocked = shadow.IsCascadeLocked[0] && shadow.IsCascadeLocked[1] && shadow.IsCascadeLocked[2] && shadow.IsCascadeLocked[3]; if (shadow.ShadowMap == null) { shadow.ShadowMap = graphicsService.RenderTargetPool.Obtain2D(format); allLocked = false; // Need to render shadow map. } // If we can reuse the whole shadow map texture, abort early. if (allLocked) { continue; } _csmSplitDistances[0] = projection.Near; _csmSplitDistances[1] = shadow.Distances.X; _csmSplitDistances[2] = shadow.Distances.Y; _csmSplitDistances[3] = shadow.Distances.Z; _csmSplitDistances[4] = shadow.Distances.W; // (Re-)Initialize the array for cached matrices in the CascadedShadow. if (shadow.ViewProjections == null || shadow.ViewProjections.Length < shadow.NumberOfCascades) { shadow.ViewProjections = new Matrix[shadow.NumberOfCascades]; } // Initialize the projection matrices to an empty matrix. // The unused matrices should not contain valid projections because // CsmComputeSplitOptimized in CascadedShadowMask.fxh should not choose // the wrong cascade. for (int j = 0; j < shadow.ViewProjections.Length; j++) { if (!shadow.IsCascadeLocked[j]) // Do not delete cached info for cached cascade. { shadow.ViewProjections[j] = new Matrix(); } } // If some cascades are cached, we have to create a new shadow map and copy // the old cascades into the new shadow map. if (shadow.IsCascadeLocked[0] || shadow.IsCascadeLocked[1] || shadow.IsCascadeLocked[2] || shadow.IsCascadeLocked[3]) { var oldShadowMap = shadow.ShadowMap; shadow.ShadowMap = graphicsService.RenderTargetPool.Obtain2D(new RenderTargetFormat(oldShadowMap)); graphicsDevice.SetRenderTarget(shadow.ShadowMap); graphicsDevice.Clear(Color.White); var spriteBatch = graphicsService.GetSpriteBatch(); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullNone); for (int cascade = 0; cascade < shadow.NumberOfCascades; cascade++) { if (shadow.IsCascadeLocked[cascade]) { var viewport = GetViewport(shadow, cascade); var rectangle = new Rectangle(viewport.X, viewport.Y, viewport.Width, viewport.Height); spriteBatch.Draw(oldShadowMap, rectangle, rectangle, Color.White); } } spriteBatch.End(); graphicsService.RenderTargetPool.Recycle(oldShadowMap); } else { graphicsDevice.SetRenderTarget(shadow.ShadowMap); graphicsDevice.Clear(Color.White); } context.RenderTarget = shadow.ShadowMap; graphicsDevice.DepthStencilState = DepthStencilState.Default; graphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; graphicsDevice.BlendState = BlendState.Opaque; context.ReferenceNode = lightNode; context.Object = shadow; context.ShadowNear = 0; // Obsolete: Only kept for backward compatibility. bool shadowMapContainsSomething = false; for (int split = 0; split < shadow.NumberOfCascades; split++) { if (shadow.IsCascadeLocked[split]) { continue; } context.Data[RenderContextKeys.ShadowTileIndex] = CubeMapShadowMapRenderer.BoxedIntegers[split]; // near/far of this split. float near = _csmSplitDistances[split]; float far = Math.Max(_csmSplitDistances[split + 1], near + Numeric.EpsilonF); // Create a view volume for this split. _splitVolume.SetFieldOfView(fieldOfViewY, aspectRatio, near, far); // Find the bounding sphere of the split camera frustum. Vector3 center; float radius; GetBoundingSphere(_splitVolume, out center, out radius); // Extend radius to get enough border for filtering. int shadowMapSize = shadow.ShadowMap.Height; // We could extend by (ShadowMapSize + BorderTexels) / ShadowMapSize; // Add at least 1 texel. (This way, shadow mask shader can clamp uv to // texture rect in without considering half texel border to avoid sampling outside..) radius *= (float)(shadowMapSize + 1) / shadowMapSize; // Convert center to light space. Pose lightPose = lightNode.PoseWorld; center = cameraPose.ToWorldPosition(center); center = lightPose.ToLocalPosition(center); // Snap center to texel positions to avoid shadow swimming. SnapPositionToTexels(ref center, 2 * radius, shadowMapSize); // Convert center back to world space. center = lightPose.ToWorldPosition(center); Matrix orientation = lightPose.Orientation; Vector3 backward = orientation.GetColumn(2); var orthographicProjection = (OrthographicProjection)_orthographicCameraNode.Camera.Projection; // Create a tight orthographic frustum around the cascade's bounding sphere. orthographicProjection.SetOffCenter(-radius, radius, -radius, radius, 0, 2 * radius); Vector3 cameraPosition = center + radius * backward; Pose frustumPose = new Pose(cameraPosition, orientation); Pose view = frustumPose.Inverse; shadow.ViewProjections[split] = (Matrix)view * orthographicProjection; // Convert depth bias from "texel" to light space [0, 1] depth. // Minus sign to move receiver depth closer to light. Divide by depth to normalize. float unitsPerTexel = orthographicProjection.Width / shadow.ShadowMap.Height; shadow.EffectiveDepthBias[split] = -shadow.DepthBias[split] * unitsPerTexel / orthographicProjection.Depth; // Convert normal offset from "texel" to world space. shadow.EffectiveNormalOffset[split] = shadow.NormalOffset[split] * unitsPerTexel; // For rendering the shadow map, move near plane back by MinLightDistance // to catch occluders in front of the cascade. orthographicProjection.Near = -shadow.MinLightDistance; _orthographicCameraNode.PoseWorld = frustumPose; // Set a viewport to render a tile in the texture atlas. graphicsDevice.Viewport = GetViewport(shadow, split); context.Viewport = graphicsDevice.Viewport; shadowMapContainsSomething |= RenderCallback(context); } // Recycle shadow map if empty. if (!shadowMapContainsSomething) { graphicsService.RenderTargetPool.Recycle(shadow.ShadowMap); shadow.ShadowMap = null; } } graphicsDevice.SetRenderTarget(null); savedRenderState.Restore(); context.CameraNode = cameraNode; context.ShadowNear = float.NaN; context.Technique = null; context.RenderTarget = originalRenderTarget; context.Viewport = originalViewport; context.ReferenceNode = originalReferenceNode; context.Object = null; context.Data[RenderContextKeys.ShadowTileIndex] = null; }
public override void Render(IList <SceneNode> nodes, RenderContext context, RenderOrder order) { if (nodes == null) { throw new ArgumentNullException("nodes"); } if (context == null) { throw new ArgumentNullException("context"); } int numberOfNodes = nodes.Count; if (numberOfNodes == 0) { return; } Debug.Assert(context.CameraNode != null, "A camera node has to be set in the render context."); Debug.Assert(context.Scene != null, "A scene has to be set in the render context."); var originalRenderTarget = context.RenderTarget; var originalViewport = context.Viewport; var originalReferenceNode = context.ReferenceNode; // Camera properties var cameraNode = context.CameraNode; var cameraPose = cameraNode.PoseWorld; var projection = cameraNode.Camera.Projection; if (!(projection is PerspectiveProjection)) { throw new NotImplementedException("VSM shadow maps not yet implemented for scenes with perspective camera."); } float fieldOfViewY = projection.FieldOfViewY; float aspectRatio = projection.AspectRatio; // Update SceneNode.LastFrame for all rendered nodes. int frame = context.Frame; cameraNode.LastFrame = frame; // The scene node renderer should use the light camera instead of the player camera. context.CameraNode = _orthographicCameraNode; // The shadow map is rendered using the technique "DirectionalVsm". // See ShadowMap.fx in the DigitalRune source code folder. context.Technique = "DirectionalVsm"; var graphicsService = context.GraphicsService; var graphicsDevice = graphicsService.GraphicsDevice; var originalBlendState = graphicsDevice.BlendState; var originalDepthStencilState = graphicsDevice.DepthStencilState; var originalRasterizerState = graphicsDevice.RasterizerState; for (int i = 0; i < numberOfNodes; i++) { var lightNode = nodes[i] as LightNode; if (lightNode == null) { continue; } var shadow = lightNode.Shadow as VarianceShadow; if (shadow == null) { continue; } // LightNode is visible in current frame. lightNode.LastFrame = frame; // The format of the shadow map: var format = new RenderTargetFormat( shadow.PreferredSize, shadow.PreferredSize, false, shadow.Prefer16Bit ? SurfaceFormat.HalfVector2 : SurfaceFormat.Vector2, // VSM needs two channels! DepthFormat.Depth24); if (shadow.ShadowMap != null && shadow.IsLocked) { continue; } if (shadow.ShadowMap == null) { shadow.ShadowMap = graphicsService.RenderTargetPool.Obtain2D(format); } graphicsDevice.DepthStencilState = DepthStencilState.Default; graphicsDevice.BlendState = BlendState.Opaque; // Render front and back faces for VSM due to low shadow map texel density. // (VSM is usually used for distant geometry.) graphicsDevice.RasterizerState = RasterizerState.CullNone; graphicsDevice.SetRenderTarget(shadow.ShadowMap); context.RenderTarget = shadow.ShadowMap; context.Viewport = graphicsDevice.Viewport; graphicsDevice.Clear(Color.White); // Compute an orthographic camera for the light. // If Shadow.TargetArea is null, the shadow map should cover the area in front of the player camera. // If Shadow.TargetArea is set, the shadow map should cover this static area. if (shadow.TargetArea == null) { // near/far of this shadowed area. float near = projection.Near; float far = shadow.MaxDistance; // Abort if near-far distances are invalid. if (Numeric.IsGreaterOrEqual(near, far)) { continue; } // Create a view volume for frustum part that is covered by the shadow map. _cameraVolume.SetFieldOfView(fieldOfViewY, aspectRatio, near, far); // Find the bounding sphere of the frustum part. Vector3 center; float radius; GetBoundingSphere(_cameraVolume, out center, out radius); // Convert center to light space. Pose lightPose = lightNode.PoseWorld; center = cameraPose.ToWorldPosition(center); center = lightPose.ToLocalPosition(center); // Snap center to texel positions to avoid shadow swimming. SnapPositionToTexels(ref center, 2 * radius, shadow.ShadowMap.Height); // Convert center back to world space. center = lightPose.ToWorldPosition(center); Matrix orientation = lightPose.Orientation; Vector3 backward = orientation.GetColumn(2); var orthographicProjection = (OrthographicProjection)_orthographicCameraNode.Camera.Projection; // Create a tight orthographic frustum around the target bounding sphere. orthographicProjection.SetOffCenter(-radius, radius, -radius, radius, 0, 2 * radius); Vector3 cameraPosition = center + radius * backward; Pose frustumPose = new Pose(cameraPosition, orientation); Pose view = frustumPose.Inverse; shadow.ViewProjection = (Matrix)view * orthographicProjection; // For rendering the shadow map, move near plane back by MinLightDistance // to catch occluders in front of the camera frustum. orthographicProjection.Near = -shadow.MinLightDistance; _orthographicCameraNode.PoseWorld = frustumPose; } else { // Get bounding sphere of static target area. Aabb targetAabb = shadow.TargetArea.Value; Vector3 center = targetAabb.Center; float radius = (targetAabb.Maximum - center).Length; // Convert center to light space. Matrix orientation = lightNode.PoseWorld.Orientation; Vector3 backward = orientation.GetColumn(2); var orthographicProjection = (OrthographicProjection)_orthographicCameraNode.Camera.Projection; // Create a tight orthographic frustum around the target bounding sphere. orthographicProjection.SetOffCenter(-radius, radius, -radius, radius, 0, 2 * radius); Vector3 cameraPosition = center + radius * backward; Pose frustumPose = new Pose(cameraPosition, orientation); Pose view = frustumPose.Inverse; shadow.ViewProjection = (Matrix)view * orthographicProjection; // For rendering the shadow map, move near plane back by MinLightDistance // to catch occluders in front of the camera frustum. orthographicProjection.Near = -shadow.MinLightDistance; _orthographicCameraNode.PoseWorld = frustumPose; } context.ReferenceNode = lightNode; context.Object = shadow; // Render objects into shadow map. bool shadowMapContainsSomething = RenderCallback(context); if (shadowMapContainsSomething) { // Blur shadow map. if (shadow.Filter != null && shadow.Filter.Scale > 0) { context.SourceTexture = shadow.ShadowMap; shadow.Filter.Process(context); context.SourceTexture = null; } } else { // Shadow map is empty. Recycle it. graphicsService.RenderTargetPool.Recycle(shadow.ShadowMap); shadow.ShadowMap = null; } } graphicsDevice.SetRenderTarget(null); graphicsDevice.BlendState = originalBlendState; graphicsDevice.DepthStencilState = originalDepthStencilState; graphicsDevice.RasterizerState = originalRasterizerState; context.CameraNode = cameraNode; context.Technique = null; context.RenderTarget = originalRenderTarget; context.Viewport = originalViewport; context.ReferenceNode = originalReferenceNode; context.Object = null; }