[SOLVED] 2d image normalmap pixel shader, only lit from one direction?

I’m trying to get normal mapped 2d sprites working, but having some problems with the shader. I’ve got an image and normal map, being lit by a directional light that rotates direction, but for some reason it is only lit correctly when the light is shining from the upper left. Not sure if the problem is in the shader, the code, or maybe the normal map itself.

Here’s the pixel shader I’m using:

// Effect applies normalmapped lighting to a 2D sprite.

float3 LightDirection;
float3 LightColor = 1.0;
float3 AmbientColor = 0.35;

sampler TextureSampler : register(s0);
sampler NormalSampler : register(s1)
{ 
    Texture = (NormalTexture);
};

float4 main(float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
    //Look up the texture value
    float4 tex = tex2D(TextureSampler, texCoord);

    //Look up the normalmap value
    float4 normal = tex2D(NormalSampler, texCoord);
    
    // Compute lighting.
    float lightAmount = dot(normal.xyz, LightDirection);
    color.rgb *= AmbientColor + (lightAmount * LightColor);

    return tex * color;
}

technique Normalmap
{
    pass Pass1
    {
        PixelShader = compile ps_3_0 main();
    }
}

and here is a quick monogame project (WindowsGL) that uses it:

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

namespace SpriteEffects
{
    /// <summary>
    /// Sample demonstrating how pixel shaders can be used to apply special effects to sprite rendering.
    /// </summary>
    public class SpriteEffectsGame : Game
    {
        GraphicsDeviceManager graphics;

        /// <summary>
        /// Shader to draw the texture, light correctly using the supplied normal map
        /// </summary>
        private Effect normalmapEffect;

        // Textures used by this sample.
        Texture2D catTexture;
        Texture2D catNormalmapTexture;

        // SpriteBatch instance used to render all the effects.
        SpriteBatch spriteBatch;

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

        /// <summary>
        /// Loads graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            normalmapEffect = Content.Load<Effect>("normalmap");
            catTexture = Content.Load<Texture2D>("cat");
            catNormalmapTexture = Content.Load<Texture2D>("CatNormalMap");
            spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
        }

        /// <summary>
        /// Allows the game to run logic.
        /// </summary>
        protected override void Update(GameTime gameTime)
        {
            if ((GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) ||
                Keyboard.GetState().IsKeyDown(Keys.Escape))
            {
                Exit();
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            //This is the light direction to use to light any norma. maps.
            Vector2 dir = MoveInCircle(gameTime, 1.0f);
            Vector3 lightDirection = new Vector3(dir.X, dir.Y, 0f);
            lightDirection.Normalize();

            //Clear the device to XNA blue.
            GraphicsDevice.Clear(Color.CornflowerBlue);

            //Set the light directions.
            normalmapEffect.Parameters["LightDirection"].SetValue(lightDirection);
            normalmapEffect.Parameters["NormalTexture"].SetValue(catNormalmapTexture);
            normalmapEffect.Parameters["LightColor"].SetValue(new Vector3(1f, 1f, 1f));
            normalmapEffect.Parameters["AmbientColor"].SetValue(new Vector3(.25f, 0.25f, 0.25f));

            //Draw the lit texture.
            spriteBatch.Begin(0, null, null, null, null, normalmapEffect);
            spriteBatch.Draw(catTexture, Vector2.Zero, Color.White);
            spriteBatch.End();
        }

        /// <summary>
        /// Helper for moving a value around in a circle.
        /// </summary>
        static Vector2 MoveInCircle(GameTime gameTime, float speed)
        {
            double time = gameTime.TotalGameTime.TotalSeconds * speed;

            float x = (float)Math.Cos(time);
            float y = (float)Math.Sin(time);

            return new Vector2(x, y);
        }
    }
}

Any help getting the light model working is super appreciated. Additionally, any recommendations to optimize this code would be welcomed. Cheers!

Replace this:

float4 normal = tex2D(NormalSampler, texCoord);

with this:

float4 normal = 2 * tex2D(NormalSampler, texCoord) - 1 ;

You have to map vector given by bitmap value into 3d space.

1 Like

Thank you, that did the trick!