コード例 #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.Validate(_effect);
            context.ThrowIfCameraMissing();

            var graphicsDevice   = _effect.GraphicsDevice;
            var savedRenderState = new RenderStateSnapshot(graphicsDevice);

            graphicsDevice.DepthStencilState = DepthStencilState.None;
            graphicsDevice.RasterizerState   = RasterizerState.CullNone;
            graphicsDevice.BlendState        = GraphicsHelper.BlendStateAdd;

            var viewport = graphicsDevice.Viewport;

            _parameterViewportSize.SetValue(new Vector2(viewport.Width, viewport.Height));
            _parameterGBuffer0.SetValue(context.GBuffer0);
            _parameterGBuffer1.SetValue(context.GBuffer1);

            var    cameraNode     = context.CameraNode;
            Pose   cameraPose     = cameraNode.PoseWorld;
            Matrix viewProjection = (Matrix)cameraNode.View * cameraNode.Camera.Projection;

            // Update SceneNode.LastFrame for all visible nodes.
            int frame = context.Frame;

            context.CameraNode.LastFrame = frame;

            var isHdrEnabled = context.IsHdrEnabled();

            for (int i = 0; i < numberOfNodes; i++)
            {
                var lightNode = nodes[i] as LightNode;
                if (lightNode == null)
                {
                    continue;
                }

                var light = lightNode.Light as PointLight;
                if (light == null)
                {
                    continue;
                }

                // LightNode is visible in current frame.
                lightNode.LastFrame = frame;

                float hdrScale = isHdrEnabled ? light.HdrScale : 1;
                _parameterDiffuseColor.SetValue((Vector3)light.Color * light.DiffuseIntensity * hdrScale);
                _parameterSpecularColor.SetValue((Vector3)light.Color * light.SpecularIntensity * hdrScale);

                Pose lightPose = lightNode.PoseWorld;

                bool hasShadow = (lightNode.Shadow != null && lightNode.Shadow.ShadowMask != null);
                if (hasShadow)
                {
                    switch (lightNode.Shadow.ShadowMaskChannel)
                    {
                    case 0: _parameterShadowMaskChannel.SetValue(new Vector4(1, 0, 0, 0)); break;

                    case 1: _parameterShadowMaskChannel.SetValue(new Vector4(0, 1, 0, 0)); break;

                    case 2: _parameterShadowMaskChannel.SetValue(new Vector4(0, 0, 1, 0)); break;

                    default: _parameterShadowMaskChannel.SetValue(new Vector4(0, 0, 0, 1)); break;
                    }

                    _parameterShadowMask.SetValue(lightNode.Shadow.ShadowMask);
                }

                _parameterPosition.SetValue((Vector3)(lightPose.Position - cameraPose.Position));
                _parameterRange.SetValue(light.Range);
                _parameterAttenuation.SetValue(light.Attenuation);

                bool hasTexture = (light.Texture != null);
                if (hasTexture)
                {
                    _parameterTexture.SetValue(light.Texture);

                    // Cube maps are left handed --> Sample with inverted z. (Otherwise, the
                    // cube map and objects or texts in it are mirrored.)
                    var mirrorZ = Matrix.CreateScale(1, 1, -1);
                    _parameterTextureMatrix.SetValue((Matrix)(mirrorZ * lightPose.Inverse));
                }

                var rectangle           = GraphicsHelper.GetViewportRectangle(cameraNode, viewport, lightPose.Position, light.Range);
                var texCoordTopLeft     = new Vector2F(rectangle.Left / (float)viewport.Width, rectangle.Top / (float)viewport.Height);
                var texCoordBottomRight = new Vector2F(rectangle.Right / (float)viewport.Width, rectangle.Bottom / (float)viewport.Height);
                GraphicsHelper.GetFrustumFarCorners(cameraNode.Camera.Projection, texCoordTopLeft, texCoordBottomRight, _frustumFarCorners);

                // Convert frustum far corners from view space to world space.
                for (int j = 0; j < _frustumFarCorners.Length; j++)
                {
                    _frustumFarCorners[j] = (Vector3)cameraPose.ToWorldDirection((Vector3)_frustumFarCorners[j]);
                }

                _parameterFrustumCorners.SetValue(_frustumFarCorners);

                if (lightNode.Clip != null)
                {
                    var data = lightNode.RenderData as LightRenderData;
                    if (data == null)
                    {
                        data = new LightRenderData();
                        lightNode.RenderData = data;
                    }

                    data.UpdateClipSubmesh(context.GraphicsService, lightNode);

                    graphicsDevice.DepthStencilState = GraphicsHelper.DepthStencilStateOnePassStencilFail;
                    graphicsDevice.BlendState        = GraphicsHelper.BlendStateNoColorWrite;

                    _parameterWorldViewProjection.SetValue((Matrix)data.ClipMatrix * viewProjection);
                    _passClip.Apply();
                    data.ClipSubmesh.Draw();

                    graphicsDevice.DepthStencilState = lightNode.InvertClip
            ? GraphicsHelper.DepthStencilStateStencilEqual0
            : GraphicsHelper.DepthStencilStateStencilNotEqual0;
                    graphicsDevice.BlendState = GraphicsHelper.BlendStateAdd;
                }
                else
                {
                    graphicsDevice.DepthStencilState = DepthStencilState.None;
                }

                if (hasShadow)
                {
                    if (hasTexture)
                    {
                        if (light.Texture.Format == SurfaceFormat.Alpha8)
                        {
                            _passShadowedTexturedAlpha.Apply();
                        }
                        else
                        {
                            _passShadowedTexturedRgb.Apply();
                        }
                    }
                    else
                    {
                        _passShadowed.Apply();
                    }
                }
                else
                {
                    if (hasTexture)
                    {
                        if (light.Texture.Format == SurfaceFormat.Alpha8)
                        {
                            _passTexturedAlpha.Apply();
                        }
                        else
                        {
                            _passTexturedRgb.Apply();
                        }
                    }
                    else
                    {
                        _passDefault.Apply();
                    }
                }

                graphicsDevice.DrawQuad(rectangle);
            }

            savedRenderState.Restore();
        }
コード例 #2
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.Validate(_effect);
            context.ThrowIfCameraMissing();

            var graphicsDevice   = _effect.GraphicsDevice;
            var savedRenderState = new RenderStateSnapshot(graphicsDevice);

            graphicsDevice.DepthStencilState = DepthStencilState.None;
            graphicsDevice.RasterizerState   = RasterizerState.CullNone;
            graphicsDevice.BlendState        = GraphicsHelper.BlendStateAdd;

            var viewport = graphicsDevice.Viewport;

            _parameterViewportSize.SetValue(new Vector2(viewport.Width, viewport.Height));
            _parameterGBuffer0.SetValue(context.GBuffer0);
            _parameterGBuffer1.SetValue(context.GBuffer1);

            var    cameraNode     = context.CameraNode;
            Matrix viewProjection = (Matrix)cameraNode.View * cameraNode.Camera.Projection;

            var cameraPose = cameraNode.PoseWorld;

            GraphicsHelper.GetFrustumFarCorners(cameraNode.Camera.Projection, _cameraFrustumFarCorners);

            // Convert frustum far corners from view space to world space.
            for (int i = 0; i < _cameraFrustumFarCorners.Length; i++)
            {
                _cameraFrustumFarCorners[i] = (Vector3)cameraPose.ToWorldDirection((Vector3)_cameraFrustumFarCorners[i]);
            }

            _parameterFrustumCorners.SetValue(_cameraFrustumFarCorners);

            // Update SceneNode.LastFrame for all visible nodes.
            int frame = context.Frame;

            cameraNode.LastFrame = frame;

            var isHdrEnabled = context.IsHdrEnabled();

            for (int i = 0; i < numberOfNodes; i++)
            {
                var lightNode = nodes[i] as LightNode;
                if (lightNode == null)
                {
                    continue;
                }

                var light = lightNode.Light as DirectionalLight;
                if (light == null)
                {
                    continue;
                }

                // LightNode is visible in current frame.
                lightNode.LastFrame = frame;

                float hdrScale = isHdrEnabled ? light.HdrScale : 1;
                _parameterDiffuseColor.SetValue((Vector3)light.Color * light.DiffuseIntensity * hdrScale);
                _parameterSpecularColor.SetValue((Vector3)light.Color * light.SpecularIntensity * hdrScale);

                Pose    lightPose           = lightNode.PoseWorld;
                Vector3 lightDirectionWorld = lightPose.ToWorldDirection(Vector3.Forward);
                _parameterLightDirection.SetValue((Vector3)lightDirectionWorld);

                bool hasShadow = (lightNode.Shadow != null && lightNode.Shadow.ShadowMask != null);
                if (hasShadow)
                {
                    switch (lightNode.Shadow.ShadowMaskChannel)
                    {
                    case 0:  _parameterShadowMaskChannel.SetValue(new Vector4(1, 0, 0, 0)); break;

                    case 1:  _parameterShadowMaskChannel.SetValue(new Vector4(0, 1, 0, 0)); break;

                    case 2:  _parameterShadowMaskChannel.SetValue(new Vector4(0, 0, 1, 0)); break;

                    default: _parameterShadowMaskChannel.SetValue(new Vector4(0, 0, 0, 1)); break;
                    }

                    _parameterShadowMask.SetValue(lightNode.Shadow.ShadowMask);
                }

                bool hasTexture = (light.Texture != null);
                if (hasTexture)
                {
                    var textureProjection = Matrix.CreateOrthographicOffCenter(
                        -light.TextureOffset.X,
                        -light.TextureOffset.X + Math.Abs(light.TextureScale.X),
                        light.TextureOffset.Y,
                        light.TextureOffset.Y + Math.Abs(light.TextureScale.Y),
                        1,  // Not relevant
                        2); // Not relevant.
                    var scale = Matrix.CreateScale(Math.Sign(light.TextureScale.X), Math.Sign(light.TextureScale.Y), 1);

                    _parameterTextureMatrix.SetValue((Matrix)(GraphicsHelper.ProjectorBiasMatrix * scale * textureProjection * lightPose.Inverse));

                    _parameterTexture.SetValue(light.Texture);
                }

                if (lightNode.Clip != null)
                {
                    var data = lightNode.RenderData as LightRenderData;
                    if (data == null)
                    {
                        data = new LightRenderData();
                        lightNode.RenderData = data;
                    }

                    data.UpdateClipSubmesh(context.GraphicsService, lightNode);

                    graphicsDevice.DepthStencilState = GraphicsHelper.DepthStencilStateOnePassStencilFail;
                    graphicsDevice.BlendState        = GraphicsHelper.BlendStateNoColorWrite;

                    _parameterWorldViewProjection.SetValue((Matrix)data.ClipMatrix * viewProjection);
                    _passClip.Apply();
                    data.ClipSubmesh.Draw();

                    graphicsDevice.DepthStencilState = lightNode.InvertClip
            ? GraphicsHelper.DepthStencilStateStencilEqual0
            : GraphicsHelper.DepthStencilStateStencilNotEqual0;
                    graphicsDevice.BlendState = GraphicsHelper.BlendStateAdd;
                }
                else
                {
                    graphicsDevice.DepthStencilState = DepthStencilState.None;
                }

                if (hasShadow)
                {
                    if (hasTexture)
                    {
                        if (light.Texture.Format == SurfaceFormat.Alpha8)
                        {
                            _passShadowedTexturedAlpha.Apply();
                        }
                        else
                        {
                            _passShadowedTexturedRgb.Apply();
                        }
                    }
                    else
                    {
                        _passShadowed.Apply();
                    }
                }
                else
                {
                    if (hasTexture)
                    {
                        if (light.Texture.Format == SurfaceFormat.Alpha8)
                        {
                            _passTexturedAlpha.Apply();
                        }
                        else
                        {
                            _passTexturedRgb.Apply();
                        }
                    }
                    else
                    {
                        _passDefault.Apply();
                    }
                }

                graphicsDevice.DrawFullScreenQuad();
            }

            savedRenderState.Restore();
        }
コード例 #3
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.Validate(_effect);
            context.ThrowIfCameraMissing();

            var graphicsDevice   = _effect.GraphicsDevice;
            var savedRenderState = new RenderStateSnapshot(graphicsDevice);

            graphicsDevice.DepthStencilState = DepthStencilState.None;
            graphicsDevice.RasterizerState   = RasterizerState.CullNone;
            graphicsDevice.BlendState        = GraphicsHelper.BlendStateAdd;

            var viewport = graphicsDevice.Viewport;

            _parameterViewportSize.SetValue(new Vector2(viewport.Width, viewport.Height));
            _parameterGBuffer0.SetValue(context.GBuffer0);
            _parameterGBuffer1.SetValue(context.GBuffer1);

            var    cameraNode     = context.CameraNode;
            Matrix viewProjection = (Matrix)cameraNode.View * cameraNode.Camera.Projection;

            // Update SceneNode.LastFrame for all visible nodes.
            int frame = context.Frame;

            cameraNode.LastFrame = frame;

            var isHdrEnabled = context.IsHdrEnabled();

            for (int i = 0; i < numberOfNodes; i++)
            {
                var lightNode = nodes[i] as LightNode;
                if (lightNode == null)
                {
                    continue;
                }

                var light = lightNode.Light as AmbientLight;
                if (light == null)
                {
                    continue;
                }

                // LightNode is visible in current frame.
                lightNode.LastFrame = frame;

                float hdrScale = isHdrEnabled ? light.HdrScale : 1;
                _parameterLightColor.SetValue((Vector3)light.Color * light.Intensity * hdrScale);
                _parameterHemisphericAttenuation.SetValue(light.HemisphericAttenuation);

                Vector3 upWorld = lightNode.PoseWorld.ToWorldDirection(Vector3.Up);
                _parameterUp.SetValue((Vector3)upWorld);

                if (lightNode.Clip != null)
                {
                    var data = lightNode.RenderData as LightRenderData;
                    if (data == null)
                    {
                        data = new LightRenderData();
                        lightNode.RenderData = data;
                    }

                    data.UpdateClipSubmesh(context.GraphicsService, lightNode);

                    graphicsDevice.DepthStencilState = GraphicsHelper.DepthStencilStateOnePassStencilFail;
                    graphicsDevice.BlendState        = GraphicsHelper.BlendStateNoColorWrite;

                    _parameterWorldViewProjection.SetValue((Matrix)data.ClipMatrix * viewProjection);
                    _passClip.Apply();
                    data.ClipSubmesh.Draw();

                    graphicsDevice.DepthStencilState = lightNode.InvertClip
            ? GraphicsHelper.DepthStencilStateStencilEqual0
            : GraphicsHelper.DepthStencilStateStencilNotEqual0;
                    graphicsDevice.BlendState = GraphicsHelper.BlendStateAdd;
                }
                else
                {
                    graphicsDevice.DepthStencilState = DepthStencilState.None;
                }

                _passLight.Apply();
                graphicsDevice.DrawFullScreenQuad();
            }

            savedRenderState.Restore();
        }
コード例 #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.Validate(_effect);
            context.ThrowIfCameraMissing();

            var graphicsDevice   = _effect.GraphicsDevice;
            var savedRenderState = new RenderStateSnapshot(graphicsDevice);

            graphicsDevice.DepthStencilState = DepthStencilState.None;
            graphicsDevice.RasterizerState   = RasterizerState.CullNone;
            graphicsDevice.BlendState        = GraphicsHelper.BlendStateAdd;

            var viewport = graphicsDevice.Viewport;

            _parameterViewportSize.SetValue(new Vector2(viewport.Width, viewport.Height));
            _parameterGBuffer0.SetValue(context.GBuffer0);
            _parameterGBuffer1.SetValue(context.GBuffer1);

            var    cameraNode     = context.CameraNode;
            Pose   cameraPose     = cameraNode.PoseWorld;
            Matrix viewProjection = (Matrix)cameraNode.View * cameraNode.Camera.Projection;

            // Update SceneNode.LastFrame for all visible nodes.
            int frame = context.Frame;

            context.CameraNode.LastFrame = frame;

            bool isHdrEnabled = context.IsHdrEnabled();

            // Copy nodes to list and sort them by persistent IDs. This is necessary to avoid popping when
            // light probes overlap.
            _jobs.Clear();
            for (int i = 0; i < numberOfNodes; i++)
            {
                var lightNode = nodes[i] as LightNode;
                if (lightNode == null)
                {
                    continue;
                }

                var light = lightNode.Light as ImageBasedLight;
                if (light == null || light.Texture == null)
                {
                    continue;
                }

                // Build sort-ID - high values for lights which should be rendered last.
                ulong sortId = 0;

                // Render infinite lights first and others later.
                if (!(light.Shape is InfiniteShape))
                {
                    sortId += ((ulong)1 << 32); // Set high value above 32-bit range.
                }
                // Sort by priority. Lights with higher priority should be rendered last
                // (= over the other lights).
                // Shift priority (signed int) to positive range and add it.
                sortId += (ulong)((long)lightNode.Priority + int.MaxValue + 1);

                // Shift sortId and add light.Id in least significant bits.
                sortId = (sortId << 16) | (ushort)light.Id;

                // Add to list for sorting.
                _jobs.Add(new Job
                {
                    SortId    = sortId,
                    LightNode = lightNode,
                });
            }

            // Sort by ascending sort-ID value.
            _jobs.Sort(Comparer.Instance);

            numberOfNodes = _jobs.Count;
            for (int i = 0; i < numberOfNodes; i++)
            {
                var lightNode = _jobs[i].LightNode;
                var light     = (ImageBasedLight)lightNode.Light;

                // LightNode is visible in current frame.
                lightNode.LastFrame = frame;

                // ReSharper disable CompareOfFloatsByEqualityOperator
                bool enableDiffuse = !(Numeric.IsNaN(light.DiffuseIntensity) || (light.DiffuseIntensity == 0.0f && light.BlendMode == 0.0f));
                // ReSharper restore CompareOfFloatsByEqualityOperator

                bool enableSpecular = !Numeric.IsNaN(light.SpecularIntensity);
                if (!enableDiffuse && !enableSpecular)
                {
                    continue;
                }

                float hdrScale = isHdrEnabled ? light.HdrScale : 1;

                // We use 1x1 mipmap level for diffuse.
                // (2x2 is still okay, 4x4 already looks a bit like a specular reflection.)
                float diffuseIntensity = enableDiffuse ? light.DiffuseIntensity : 0.0f;
                _parameterParameters0.SetValue(new Vector4(
                                                   (Vector3)light.Color * diffuseIntensity * hdrScale, // DiffuseColor
                                                   Math.Max(0, light.Texture.LevelCount - 1)));        // Diffuse mip level.

                // Shader supports only RGBM.
                float rgbmMax;
                if (light.Encoding is RgbmEncoding)
                {
                    rgbmMax = GraphicsHelper.ToGamma(((RgbmEncoding)light.Encoding).Max);
                }
                else if (light.Encoding is SRgbEncoding)
                {
                    // Decoding RGBM with MaxValue 1 is equal to encoding sRGB, i.e. only
                    // gamma-to-linear is performed (assuming that the cube map alpha channel is 1).
                    rgbmMax = 1;
                }
                else
                {
                    throw new NotSupportedException(
                              "ImageBasedLight must use sRGB or RGBM encoding. Other encodings are not yet supported.");
                }

                _parameterParameters1.SetValue(new Vector4(
                                                   (Vector3)light.Color * light.SpecularIntensity * hdrScale, // SpecularColor
                                                   rgbmMax));

                // Bounding box can be a box shape or an infinite shape.
                var boundingBoxShape = lightNode.Shape as BoxShape;

                // Get extent of bounding box. For infinite shapes we simply set a large value.
                var boundingBoxExtent = boundingBoxShape != null
                              ? boundingBoxShape.Extent * lightNode.ScaleWorld
                              : new Vector3(1e20f);

                // Falloff can only be used for box shapes but not for infinite shapes.
                float falloffRange = (boundingBoxShape != null) ? light.FalloffRange : 0;

                // AABB for localization in local space.
                // Use invalid min and max (min > max) to disable localization.
                Aabb projectionAabb = new Aabb(new Vector3(1), new Vector3(-1));
                if (light.EnableLocalizedReflection)
                {
                    if (light.LocalizedReflectionBox.HasValue)
                    {
                        // User defined AABB.
                        projectionAabb          = light.LocalizedReflectionBox.Value;
                        projectionAabb.Minimum *= lightNode.ScaleWorld;
                        projectionAabb.Maximum *= lightNode.ScaleWorld;
                    }
                    else if (boundingBoxShape != null)
                    {
                        // AABB is equal to the bounding box.
                        projectionAabb = new Aabb(-boundingBoxExtent / 2, boundingBoxExtent / 2);
                    }
                }

                _parameterParameters2.SetValue(new Vector4(
                                                   boundingBoxExtent.X / 2,
                                                   boundingBoxExtent.Y / 2,
                                                   boundingBoxExtent.Z / 2,
                                                   falloffRange));

                _parameterParameters3.SetValue(new Vector4(
                                                   projectionAabb.Minimum.X,
                                                   projectionAabb.Minimum.Y,
                                                   projectionAabb.Minimum.Z,
                                                   light.Texture.Size));

                _parameterParameters4.SetValue(new Vector4(
                                                   projectionAabb.Maximum.X,
                                                   projectionAabb.Maximum.Y,
                                                   projectionAabb.Maximum.Z,
                                                   light.BlendMode));

                // Precomputed value for specular reflection lookup.
                const float sqrt3 = 1.7320508075688772935274463415059f;
                _parameterPrecomputedTerm.SetValue((float)Math.Log(light.Texture.Size * sqrt3, 2.0));

                _parameterEnvironmentMap.SetValue(light.Texture);

                // Compute screen space rectangle and FrustumFarCorners.
                var rectangle           = GraphicsHelper.GetViewportRectangle(cameraNode, viewport, lightNode);
                var texCoordTopLeft     = new Vector2F(rectangle.Left / (float)viewport.Width, rectangle.Top / (float)viewport.Height);
                var texCoordBottomRight = new Vector2F(rectangle.Right / (float)viewport.Width, rectangle.Bottom / (float)viewport.Height);
                GraphicsHelper.GetFrustumFarCorners(cameraNode.Camera.Projection, texCoordTopLeft, texCoordBottomRight, _frustumFarCorners);

                // Convert frustum far corners from view space to world space.
                for (int j = 0; j < _frustumFarCorners.Length; j++)
                {
                    _frustumFarCorners[j] = (Vector3)cameraPose.ToWorldDirection((Vector3)_frustumFarCorners[j]);
                }

                _parameterFrustumCorners.SetValue(_frustumFarCorners);

                EffectPass passLight = null;
                if (enableDiffuse && enableSpecular)
                {
                    passLight = _passDiffuseAndSpecularLight;
                }
                else if (enableDiffuse)
                {
                    // TODO: Can we disable writes to LightBuffer1?
                    passLight = _passDiffuseLight;
                }
                else
                {
                    // TODO: Can we disable writes to LightBuffer0?
                    passLight = _passSpecularLight;
                }

                // Simply render fullscreen quad if we do not have a clip shape or a bounding box.
                if (lightNode.Clip == null && boundingBoxShape == null)
                {
                    graphicsDevice.BlendState = BlendState.AlphaBlend;

                    // Transform matrix transforms from world space with camera as origin to
                    // local space. The lightNode.Scale is already in the other parameters and not
                    // used in Transform.
                    var pose = lightNode.PoseWorld;
                    pose.Position -= cameraPose.Position;
                    _parameterTransform.SetValue(pose.Inverse);

                    passLight.Apply();
                    graphicsDevice.DrawFullScreenQuad();
                    continue;
                }

                // ----- Render clip mesh.
                graphicsDevice.DepthStencilState = GraphicsHelper.DepthStencilStateOnePassStencilFail;
                graphicsDevice.BlendState        = GraphicsHelper.BlendStateNoColorWrite;
                if (lightNode.Clip != null)
                {
                    // Using user-defined clip shape.
                    var data = lightNode.RenderData as LightRenderData;
                    if (data == null)
                    {
                        data = new LightRenderData();
                        lightNode.RenderData = data;
                    }

                    data.UpdateClipSubmesh(context.GraphicsService, lightNode);
                    _parameterTransform.SetValue((Matrix)data.ClipMatrix * viewProjection);
                    _passClip.Apply();
                    data.ClipSubmesh.Draw();

                    graphicsDevice.DepthStencilState = lightNode.InvertClip
            ? GraphicsHelper.DepthStencilStateStencilEqual0
            : GraphicsHelper.DepthStencilStateStencilNotEqual0;
                }
                else
                {
                    Debug.Assert(boundingBoxShape != null);

                    // Use box submesh.
                    if (_boxSubmesh == null)
                    {
                        _boxSubmesh = MeshHelper.GetBox(context.GraphicsService);
                    }

                    Matrix world = lightNode.PoseWorld
                                   * Matrix.CreateScale(lightNode.ScaleLocal * boundingBoxShape.Extent);
                    _parameterTransform.SetValue((Matrix)world * viewProjection);

                    _passClip.Apply();
                    _boxSubmesh.Draw();

                    graphicsDevice.DepthStencilState = GraphicsHelper.DepthStencilStateStencilNotEqual0;
                }

                graphicsDevice.BlendState = BlendState.AlphaBlend;

                {
                    // Transform matrix transforms from world space with camera as origin to
                    // local space. The lightNode.Scale is already in the other parameters and not
                    // used in Transform.
                    var pose = lightNode.PoseWorld;
                    pose.Position -= cameraPose.Position;
                    _parameterTransform.SetValue(pose.Inverse);
                }

                // ----- Render full screen quad.
                passLight.Apply();
                graphicsDevice.DrawQuad(rectangle);
            }

            savedRenderState.Restore();
            _jobs.Clear();
        }