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);
        }
Exemple #4
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;
        }
Exemple #6
0
        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));
        }
Exemple #7
0
 public void SetFieldOfViewException5()
 {
     PerspectiveViewVolume frustum = new PerspectiveViewVolume();
       frustum.SetFieldOfView(MathHelper.ToRadians(60), 16.0f / 9.0f, 1, 0);
 }
Exemple #8
0
        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);
        }