Game Design, Programming and running a one-man games business…

Normal mapping question (it’s my day OFF)

EDIT: I’ve fixed it. It’s my own dumbassness. I was setting the sprite_angle variable in the shader AFTER the shader had started, and it was presumably never being set, and populated with garbage (or the ship beforehand) it works a charm now :D

Right so, despite being good at AI coding and optimisation, i suck at this clever stuff you people call ‘3D math’. I was in the pub that day of school. So I enlist you, the all-knowing internet to explain to me like a child what I am doing wrong. Here are 3 images:


The left is just the ship, the middle is a normal map thingy (thanks to charles!) and the right is what it looks like on screen given a single light source. The end result is exactly what I wanted, and there is much rejoicing… BUT. It’s screwed up, the position of the light source is wrong, and seemingly random, and sometimes jumps and cycles all over the place. I reckon I have everything sorted except the shader, which is an fx file as follows:

sampler2D g_samSrcColor : register( s0);
sampler2D g_samNormalMapColor: register( s1);
float sprite_angle : register(C0); 

float4 NormalMap( float2 texCoord : TEXCOORD0 ) : COLOR0
    //get the value of the normal at this texturecoord
     float3 normalcolor = tex2D(g_samNormalMapColor, texCoord);

     //convert it to +/- 1.0 range
     normalcolor *= 2.0f;
     normalcolor -= 1.0f;

     float3 LightDirection;
     LightDirection.x = sin(sprite_angle);
     LightDirection.y = cos(sprite_angle);
     LightDirection.z = 0;

     float dot_prod = dot(LightDirection, normalcolor);

     //apply as a tint to the final pixel
       float4 original = tex2D(g_samSrcColor, texCoord); 
       float4 final = original;
       final.r *= dot_prod;
       final.b *= dot_prod;
       final.g *= dot_prod;

       return final;

As an idiot, I’m not really sure what I am doing here. What I *think* I’m doing is this: I am drawing a sprite which has a separate normal map (g_samNormalMapColor) I also pass in the current angle of the sprite. I sample the color of the normal map, and convert it into the required range. I then convert the sprites angle into a light direction vector (probably wrongly) and I then do some magic which kids call ‘dot’ which I’m guessing gives me the brightness of the pixel given the angle and the default light direction. I then multiply the original texture color by this brightness to tint my final rendered sprite. Yay.

it’s something clever to do with angles and vectors and stuff isn’t it? explain it to me like I’m an idiot :D


And yeah…I’m playing about with Gratuitous Space Battles. It’s a Sunday morning, don’t read too much into it :D

