1
\$\begingroup\$

I have run into a bit of trouble when using a custom shader (gaussian effect) within another drawable game component, as I cant figure out how to break out of the draw method, so the effect is applied to everything.

Basically, I have a WP8 project where the game class merely instantiates two separate DrawableGameComponents

        // Resource used for loading the different screens.
    private ScreenManager mScreenManager;

    public static BloomComponent BloomComponent;

    ....

        // Initialize the bloom component.
        BloomComponent = new BloomComponent(this);
        Components.Add(BloomComponent);
        BloomComponent.Settings = new BloomSettings(null, 0.25f, 4, 2, 1, 1.5f, 1);

        // Initialize the screen manager.
        mScreenManager = new ScreenManager(this);
        Components.Add(mScreenManager);

and the draw method of the game class itself doesn't actually do anything - it leaves it up to the components initialized when the game is instantiated.

The screen manager then renders the "Gameplay" screen which goes through and draws all the game sprites and some GUI text for the score etc, which is all fine.

I am then trying to use the BloomComponent via a static call to the Game class's BloomComponent instance, within the draw method of my "gameplay" screen, which works and applies the effect. The only problem is it applies the effect to everything, including the GUI text for the score; I only want to apply it to the sprites being rendered. Hence, I am basically trying to terminate the draw call for the BloomComponent. I had taken the BloomCoponent from an online sample, which appears to break the effects draw by calling base.Draw(gameTime). However, from within the Draw() method for my screen I cannot access the xna.Game Draw() due to the way the ScreenManager renders the screens:

    /// <summary>
    /// Tells each screen to draw itself.
    /// </summary>
    public override void Draw(GameTime gameTime)
    {
        // Draw all of the game screens.
        foreach (GameScreen screen in mScreens)
        {
            // If the screen is hidden, don't draw it.
            if (screen.ScreenState == ScreenState.Hidden)
                continue;

            // Draw the screen.
            screen.Draw(gameTime);
        }
    }

So my question is,is there a way to break out/stop a drawable game component, so that I can stop the effect from being applied to everything?

Here is my GameplayScreen Class, and the BloomComponent for reference:

GameplayScreen.cs

#region Using Statements

using System;
using System.IO;
using System.Collections;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Input.Touch;
using System.IO.IsolatedStorage;
using Microsoft.Xna.Framework.Storage;
using System.Xml.Serialization;
using System.Collections.Generic;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Controls;
using System.Windows;
using Vision.ScreenPad;

#endregion

namespace Vision
{
    /// <summary>
    /// This screen implements the main game logic. It provides access functionality to
    /// load, render and update the level and player objects. It also checks and handles user
    /// input.
    /// </summary>
    class GameplayScreen : GameScreen
    {

        #region Fields/Properties

        // Resource for loading content.
        public ContentManager Content
        {
            get { return mContent; }
        }
        private ContentManager mContent;

        // Resource for drawring content.
        private SpriteBatch mSpriteBatch;

        // The screens viewport.
        public static Viewport Viewport;

        public static Vector2 ScreenSize { get { return new Vector2(Viewport.Width, Viewport.Height); } }

        public static ParticleManager<ParticleState> ParticleManager { get; private set; }

        public static GameTime GameTime { get; private set; }

        public static BackgroundGrid BackgroundGrid { get; private set; }

        private static BloomComponent BloomComponent;

        #endregion

        //////////////////////////

        // The on screen game pad
        public static Vision.ScreenPad.ScreenPad ScreenPad;

        // The current state of the screen pad 
        Vision.ScreenPad.ScreenPadState CurrentScreenPadState;


        #region Initialization

        /// <summary>
        /// Constructor to create a new gameplay screen.
        /// </summary>
        /// <param name="levelIndex">The numeric index for the level to be loaded.</param>
        /// <remarks>
        /// The actual level file loaded will be value of the index incremented by one.
        /// </remarks>
        public GameplayScreen()
        {
            // Set the transition time for when the screen is loaded and unloaded.
            TransitionOnTime = TimeSpan.FromSeconds(1.0);
            TransitionOffTime = TimeSpan.FromSeconds(0.3);

#if WINDOWS_PHONE
            PhoneApplicationService.Current.Deactivated += GameDeactivated;
            App.RootFrame.Obscured += GameObscured;
#endif
        }

        #endregion

        #region Loading

        /// <summary>
        /// Loads all of the gameplay screens cotent.
        /// </summary>
        /// <remarks>
        /// The level object loads the player and all tile content contained within the level
        /// file, which is retrieved using the incrememnted level index.
        /// </remarks>
        public override void LoadContent()
        {
            if (mContent == null)
                mContent = new ContentManager(ScreenManager.Game.Services, "Content");

            // Create a new SpriteBatch which will be used to draw the level and player textures.
            mSpriteBatch = new SpriteBatch(ScreenManager.GraphicsDevice);

            // Load the viewport.
            Viewport = ScreenManager.GraphicsDevice.Viewport;

            ScreenPad = new ScreenPad.ScreenPad(Viewport,
                Content.Load<Texture2D>("Overlays/Controller/ThumbBase"),
                Content.Load<Texture2D>("Overlays/Controller/ThumbStick"),
                Content.Load<Texture2D>("Overlays/Controller/Dpad_All"),
                Color.Blue,
                Color.Red,
                Color.Green
                );

            // Load the art objects
            Art.Load(mContent);

            // Load the sound objects
            Sound.Load(Content);


            ///////////////////////////////////////////////////


            //BloomComponent = new BloomComponent(ScreenManager.Game);
            //ScreenManager.Game.Components.Add(BloomComponent);

            //BloomComponent.Settings = new BloomSettings(null, 0.25f, 4, 2, 1, 1.5f, 1);


            //////////////////////////////////////////////////

            ParticleManager = new ParticleManager<ParticleState>(1024 * 20, ParticleState.UpdateParticle);

            const int maxGridPoints = 1500;
            Vector2 gridSpacing = new Vector2((float)Math.Sqrt(Viewport.Width * Viewport.Height / maxGridPoints));
            BackgroundGrid = new BackgroundGrid(Viewport.Bounds, gridSpacing);

            EntityManager.Add(PlayerShip.Instance);
        }

        /// <summary>
        /// Removes all graphics content used by the gameplay screen.
        /// </summary>
        public override void UnloadContent()
        {
            mContent.Unload();
        }

        #endregion

        #region Rendering

        /// <summary>
        /// This is called when the gameplay screen should draw all of the loaded content.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        /// <remarks>
        /// The spritebatch is passed to the level object to localise the rendering of level content. 
        /// The same spritebatch is also passed to the player object to allow it to render it's content.
        /// </remarks>
        public override void Draw(GameTime gameTime)
        {

            //////////////////////////////////////////////////////

            GameRoot.BloomComponent.BeginDraw();

            // Draw entities. Sort by texture for better batching.
            mSpriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);
            EntityManager.Draw(mSpriteBatch);
            mSpriteBatch.End();

            ///////////////////////////////////////////////////

            mSpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
            BackgroundGrid.Draw(mSpriteBatch);
            ParticleManager.Draw(mSpriteBatch);
            mSpriteBatch.End();

            ////////////////////////////////////////////////////

            mSpriteBatch.Begin();

            ScreenPad.Draw(gameTime, mSpriteBatch);

            mSpriteBatch.End();

            base.Draw(gameTime);

            // I want to end it here, but the call to base just calls the virtual Draw method for the Game clas which has no functionality at all 

            //////////////////////////////////////////////////


            // Draw user interface
            mSpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);

            mSpriteBatch.DrawString(Art.Font, "Lives: " + PlayerStatus.Lives, new Vector2(5), Color.White);
            DrawRightAlignedString("Score: " + PlayerStatus.Score, 5);
            DrawRightAlignedString("Multiplier: " + PlayerStatus.Multiplier, 35);

            if (PlayerStatus.IsGameOver)
            {
                string text = "Game Over\n" +
                    "Your Score: " + PlayerStatus.Score + "\n" +
                    "High Score: " + PlayerStatus.HighScore;

                Vector2 textSize = Art.Font.MeasureString(text);
                mSpriteBatch.DrawString(Art.Font, text, ScreenSize / 2 - textSize / 2, Color.White);
            }

            mSpriteBatch.End();

            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0)
                ScreenManager.FadeBackBufferToBlack(1f - TransitionAlpha);
        }

        private void DrawRightAlignedString(string text, float y)
        {
            var textWidth = Art.Font.MeasureString(text).X;
            mSpriteBatch.DrawString(Art.Font, text, new Vector2(ScreenSize.X - textWidth - 5, y), Color.White);
        }

        #endregion

        #region Update/Input Handling

        /// <summary>
        /// Allows the game to run logic such as updating the level and
        /// checking for collisions.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        /// <remarks>
        /// Update logic is localised within the object being updated.
        /// </remarks>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {

            GameTime = gameTime;

            ScreenPad.Update();

            CurrentScreenPadState = ScreenPad.GetState();

            PlayerStatus.Update();
            EntityManager.Update();
            EnemySpawner.Update();
            ParticleManager.Update();
            BackgroundGrid.Update();

            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
        }

        /// <summary>
        /// Allows the game to respond to user input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        /// <remarks>
        /// The gameplay screen only needs to handle generalised touch input
        /// and the use of the back button.
        /// </remarks>
        public override void HandleInput(InputState input)
        {
            if (input == null)
                throw new ArgumentNullException("input");
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Event handler to capture when the application is deactivated.
        /// </summary>
        private void GameDeactivated(object sender, DeactivatedEventArgs e)
        {

        }

        /// <summary>
        /// Event handler to capture when the application is obscured.
        /// </summary>
        private void GameObscured(object sender, ObscuredEventArgs e)
        {

        }

        #endregion

    }
}

