0
\$\begingroup\$

I'm trying to apply a shader postprocessing effect to my rendered game.

Here is the game code:

namespace ShaderTestDesktop
{
    public class Game1 : Game
    {
        private GraphicsDeviceManager graphics;
        private SpriteBatch _spriteBatch;
        private Texture2D _alien;
        private RenderTarget2D _renderTexture;
        private Effect _shader;
        private float _angle;
        private Vector2 _position;

        private bool _isShaderApplied;
        private KeyboardState _keyboardState;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();

            _keyboardState = Keyboard.GetState();
        }

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

            _shader = Content.Load<Effect>("nop");
            _alien = Content.Load<Texture2D>("alien");

            _renderTexture = new RenderTarget2D(
                GraphicsDevice,
                graphics.PreferredBackBufferWidth,
                graphics.PreferredBackBufferHeight
            );

            _position = new Vector2(
                GraphicsDevice.Viewport.Width / 2,
                GraphicsDevice.Viewport.Height / 2
            );
        }

        protected override void Update(GameTime gameTime)
        {
            _angle += 2f * (float)gameTime.ElapsedGameTime.TotalSeconds;

            var newState = Keyboard.GetState();

            if (!_keyboardState.IsKeyDown(Keys.Space) && newState.IsKeyDown(Keys.Space))
                _isShaderApplied = !_isShaderApplied;

            _keyboardState = newState;

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            // render the sprite to the intermediate texture
            GraphicsDevice.SetRenderTarget(_renderTexture);
            GraphicsDevice.Clear(Color.Transparent);

            _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
            _spriteBatch.Draw(_alien, _position, null, Color.White, _angle, new Vector2(128, 128), 1, SpriteEffects.None, 0);
            _spriteBatch.End();

            // post-process
            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.CornflowerBlue);

            _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, _isShaderApplied ? _shader : null);
            _spriteBatch.Draw(_renderTexture, Vector2.Zero, Color.White);
            _spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

The shader currently does nothing and is defined simply as:

sampler2D s0;

float4 MainPS(float2 coords: TEXCOORD0) : COLOR
{
    return tex2D(s0, coords);
}

technique BasicColorDrawing
{
    pass P0
    {
        PixelShader = compile ps_4_0_level_9_1 MainPS();
    }
};

Rendering without the shader looks like this (rotates clockwise):

Expected rendering

When the shader is applied, the object is apparently moved, scaled and mirrored (rotates counter-clockwise):

Actual rendering

The behaviour is consistent across platforms (at least Desktop and WP8). What am I doing wrong?

UPDATE:

As suggested in the comments, I modified the shader code to add a vertex shader too. Some articles suggested also creating a matrix to match the default transformation matrix from SpriteBatch.

Here's the new shader code:

matrix WorldViewProjection;

struct VertexShaderInput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
};

struct VertexShaderOutput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
};

VertexShaderOutput MainVS(in VertexShaderInput input)
{
    VertexShaderOutput output = (VertexShaderOutput)0;

    output.Position = mul(input.Position, WorldViewProjection);
    output.Color = input.Color;

    return output;
}

float4 MainPS(VertexShaderOutput input) : COLOR
{
    return input.Color;
}

technique BasicColorDrawing
{
    pass P0
    {
        VertexShader = compile vs_4_0_level_9_1 MainVS();
        PixelShader = compile ps_4_0_level_9_1 MainPS();
    }
};

New shader initialization code:

var halfPixel = Matrix.CreateTranslation(-0.5f, -0.5f, 0);
var offCenter = Matrix.CreateOrthographicOffCenter(0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 0, 1);
_shader = Content.Load<Effect>("nop");
_shader.Parameters["WorldViewProjection"].SetValue(halfPixel * offCenter);

New result (notice the 1px CornflowerBlue border at the right):

Actual rendering number 2

\$\endgroup\$
3
  • \$\begingroup\$ Your original result looks like you're rendering into the square (0...1) in normalized device coordinates (ie. The top-right corner), instead of (-1...1). The apparent rotation is because the y direction is opposite — flipping the vertical texcoord will fix this. In the second attempt, it looks like your vertex shader is not passing down the texture coordinate information that your pixel shader wants to use, which is preventing drawing from occurring correctly. \$\endgroup\$ – DMGregory Feb 7 '17 at 12:07
  • \$\begingroup\$ @DMGregory wow, passing the TEXCOORD0 through actually worked. Thanks! If you submit this as an answer, I will mark it as solution. \$\endgroup\$ – Impworks Feb 7 '17 at 12:40
  • \$\begingroup\$ I'm a bit tied up to write a proper in-depth answer just now. If you like, go ahead and write-up what worked for you. \$\endgroup\$ – DMGregory Feb 7 '17 at 13:22
1
\$\begingroup\$
PixelShader = compile ps_4_0_level_9_1 MainPS();

It's illegal to have a pixel shader without a vertex shader in shader model 4.

\$\endgroup\$
1
  • \$\begingroup\$ And yet it compiles and runs? I added an update to the original question with an attempt to add vertex shader, looks like it broke even more. \$\endgroup\$ – Impworks Feb 7 '17 at 11:40
0
\$\begingroup\$

The trouble was due to texture coordinates not being passed from vertex to pixel shader. Here is the correct version of the shader:

matrix WorldViewProjection;
sampler2D TextureSampler: register(s0); // added

struct VertexShaderInput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
    float2 TexCoords: TEXCOORD0; // added
};

struct VertexShaderOutput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
    float2 TexCoords: TEXCOORD0; // added
};

VertexShaderOutput MainVS(in VertexShaderInput input)
{
    VertexShaderOutput output = (VertexShaderOutput)0;

    output.Position = mul(input.Position, WorldViewProjection);
    output.Color = input.Color;
    output.TexCoords = input.TexCoords; // added

    return output;
}

float4 MainPS(VertexShaderOutput input) : COLOR
{
    return tex2D(TextureSampler, input.TexCoords);
}

technique BasicColorDrawing
{
    pass P0
    {
        VertexShader = compile vs_4_0_level_9_1 MainVS();
        PixelShader = compile ps_4_0_level_9_1 MainPS();
    }
};

Big thanks to @DMGregory for help!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.