16 thoughts on Normal mapping question (it’s my day OFF)

  1. I would debug it step by step.

    I’d start with just outputting the light direction variable (in 0-1 space), as the jumping about sounds suspiciously like you’ve either got an issue with the constant buffer or the angle is in the wrong units.

    Note that you’re likely best of converting the angle to a direction in the vertex shader for performance, plus you already have your transforms there I’m guessing so can use those to transform a world space light vector.

  2. Unless you have a funny coordinate system, you have cos and sin reversed. Also check that sprite_angle is being passed in as radians.

    1. Since the dot product can be a negative value you should probably do.

      max(0,dot(LightDirection, normalcolor));

      also agree with swapping sin and cos

    2. This arrangement of sin and cos is correct, in a way; it rotates the vector (0, 1, 0) over the given sprite_angle. Not knowing whether y points up or down, nor if angles are measured turnwise or widdershins, it’s hard to tell whether this is right, but it’s not necessarily wrong.

      1. Doesn’t the lightDirection rotate in opposite way as it is now. You increase the sprite_angle to me that is ccw rotation. But the lightDirection will rotate cw.
        I’m thinking in a right handed coordinate system.

  3. swapping cos and sine doesn’t actually fix it, so thats just likely an extra bug :D The sprite_angle is definitely in radians when I pass it in.

    I don’t use a vertex shader, I don’t need one. everything is a transformed and lit vertex, with vert positions computed within the game, not by shaders.

    I think something fundamental is wrong because if I pause the game, then slide the camera around, the effect on the same (unmoving) ship will vary, which is definitely not right!

    1. > I think something fundamental is wrong because if I pause the game, then slide the camera around, the effect on the same (unmoving) ship will vary, which is definitely not right!

      Whoa, indeed. That must mean sprite_angle changes depending on the camera position, right? So then the shader is not to blame.

    2. Set sprite_angle to some constant value in your code. If it will still vary when you slide the camera on unmoving ship, something is wrong with passing it to the shader. If not, then something is wrong with your logic of computing sprite_angle value.

    3. Are you computing you sprite_angle between 0,0 and one vertex on the sprite and not between an edge of the sprite and a world axis?

  4. “Dot” is just the dot product. If you have a vector (x1, y1, z1) and (x2, y2, z2), the dot product is x1*x2 + y1*y2 + z1*z2 (which is a number, not a vector). Think of it as a measure of “similarity” between two vectors: if e.g. x1 and x2 have the same sign (positive or negative), then x1*x2 will be a positive number, and vice versa.

    In typical Phong lighting, you compute the “similarity” between:
    – the surface normal vector, which points perpendicularly away from the surface, and
    – the light vector, which points *towards* the light source.

    If these two mostly line up, you can imagine that the surface is turned towards the light and will be brightly lit. This is signified by a positive dot product.

    If the two are perpendicular to each other, the light just glances the surface, and doesn’t illuminate the it at all. This is a dot product of zero. The very definition of perpendicular vectors is that they have zero dot product.

    If these two are pointing in different directions, the light is behind the surface. This results in a negative dot product. Usually you clamp it to zero, so you don’t end up with negative brightness. Your code doesn’t do that. It works fine because final will have negative colours, which are clamped to zero by the GPU, but if you ever add ambient lighting (just a constant term) you will run into trouble doing this. Write max(0, dot(…)) to preempt this.

    When computing the dot product, you need to make sure that both vectors are defined in the same coordinate system, or gibberish will come out. The normal map is just a static sprite, so it’s defined in “sprite space”. The light vector seems to be converted into sprite space here:

    float3 LightDirection;
    LightDirection.x = sin(sprite_angle);
    LightDirection.y = cos(sprite_angle);
    LightDirection.z = 0;

    Now that is a bit odd, because this code appears to have no sense of a light vector in “world space”. I would expect this code to take an input “world space” light vector, and then rotate it to match the orientation of the sprite to convert it into “sprite space”. Instead this uses an implicit, hardcoded world space light vector of (0, 1, 0), found by plugging in sprite_angle == 0, in which case “world space” and “sprite space” line up. If that’s what you want (light always from the top, or bottom, depending on which way your y axis points), no problem.

    Things to check:
    – Are all angles in radians, as cos and sin expect them to be?
    – Are the angles measured in the correct direction (clockwise vs counterclockwise)? Try adding a minus sign to all occurrences of sprite_angle, see what happens.
    – Is sprite_angle what you expect it to be? Try setting final.r = sprite_angle / (2*PI); final.g = final.b = 0; and see if the colour varies smoothly from black to red in one full rotation of the ship.

  5. Can you post a YouTube video of the bug? It would be very helpful to see exactly how it behaves. I don’t think the shader you posted is to blame given the current description.

  6. oooh Ooh, I think I might nearly have it. Just fixing sprite_angle to be something constant fixes it (for a set angle…) so somehow that must be going screwy…

  7. Just glanced at this for a second, but your dot computation should accept two vectors as arguments – it looks as though yours has a vector and a color. Your second argument should be the (normalized) normal itself, not just its color.

  8. I know that feeling, man. Spending hours wondering why something isn’t working, and then realizing you misspelt something, or confused some variable with another…

Comments are currently closed.