Search code examples
shaderxna-4.0hlsl

Why is XNA 4.0 HLSL Pixel Shader affecting the entire texture, instead of working pixel-by-pixel?


I'm trying to implement a 2D lighting system to learn how to use shaders. I've just got my first shader up and running tonight.

Right now, the game is pretty simple, I just use this code to render 80x80px tiles over the background.

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

        Texture2D tile = this.Content.Load<Texture2D>("tile");

        Effect effect = this.Content.Load<Effect>("test");

        MouseState ms = Mouse.GetState();

        effect.Parameters["LightPosition"].SetValue(new Vector2(ms.X, ms.Y));

        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullNone, effect);

        for (int row = 0; row < 10; row++)
        {
            for (int col = 0; col < 6; col++)
            {
                Vector2 tilePos = new Vector2(row * 80, col * 80);
                effect.Parameters["TilePosition"].SetValue(tilePos);
                spriteBatch.Draw(tile, tilePos, Color.White);
            }
        }
        spriteBatch.End();

        // TODO: Add your drawing code here

        base.Draw(gameTime);
    }

What I want to do is have the mouse pointer act as the light source, and then compute the difference (pythagorean) between the position of the mouse and the position of each pixel on each tile and use that to progressively darken the tiles the further away each pixel is from the mouse pointer.

What's happening, however, is that ALL of the pixels on any given tile "light up" or "darken" at once, based on how far away the mouse pointer is from the top left corner of the tile.

Here's my shader code.

texture TileTexture;

float2 LightPosition : VPOS;
float2 TilePosition : VPOS;

sampler TextureSampler = sampler_state
{
    Texture = <TileTexture>;
};

float4 PixelShaderFunction(float2 TextureCoordinate : TEXCOORD0) : COLOR0
{
    float4 color = tex2D(TextureSampler, TextureCoordinate);
    float x_dist;
    float y_dist;
    float distance;
    // Calculate distance formula (pythagorean - a2 = b2 + c2)
    x_dist = abs(LightPosition.x - (TextureCoordinate.x + TilePosition.x));
    y_dist = abs(LightPosition.y - (TextureCoordinate.y + TilePosition.y));
    distance = pow(x_dist, 2) + pow(y_dist, 2);
    distance = sqrt(distance);
    color.r = color.r - distance * 0.01;
    color.g = color.g - distance * 0.01;
    color.b = color.b - distance * 0.01;

    return color;
}


technique Lighting
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

Strangely, this code actually did affect each pixel of the tile individually, and I'm really not seeing what I changed that stopped it from working.

float4 PixelShaderFunction(float2 TextureCoordinate : TEXCOORD0) : COLOR0
{
    float4 color = tex2D(TextureSampler, TextureCoordinate);

    //float value = (color.r + color.g + color.b) / 3; 
    color.r = color.r + TextureCoordinate.x + TextureCoordinate.y - 0.75;
    color.g = color.g + TextureCoordinate.x + TextureCoordinate.y - 0.75;
    color.b = color.b + TextureCoordinate.x + TextureCoordinate.y - 0.75;

    return color;
}

Thanks in advance for any help.


Solution

  • The TEXCOORD semantic does not represent a texture's pixels by whole numbers, it represents them by float values from 0.0 to 1.0. Changing these lines:

    x_dist = abs(LightPosition.x - (TextureCoordinate.x + TilePosition.x));
    y_dist = abs(LightPosition.y - (TextureCoordinate.y + TilePosition.y));
    

    To this:

    x_dist = abs(LightPosition.x - ((TextureCoordinate.x * 80) + TilePosition.x));
    y_dist = abs(LightPosition.y - ((TextureCoordinate.y * 80) + TilePosition.y));
    

    Where 80 represents the height and width of the texture...

    Fixes the problem.