BloomComponent.cs

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Vision
{
    public class BloomComponent : DrawableGameComponent
    {
        #region Fields

        SpriteBatch spriteBatch;

        Effect bloomExtractEffect;
        Effect bloomCombineEffect;
        Effect gaussianBlurEffect;

        RenderTarget2D sceneRenderTarget;
        RenderTarget2D renderTarget1;
        RenderTarget2D renderTarget2;


        // Choose what display settings the bloom should use.
        public BloomSettings Settings
        {
            get { return settings; }
            set { settings = value; }
        }

        BloomSettings settings = BloomSettings.PresetSettings[0];


        // Optionally displays one of the intermediate buffers used
        // by the bloom postprocess, so you can see exactly what is
        // being drawn into each rendertarget.
        public enum IntermediateBuffer
        {
            PreBloom,
            BlurredHorizontally,
            BlurredBothWays,
            FinalResult,
        }

        public IntermediateBuffer ShowBuffer
        {
            get { return showBuffer; }
            set { showBuffer = value; }
        }

        IntermediateBuffer showBuffer = IntermediateBuffer.FinalResult;
        private byte[] shaderCode;


        #endregion

        #region Initialization


        public BloomComponent(Game game) : base(game)
        {
            if (game == null)
                throw new ArgumentNullException("game");
        }


        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            var assembly = Assembly.GetExecutingAssembly();

            var stream = assembly.GetManifestResourceStream("Vision.Content.Shaders.BloomExtract.mgfxo");
            using (var ms = new MemoryStream())
            {
                stream.CopyTo(ms);
                shaderCode = ms.ToArray();
            }
            stream.Close();
            bloomExtractEffect = new Effect(GraphicsDevice, shaderCode);

            stream = assembly.GetManifestResourceStream("Vision.Content.Shaders.BloomCombine.mgfxo");
            using (var ms = new MemoryStream())
            {
                stream.CopyTo(ms);
                shaderCode = ms.ToArray();
            }
            stream.Close();
            bloomCombineEffect = new Effect(GraphicsDevice, shaderCode);

            stream = assembly.GetManifestResourceStream("Vision.Content.Shaders.GaussianBlur.mgfxo");
            using (var ms = new MemoryStream())
            {
                stream.CopyTo(ms);
                shaderCode = ms.ToArray();
            }
            stream.Close();
            gaussianBlurEffect = new Effect(GraphicsDevice, shaderCode);

            // Look up the resolution and format of our main backbuffer.
            PresentationParameters pp = GraphicsDevice.PresentationParameters;

            int width = pp.BackBufferWidth;
            int height = pp.BackBufferHeight;



            // Create a texture for rendering the main scene, prior to applying bloom.
            sceneRenderTarget = new RenderTarget2D(GraphicsDevice, width, height, false, SurfaceFormat.Color, pp.DepthStencilFormat, pp.MultiSampleCount, RenderTargetUsage.DiscardContents);

            // Create two rendertargets for the bloom processing. These are half the
            // size of the backbuffer, in order to minimize fillrate costs. Reducing
            // the resolution in this way doesn't hurt quality, because we are going
            // to be blurring the bloom images in any case.
            width /= 2;
            height /= 2;

            renderTarget1 = new RenderTarget2D(GraphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.None);
            renderTarget2 = new RenderTarget2D(GraphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.None);
        }


        /// <summary>
        /// Unload your graphics content.
        /// </summary>
        protected override void UnloadContent()
        {
            sceneRenderTarget.Dispose();
            renderTarget1.Dispose();
            renderTarget2.Dispose();
        }


        #endregion

        #region Draw


        /// <summary>
        /// This should be called at the very start of the scene rendering. The bloom
        /// component uses it to redirect drawing into its custom rendertarget, so it
        /// can capture the scene image in preparation for applying the bloom filter.
        /// </summary>
        public void BeginDraw()
        {
            if (Visible)
            {
                GraphicsDevice.SetRenderTarget(sceneRenderTarget);
            }
        }


        /// <summary>
        /// This is where it all happens. Grabs a scene that has already been rendered,
        /// and uses postprocess magic to add a glowing bloom effect over the top of it.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            GraphicsDevice.SamplerStates[1] = SamplerState.LinearClamp;

            // Pass 1: draw the scene into rendertarget 1, using a
            // shader that extracts only the brightest parts of the image.
            bloomExtractEffect.Parameters["BloomThreshold"].SetValue(Settings.BloomThreshold);

            DrawFullscreenQuad(sceneRenderTarget, renderTarget1, bloomExtractEffect, IntermediateBuffer.PreBloom);

            // Pass 2: draw from rendertarget 1 into rendertarget 2,
            // using a shader to apply a horizontal gaussian blur filter.
            SetBlurEffectParameters(1.0f / (float)renderTarget1.Width, 0);

            DrawFullscreenQuad(renderTarget1, renderTarget2, gaussianBlurEffect, IntermediateBuffer.BlurredHorizontally);

            // Pass 3: draw from rendertarget 2 back into rendertarget 1,
            // using a shader to apply a vertical gaussian blur filter.
            SetBlurEffectParameters(0, 1.0f / (float)renderTarget1.Height);

            DrawFullscreenQuad(renderTarget2, renderTarget1, gaussianBlurEffect, IntermediateBuffer.BlurredBothWays);

            // Pass 4: draw both rendertarget 1 and the original scene
            // image back into the main backbuffer, using a shader that
            // combines them to produce the final bloomed result.
            GraphicsDevice.SetRenderTarget(null);

            EffectParameterCollection parameters = bloomCombineEffect.Parameters;

            parameters["BloomIntensity"].SetValue(Settings.BloomIntensity);
            parameters["BaseIntensity"].SetValue(Settings.BaseIntensity);
            parameters["BloomSaturation"].SetValue(Settings.BloomSaturation);
            parameters["BaseSaturation"].SetValue(Settings.BaseSaturation);

            GraphicsDevice.Textures[1] = sceneRenderTarget;

            DrawFullscreenQuad(renderTarget1, (int)GameplayScreen.ScreenSize.X, (int)GameplayScreen.ScreenSize.Y,
                               bloomCombineEffect,
                               IntermediateBuffer.FinalResult);
        }


        /// <summary>
        /// Helper for drawing a texture into a rendertarget, using
        /// a custom shader to apply postprocessing effects.
        /// </summary>
        void DrawFullscreenQuad(Texture2D texture, RenderTarget2D renderTarget,
                                Effect effect, IntermediateBuffer currentBuffer)
        {
            GraphicsDevice.SetRenderTarget(renderTarget);

            DrawFullscreenQuad(texture,
                               renderTarget.Width, renderTarget.Height,
                               effect, currentBuffer);
        }


        /// <summary>
        /// Helper for drawing a texture into the current rendertarget,
        /// using a custom shader to apply postprocessing effects.
        /// </summary>
        void DrawFullscreenQuad(Texture2D texture, int width, int height,
                                Effect effect, IntermediateBuffer currentBuffer)
        {
            // If the user has selected one of the show intermediate buffer options,
            // we still draw the quad to make sure the image will end up on the screen,
            // but might need to skip applying the custom pixel shader.
            if (showBuffer < currentBuffer)
            {
                effect = null;
            }

            spriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect);
            spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);
            spriteBatch.End();
        }


        /// <summary>
        /// Computes sample weightings and texture coordinate offsets
        /// for one pass of a separable gaussian blur filter.
        /// </summary>
        void SetBlurEffectParameters(float dx, float dy)
        {
            // Look up the sample weight and offset effect parameters.
            EffectParameter weightsParameter, offsetsParameter;

            weightsParameter = gaussianBlurEffect.Parameters["weights"];
            offsetsParameter = gaussianBlurEffect.Parameters["offsets"];

            // Look up how many samples our gaussian blur effect supports.
            int sampleCount = weightsParameter.Elements.Count;

            // Create temporary arrays for computing our filter settings.
            float[] sampleWeights = new float[sampleCount];
            Vector2[] sampleOffsets = new Vector2[sampleCount];

            // The first sample always has a zero offset.
            sampleWeights[0] = ComputeGaussian(0);
            sampleOffsets[0] = new Vector2(0);

            // Maintain a sum of all the weighting values.
            float totalWeights = sampleWeights[0];

            // Add pairs of additional sample taps, positioned
            // along a line in both directions from the center.
            for (int i = 0; i < sampleCount / 2; i++)
            {
                // Store weights for the positive and negative taps.
                float weight = ComputeGaussian(i + 1);

                sampleWeights[i * 2 + 1] = weight;
                sampleWeights[i * 2 + 2] = weight;

                totalWeights += weight * 2;

                // To get the maximum amount of blurring from a limited number of
                // pixel shader samples, we take advantage of the bilinear filtering
                // hardware inside the texture fetch unit. If we position our texture
                // coordinates exactly halfway between two texels, the filtering unit
                // will average them for us, giving two samples for the price of one.
                // This allows us to step in units of two texels per sample, rather
                // than just one at a time. The 1.5 offset kicks things off by
                // positioning us nicely in between two texels.
                float sampleOffset = i * 2 + 1.5f;

                Vector2 delta = new Vector2(dx, dy) * sampleOffset;

                // Store texture coordinate offsets for the positive and negative taps.
                sampleOffsets[i * 2 + 1] = delta;
                sampleOffsets[i * 2 + 2] = -delta;
            }

            // Normalize the list of sample weightings, so they will always sum to one.
            for (int i = 0; i < sampleWeights.Length; i++)
            {
                sampleWeights[i] /= totalWeights;
            }

            // Tell the effect about our new filter settings.
            weightsParameter.SetValue(sampleWeights);
            offsetsParameter.SetValue(sampleOffsets);
        }


        /// <summary>
        /// Evaluates a single point on the gaussian falloff curve.
        /// Used for setting up the blur filter weightings.
        /// </summary>
        float ComputeGaussian(float n)
        {
            float theta = Settings.BlurAmount;

            return (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) *
                           Math.Exp(-(n * n) / (2 * theta * theta)));
        }


        #endregion
    }
}

Thanks in advance.

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Upon closer inspection of the bloomComponent's draw method, I realised that when it gets called it re-renders the initial render target so stops the bloom from being applied. SO all I do is now call the draw method of the bloomcomponent to stop it. It seems to run very slow, but that is a different issue all together :-(

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged .