Drawing a non rectangular part of Texture2d

Hello guys!
I’m developping a tiled base 2d game. Recently i have done a raycasting system but now i’m facing a problem that i don’t think i can resolve by myself. I need to draw only a part of a texture, for exemple, the blue part on this screen :

I was thinking about somehow drawing the image with a polygon source but i don’t know if it possible.
Do you have any idea on how to archieve this please?
Thanks you !
Just for you to know, my knowledge in Xna and monogame is very humble… :smiley:

If you wish to do this when using SpriteBatch you will need to write a pixel shader.
Which is probably the easiest way anyways.

You will need to send in a Ray to the shader. That is a position which is the player or maybe some distance behind him e.g. (float2 playerPosition;) and a direction he is looking at e.g. (float2 playerLookAtDirection;).
You will need to send in a float for range of vision in radians or degrees e.g. (float rangeInDegrees;)

In the pixel shader you will need to use the following general calculation.
(the variable names will be long and verbose for clarity).

float4 resultingPixelColor = tex2D(TextureSampler, input.TexureCoordinate); // *input.Color;
float2 playerToScreenPixelNormal = normalize(position.xy - playerPosition.xy);
float theta = dot(normalize(playerLookAtDirection) , playerToScreenPixelNormal );
float pixelDegreesToLineOfSight = theta * 90.0f;

// clips out anything not within the specified range to the line of site.
clip(pixelDegreesToLineOfSight - rangeInDegrees);

return resultingPixelColor; 
}

You will need to use the pipeline tool to make a New Effect.
http://www.monogame.net/documentation/?page=adding_content
Open it and you will see a standard barebones pixelshader.

Here is a small game1 and working shader that should help to see how to do it on both ends the game1 and the shader. The example below is just about copy pasteable and runable with a spritefont added in.

1 Like

Not sure if there is an easier version, but I would do it with the Stencil Buffer. Look it up to know further about it, but with a stenci buffer you can basically “mask” the output

  1. Draw your screen regulary (no stencil)

  2. activate and configure stencil write and just render your light/view cone (this is basically just drawing the “mask” on the stencil buffer) (with respect of alpha, as it is a square with alpha, if I suppose correctly)

  3. render geometry which shall be seen in the cone with stencil check

Not tried myself, but it should work but you’d draw all geo twice. I recommend to read a bit about the stencil buffer, as it’s much more easy once you understand how it works

If you’re drawings work differently with the cone, you may want to use your own Shader I guess (while the stencil can still be used)

Ya i suppose he could just draw a giant black pacman like texture over everything, one that has the cone part of it with a alpha of zero, might be costly dunno.

The shader though is small and will be fast with a single pass. Plenty of room to do more stuff as well, like dim out towards the edges or dim out as the visible distance increases or change the colors or whatever.

ya there are tons of shader examples on the forum here if you search for pixel shader.
writing-your-own-2d-pixel-shader-in-monogame-for-absolute-beginners

Thanks you two a lot for your answers !
I’ll try your solution @willmotil tonight. I’ll check as well the project you referred me :slight_smile:

Hi @Hawkja, Welcome to the Forums.

I guess this one’s answered plenty, just saying hi.

Happy Coding!

@willmotil After setting up the solution, i’m getting an error during compilation

I am doing something wrong please ?

Thank you for your patience :slight_smile:

Try adding that to the bottom of the game1 constructor.

graphics.GraphicsProfile = GraphicsProfile.HiDef;

Which project template are you using DX windows desktop im guessing?

Yes you are right, i’m Using MonoGame Windows Project (for the windows desktop using DirectX)
I’m still getting the error after adding the line at the bottom of my game1 constructor

if you want to access the position in PixelShader, you’ll have to have another field in the output semantics which does not use POSITION. use TEXCOORD for example.

struct IN
{
    float4 pos : POSITION0;
    float4 pos2 : TEXCOORD0;
}

just fill them with the same value in VS and just access IN.pos in PS … I’ve forgotten what’s the reason for that is technically. You can also use float3 as long as you don’t need the w value for matrix multiplication or such

my bad its dx you have to make a couple alterations.

in the shader

// effect.fx
//

#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
#define PS_SHADERMODEL ps_4_0
#endif

float4 visibleBounds;

Texture2D TextureA;
sampler2D TextureSamplerA = sampler_state
{
    Texture = <TextureA>;
};

// pixel shader
float4 MyPixelShader(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
    float4 col = tex2D(TextureSamplerA, texCoord) * color;
    float2 pos = position.xy;
    // i could probably mathematically boolean this down get rid of the ifs in a few ways but im tired.
    float dx = (pos.x - visibleBounds.x) * (visibleBounds.z - pos.x);
    float dy = (pos.y - visibleBounds.y) * (visibleBounds.w - pos.y);
    if (dx < 0.0f) 
        clip(-1);
    if (dy < 0.0f)
        clip(-1);
    return col;
}

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

in game 1

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Effect effect;
        SpriteFont font;
        string message = "";
        Rectangle boundryRectangle = new Rectangle(120, 120, 200, 250);
        public static Texture2D dotTexture;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.GraphicsProfile = GraphicsProfile.HiDef;
        }
        protected override void Initialize()
        {
            graphics.PreferredBackBufferWidth = 800;
            graphics.PreferredBackBufferHeight = 600;
            graphics.ApplyChanges();
            base.Initialize();
        }
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // load up your stuff
            font = Content.Load<SpriteFont>("font");

            // load up your effect;
            effect = Content.Load<Effect>("effect");
            effect.CurrentTechnique = effect.Techniques["BasicColorDrawing"];

            // make a message
            message =
                " Ok so i had a bit of trouble figuring this out it turns out that it was working after all. " +
                "\n I was wrong when spritebatch passes the pos to the pixel shader. " +
                "\n It passes the untransformed coordinates so it does a bit of magic under the hood. " +
                "\n However its own vertex shader is still lining them up properly even if i pass matrix identity." +
                "\n So there we go if you intend however to pass a projection matrix ill bet youll need to do the transform on the boundry" +
                "\n +\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+"
                ;

            dotTexture = TextureDotCreate(GraphicsDevice);
        }

        public Texture2D TextureDotCreate(GraphicsDevice device)
        {
            Color[] data = new Color[1];
            data[0] = new Color(255, 255, 255, 255);
            Texture2D tex = new Texture2D(device, 1, 1);
            tex.SetData<Color>(data);
            return tex;
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            base.Update(gameTime);
        }

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

            Matrix m = Matrix.Identity;
            effect.CurrentTechnique = effect.Techniques["BasicColorDrawing"];
            effect.Parameters["visibleBounds"].SetValue(GetVector4Rectangle(boundryRectangle));
            // Im cheating here cause i dont know the samplers name or if it can be used in the function.
            // So im re-splicing the same texture thats will be sent in a second time.
            // Maybe someone else can chime in on what the name of the texture sampler is, that basic effects, vertex shader uses.
            effect.Parameters["TextureA"].SetValue(font.Texture);

            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone, effect, m);
            spriteBatch.Draw(dotTexture, boundryRectangle, Color.White);
            spriteBatch.Draw(font.Texture, new Vector2(150, 320), Color.LightGray);
            spriteBatch.DrawString(font, message, new Vector2(100f, 100f), Color.MonoGameOrange);
            spriteBatch.End();

            base.Draw(gameTime);
        }

        public Vector4 GetVector4Rectangle(Rectangle r)
        {
            int h = graphics.GraphicsDevice.Viewport.Height;

            // Gl you gotta flip the y
            //return new Vector4(r.Left, (h - r.Top), r.Right, (h - r.Bottom));

            // Dx dont flip the y.
            return new Vector4(r.Left, r.Top, r.Right, r.Bottom); 
        }
        public Vector4 GetVector4GpuRectangle(Rectangle r, Matrix vp)
        {
            var lt = Vector2.Transform(new Vector2(r.Left, r.Top), vp);
            var rb = Vector2.Transform(new Vector2(r.Right, r.Bottom), vp);
            return new Vector4(lt.X, lt.Y, rb.X, rb.Y);
        }


    }
}

Ok i got your project working thanks you ! Im looking to make mine working as expected, i’m still struggling to find out what’s the problem. I’m going to check the tutorials you have linked. Right now my shader is like this:

float2 playerLookAtDirection;
float rangeInDegrees;
float2 playerPosition;
sampler TextureSampler;
float4 MainPS(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0, VertexShaderOutput input) : COLOR
{
	float4 resultingPixelColor = tex2D(TextureSampler, texCoord); // *input.Color;
	float2 playerToScreenPixelNormal = normalize(position.xy - playerPosition.xy);
	float theta = dot((playerLookAtDirection) , playerToScreenPixelNormal);
	float pixelDegreesToLineOfSight = theta * 90.0f;

	// clips out anything not within the specified range to the line of site.
	clip(pixelDegreesToLineOfSight - rangeInDegrees);

	return resultingPixelColor;
}

There is a little more to it here is a actual working example shader took me a minute to get the bugs out.

game1

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Effect effect;
        SpriteFont font;
        string message = "";
        Rectangle boundryRectangle = new Rectangle(120, 120, 200, 250);
        public static Texture2D dotTexture;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.GraphicsProfile = GraphicsProfile.HiDef;
            IsMouseVisible = true;
        }
        protected override void Initialize()
        {
            graphics.PreferredBackBufferWidth = 800;
            graphics.PreferredBackBufferHeight = 600;
            graphics.ApplyChanges();
            base.Initialize();
        }
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // load up your stuff
            font = Content.Load<SpriteFont>("font");

            // load up your effect;
            effect = Content.Load<Effect>("effect");
            effect.CurrentTechnique = effect.Techniques["ConeTechnique"];

            // make a message
            message =
                " Ok so i had a bit of trouble figuring this out it turns out that it was working after all. " +
                "\n I was wrong when spritebatch passes the pos to the pixel shader. " +
                "\n It passes the untransformed coordinates so it does a bit of magic under the hood. " +
                "\n However its own vertex shader is still lining them up properly even if i pass matrix identity." +
                "\n So there we go if you intend however to pass a projection matrix ill bet youll need to do the transform on the boundry" +
                "\n +\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+"
                ;

            dotTexture = TextureDotCreate(GraphicsDevice);
        }

        public Texture2D TextureDotCreate(GraphicsDevice device)
        {
            Color[] data = new Color[1];
            data[0] = new Color(255, 255, 255, 255);
            Texture2D tex = new Texture2D(device, 1, 1);
            tex.SetData<Color>(data);
            return tex;
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            var mouse = Mouse.GetState();
            playerLookAtTargetPosition = mouse.Position.ToVector2();

            base.Update(gameTime);
        }

        Vector2 playerPosition = new Vector2(100, 100);
        Vector2 playerLookAtTargetPosition = new Vector2(200, 20);
        float rangeInDegrees = 35f;

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

            Matrix m = Matrix.Identity;
            effect.CurrentTechnique = effect.Techniques["ConeTechnique"];
            effect.Parameters["visibleBounds"].SetValue(GetVector4Rectangle(boundryRectangle));
            effect.Parameters["playerPosition"].SetValue(playerPosition);
            effect.Parameters["playerLookAtTarget"].SetValue(playerLookAtTargetPosition);
            effect.Parameters["rangeInDegrees"].SetValue(rangeInDegrees);

            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone, effect, m);
            effect.Parameters["TextureA"].SetValue(font.Texture);
            spriteBatch.Draw(dotTexture, boundryRectangle, Color.White);
            spriteBatch.Draw(font.Texture, new Vector2(150, 320), Color.LightGray);
            spriteBatch.DrawString(font, message, new Vector2(100f, 100f), Color.MonoGameOrange);
            spriteBatch.End();

            base.Draw(gameTime);
        }

        public Vector4 GetVector4Rectangle(Rectangle r)
        {
            int h = graphics.GraphicsDevice.Viewport.Height;

            // Gl you gotta flip the y
            //return new Vector4(r.Left, (h - r.Top), r.Right, (h - r.Bottom));

            // Dx dont flip the y.
            return new Vector4(r.Left, r.Top, r.Right, r.Bottom); 
        }
        public Vector4 GetVector4GpuRectangle(Rectangle r, Matrix vp)
        {
            var lt = Vector2.Transform(new Vector2(r.Left, r.Top), vp);
            var rb = Vector2.Transform(new Vector2(r.Right, r.Bottom), vp);
            return new Vector4(lt.X, lt.Y, rb.X, rb.Y);
        }


    }
}

shader

// effect.fx
//

#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
#define PS_SHADERMODEL ps_4_0
#endif

float4 visibleBounds;
float2 playerPosition;
float2 playerLookAtTarget;
float rangeInDegrees;

Texture2D TextureA;
sampler2D TextureSamplerA = sampler_state
{
    Texture = <TextureA>;
};

// pixel shader
float4 MyPixelShader(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
    float4 col = tex2D(TextureSamplerA, texCoord) * color;
    float2 pos = position.xy;
    // i could probably mathematically boolean this down get rid of the ifs in a few ways but im tired.
    float dx = (pos.x - visibleBounds.x) * (visibleBounds.z - pos.x);
    float dy = (pos.y - visibleBounds.y) * (visibleBounds.w - pos.y);
    if (dx < 0.0f) 
        clip(-1);
    if (dy < 0.0f)
        clip(-1);
    return col;
}

// pixel shader
float4 ConePixelShader(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
    float2 playerToScreenPixelNormal = normalize(position.xy - playerPosition.xy);
    float2 playerLookAtDirection = normalize(playerLookAtTarget.xy - playerPosition.xy);
    float theta = saturate(dot(playerLookAtDirection, playerToScreenPixelNormal));
    float acos = theta * theta;
    float pixelDegreesToLineOfSight = acos * 90.0f;
    
    float4 resultingPixelColor = tex2D(TextureSamplerA, texCoord) * color;

    // clips out anything not within the specified range to the line of site.
    clip(pixelDegreesToLineOfSight - (90 - rangeInDegrees / 2.0));
    return resultingPixelColor;
}

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

technique ConeTechnique
{
    pass P0
    {
        PixelShader = compile PS_SHADERMODEL
        ConePixelShader();
    }
}
2 Likes

Thanks you a lot ! That’s what i was looking for !
I’ll have to do some modification because i can’t even see my player and the rest of the map now :joy:
But that’s ok, thanks for your time :smiley: