Not managing to create focus effect in 2d!

Hello, Monogame forums!

I am currently developing a 2d adventure RPG game, and am currently stuck trying to figure out how to make sprites appear out of focus.

In general, the idea is to let sprites come in and out of focus as they move from the background to the foreground, using a parameter that will be set for every instance. This will give many scenes a layer of depth that will improve the looks of the game by a lot.

Like so, basically (but I would happily settle for something simpler):

Any idea how to do this? Thank you so much in advance, I would really appreciate if someone here were to help me out, since I canā€™t seem to find any information whatsoever on how to do this.

Sincerely and dearly, Aldebaran Himself.

You could render a blurred version for each sprite individually, or, you render the sprites normally, and then do a depth-based fullscreen blur effect at the end.
Either way, you will need some kind of blur shader, unless you want to use preblurred versions for every sprite.
Iā€™d probably prefer the fullscreen blur effect, but it depend on the specifics of your project.

Thanks for responding, but how would I do that? (Blurring each sprite specifically.) How do I blur a sprite? I canā€™t find any shaders for this anywhere, which is why I came here.

what you can look for is ā€œDepth of fieldā€ shaders/processing. There are a lot of tutorials for this kind of stuff out there.

Here is an example: https://digitalerr0r.wordpress.com/2009/05/16/xna-shader-programming-tutorial-20-depth-of-field/

However, this is relatively complicated stuff if you are not familiar with shaders, but there is no easy solution here.

Blurring each sprite individually is not an option if you have a lot of them, since blurring is one of the most expensive operations to do.

1 Like

Just popping in to say Hello and Welcome to the forums, and I love that username haha, genius! Socrates?

Edit

Bokeh Effect would be good here, I think, but yeah Depth-Of-Field

Soā€¦ this kind of piqued my interest haha. Itā€™s a neat idea and I wanted to see if I could hack something togetherā€¦ so I gave it a shot! I went with a simplified version of what markus suggested, mostly because I just wanted to proof of concept it and then move on. Iā€™ve been known to get over-involved in thingsā€¦ *cough* :stuck_out_tongue:

Anyway, my general approach was thisā€¦

  • Render entire scene to render target except unblurred sprite.
  • Apply blur to entire render target.
  • Render unblurred sprite.

I added code to be able to select a sprite or deselect it.
Hereā€™s what it looked like unblurred:

Then with one of the balls selected:

For the shader, I just googled around the interwebs until I found some kind of blur shader and implemented it. Iā€™m sure there are betterā€¦ plus this applied to a full screen image via a render target is fairly taxing. All in all itā€™s probably not the best solution, but it was kinda fun to play around with.

Hereā€™s the codeā€¦

blur.fx (the shader)

#if OPENGL
	#define SV_POSITION POSITION
	#define VS_SHADERMODEL vs_3_0
	#define PS_SHADERMODEL ps_3_0
#else
	#define VS_SHADERMODEL vs_4_0_level_9_1
	#define PS_SHADERMODEL ps_4_0_level_9_1
#endif

Texture2D SpriteTexture;
sampler2D InputSampler = sampler_state
{
	Texture = <SpriteTexture>;
};
float2 xResolution = float2(0, 0);
bool xEnabled = true;

struct VertexShaderOutput
{
	float4 Position : SV_POSITION;
	float4 Color : COLOR0;
	float2 UV : TEXCOORD0;
};

float4 BlurShader(VertexShaderOutput input) : COLOR
{
	// Adapted from:
	// https://github.com/Jam3/glsl-fast-gaussian-blur

	float4 c = float4(0, 0, 0, 0);
	float2 uv = input.UV;
	float2 uvPix = float2(1 / xResolution.x, 1 / xResolution.y);
	float2 off1 = float2(1.41, 1.41);
	float2 off2 = float2(3.30, 3.30);
	float2 off3 = float2(5.18, 5.18);
	
	float wBaseV = 0.196;
	float4 wBase = float4(wBaseV, wBaseV, wBaseV, 1);
	
	float w1v = 0.297 / 2;
	float4 w1 = float4(w1v, w1v, w1v, 1);
	
	float w2v = 0.0945 / 2;
	float4 w2 = float4(w2v, w2v, w2v, 1);
	
	float w3v = 0.0104 / 2;
	float4 w3 = float4(w3v, w3v, w3v, 1);
	
	c += tex2D(InputSampler, uv);
	float4 cOrig = c;
	
	if (xEnabled == true)
	{
		c *= wBase;
		
		c += tex2D(InputSampler, uv + off1 * uvPix * float2(1, 0)) * w1;
		c += tex2D(InputSampler, uv + off1 * uvPix * float2(-1, 0)) * w1;
		c += tex2D(InputSampler, uv + off1 * uvPix * float2(0, 1)) * w1;
		c += tex2D(InputSampler, uv + off1 * uvPix * float2(0, -1)) * w1;
		
		c += tex2D(InputSampler, uv + off2 * uvPix * float2(1, 0)) * w2;
		c += tex2D(InputSampler, uv + off2 * uvPix * float2(-1, 0)) * w2;
		c += tex2D(InputSampler, uv + off2 * uvPix * float2(0, 1)) * w2;
		c += tex2D(InputSampler, uv + off2 * uvPix * float2(0, -1)) * w2;
		
		c += tex2D(InputSampler, uv + off3 * uvPix * float2(1, 0)) * w3;
		c += tex2D(InputSampler, uv + off3 * uvPix * float2(-1, 0)) * w3;
		c += tex2D(InputSampler, uv + off3 * uvPix * float2(0, 1)) * w3;
		c += tex2D(InputSampler, uv + off3 * uvPix * float2(0, -1)) * w3;
	}
	
	c.a = cOrig.a;
	return c;
}

technique BasicColorDrawing
{
	pass P0
	{
		PixelShader = compile PS_SHADERMODEL BlurShader();
	}
};

DOFDemoGame.cs

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;

namespace DOFDemo
{
    public class DOFDemoGame : Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;
        private Entity _background;
        private List<Entity> _entities = new List<Entity>();

        private Effect _blurEffect;
        private RenderTarget2D _target;

        private Entity _selectedEntity = null;
        private MouseState _oldMouseState;

        public DOFDemoGame()
        {
            this.IsMouseVisible = true;

            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            _graphics.PreferredBackBufferWidth = 1280;
            _graphics.PreferredBackBufferHeight = 720;
            _graphics.ApplyChanges();

            base.Initialize();
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            _blurEffect = this.Content.Load<Effect>("blur");

            _background = new Entity(this.Content.Load<Texture2D>("background"), new Rectangle(0, 0, 1280, 720)) { BlurEffect = _blurEffect };

            Texture2D ball = this.Content.Load<Texture2D>("ball");
            _entities.Add(new Entity(ball, new Rectangle(100, 200, ball.Width, ball.Height)));
            _entities.Add(new Entity(ball, new Rectangle(600, 200, ball.Width, ball.Height)));

            _target = new RenderTarget2D(_graphics.GraphicsDevice, 1280, 720);
        }

        protected override void Update(GameTime gameTime)
        {
            MouseState mouseState = Mouse.GetState();

            if (mouseState.LeftButton == ButtonState.Released && _oldMouseState.LeftButton == ButtonState.Pressed)
            {
                Entity pickedEntity = null;
                foreach (Entity e in _entities)
                {
                    if (e.Bounds.Contains(mouseState.Position))
                    {
                        pickedEntity = e;
                        break;
                    }
                }

                _selectedEntity = pickedEntity;
            }

            _oldMouseState = mouseState;

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            _graphics.GraphicsDevice.SetRenderTarget(_target);

            // Draw to target.
            _spriteBatch.Begin();
            _background.Draw(gameTime, _spriteBatch);
            foreach (Entity e in _entities)
                if (e != _selectedEntity)
                    e.Draw(gameTime, _spriteBatch);
            _spriteBatch.End();
            _graphics.GraphicsDevice.SetRenderTarget(null);

            // Add blur effect to rendered stuff.
            _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.AnisotropicClamp, null, null, _blurEffect, null);

            bool blurEnabled = (_selectedEntity == null) ? false : true;
            _blurEffect.Parameters["xEnabled"].SetValue(blurEnabled);
            _spriteBatch.Draw(_target, Vector2.Zero, Color.White);

            _spriteBatch.End();

            // Draw unblurred entity
            if (_selectedEntity != null)
            {
                _spriteBatch.Begin();
                _selectedEntity.Draw(gameTime, _spriteBatch);
                _spriteBatch.End();
            }

            base.Draw(gameTime);
        }
    }

    public class Entity
    {
        public Texture2D Texture { get; set; }
        public Rectangle Bounds { get; set; }
        public Color Color { get; set; } = Color.White;
        public Effect BlurEffect { get; set; } = null;

        public Entity(Texture2D texture, Rectangle bounds)
        {
            this.Texture = texture;
            this.Bounds = bounds;
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            if (this.BlurEffect != null)
                this.BlurEffect.Parameters["xResolution"].SetValue(new Vector2(this.Texture.Width, this.Texture.Height));

            spriteBatch.Draw(this.Texture, this.Bounds, this.Color);
        }
    }
}

Isnā€™t a depth of field shader kind of ā€˜a tank to kill a flyā€™ for a 2D game like this? Especially if it only concerns the actors moving in and out of the foreground

1 Like

A fullscreen multi sample blur effect not performant? Naaaaaw dude :stuck_out_tongue:

Yea I definitely think there are ways to optimize this, and probably a lot of other approaches. I suspect you could probably dynamically generate a blurred version of all your sprites during load time, or perhaps even as some time-sliced side process for sprites not currently in your scene while gameplay is occurring.

I am honestly confused by this topic and some approaches so I am just observing how this will pan out, but yes, correct high end approach here is depth of field BUT it has several issues especially with transparent object, on top of it there are several things to consider when it comes to quality and in my opinion proper DOF is one of most difficult effects to implement. I know there is article from Epic games on that topic and their solution for Unreal Engine somewhere on net.

If OP wont need transparent objectā€™s then Blur based on Depth buffer, if he also wants proper booketh then it will take some extra research and work. I discourage from pregenerating anything on asset level if relative distance can change.

Bluring each sprite individualy on runtime would solve transparent objects to some degree but can be quite costly performance wise, depending on game design and vision it COULD be most versatile and viable option provided there will be performance to spare (which is often case with indie titles, thus they can afford to overkill some effects with brute force). In this case I would go with shader that can generate kernel in realtime (while I would kinda regret lack of access to compute shader)ā€¦ there will be some interesting things to solve when it will come to edge of the solid object to get it ā€œlook rightā€.

I did not say it was not performant. I said it was a lot of complexity to solve something somewhat simple :wink:
There are a lot of fast blur tutorials, like this one
http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
Playing with the kernel or the radius can simulate out of focus objects

Ohā€¦ in terms of complexity it was actually pretty easy to set up. Itā€™s just an effect applied to a full screen render. I dunno, it didnā€™t seem like a lot of code to me. Either way, I donā€™t think itā€™s the best approachā€¦ it was just one of those things to do for fun. What kind of approach would you take?

(Iā€™m not actually implementing thisā€¦ just seemed like one of those fun things to explore. I think itā€™s cool to consider and brainstorm ways to do stuff like thisā€¦ gets the olā€™ brain going.)

Anyway, thatā€™s a much better article for blur than I could find! Thanks for thatā€¦ Iā€™ll bookmark that for later. I donā€™t really need a blur right now but I can certainly see a time when I might want one and thatā€™s much better than what I managed to cobble together in a few hours of hacking away based on something I found on the internet :wink:

I sort of ended up with a blurr shader when doing some propagation experiments well it was supposed to blend for a calculational rt, but the result is similar.

I think if you donā€™t completely clear the rt each time and just do like a averaging towards black you could make it much more pronounced with this. Itā€™s a start i suppose.

Maybe you can get it to were you like it.

I didnā€™t include the entire thing its too big and messy so snip out the unneeded stuff that is sent in.

#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
//#define VS_SHADERMODEL vs_3_0_level_9_1
//#define PS_SHADERMODEL ps_3_0_level_9_1
//#define VS_SHADERMODEL vs_3_0_level_10_0
//#define PS_SHADERMODEL ps_3_0_level_10_0
#define VS_SHADERMODEL vs_4_0
#define PS_SHADERMODEL ps_4_0
#endif    

//_______________________________________________________________
// common
//_______________________________________________________________
bool displayVertexNormalShading;
float alphaDiscardThreshold;
float3 lightDir;
float4 lightColor;
float specularSharpness;
float specularInfluence;
float diffuseInfluence;
float ambientInfluence;
float4 ambientColor;
float4 cameraForward;
float2 randomSeed;
float4x4 gworld;
//float4x4 gview;
//float4x4 gprojection;
float4x4 gworldviewprojection;

//_______________________________________________________________
// samplers
//_______________________________________________________________

Texture2D TextureA;
sampler2D TextureSamplerA = sampler_state
{
    Texture = <TextureA>;
    //AddressU = Wrap;
    //AddressV = Wrap;
    //MinFilter = Anisotropic;
    //MagFilter = Anisotropic;
    //MipFilter = Point;
};
Texture2D TextureB;
sampler2D TextureSamplerB = sampler_state
{
    Texture = <TextureB>;
    //AddressU = Wrap;
    //AddressV = Wrap;
    //MinFilter = Anisotropic;
    //MagFilter = Anisotropic;
    //MipFilter = Point;
};

//_______________________________________________________________
// techniques ScreenQuadDrawRtPropegating
// kinda like a blur
//_______________________________________________________________
struct VertexShaderInputScreenQuadRtPropegating
{
float4 Position : POSITION0;
float3 Normal : NORMAL0;
float4 Color : COLOR0;
float2 TexureCoordinateA : TEXCOORD0;
};
struct VertexShaderOutputScreenQuadRtPropegating
{
float4 Position : SV_Position;
//float3 Normal : TEXCOORD1;
float4 Color : COLOR0;
float2 TexureCoordinateA : TEXCOORD0;
};
struct PixelShaderOutputScreenQuadRtPropegating
{
float4 Color : COLOR0;
};

VertexShaderOutputScreenQuadRtPropegating VertexShaderScreenQuadDrawRtPropegating(VertexShaderInputScreenQuadRtPropegating input)
{
    VertexShaderOutputScreenQuadRtPropegating output;
    output.Position = mul(input.Position, gworld); // basically matrix identity
    output.TexureCoordinateA = input.TexureCoordinateA;
    output.Color = input.Color;
    return output;
}
PixelShaderOutputScreenQuadRtPropegating PixelShaderScreenQuadDrawRtPropegating(VertexShaderOutputScreenQuadRtPropegating input)
{
    PixelShaderOutputScreenQuadRtPropegating output;
    //output.Color = tex2D(TextureSamplerA, input.TexureCoordinateA); // *input.Color;
    //float4 col = tex2D(TextureSamplerA, input.TexureCoordinateA); // *input.Color;

    float4 col = tex2D(TextureSamplerA, input.TexureCoordinateA);
    float4 col_right = tex2D(TextureSamplerA, input.TexureCoordinateA + float2(.01f, .0f));
    float4 col_left = tex2D(TextureSamplerA, input.TexureCoordinateA + float2(-.01f, .0f));
    float4 col_up = tex2D(TextureSamplerA, input.TexureCoordinateA + float2(.0f, -.01f));
    float4 col_down = tex2D(TextureSamplerA, input.TexureCoordinateA + float2(.0f, .01f));

    float mult1 = .25f;
    float4 sum =
        col_right +
        col_left +
        col_up +
        col_down;
    sum.w = .99;
    float4 avg = sum * mult1 *.99;
    avg.w = .99;

    avg = max(col, avg);

    output.Color = saturate(avg);
    return output;
}
technique ScreenQuadDrawRtPropegating
{
    pass
    {
        VertexShader = compile VS_SHADERMODEL VertexShaderScreenQuadDrawRtPropegating();
        PixelShader = compile PS_SHADERMODEL PixelShaderScreenQuadDrawRtPropegating();
    }
}
1 Like

Thanks for the help, guys! Iā€™ve since managed to make the dream happen (though through manipulation of raw data). Thanks again for all your help.