Drawing Normal Maps with SpriteBatch

Drawing normal maps with XNA's SpriteBatch becomes problematic as soon as you want to rotate your sprites. This article will explain one way to rotate your normal maps without losing the performance gained through batched rendering.

Github: NormalMapRotation

I’ve been working with Alientrap on a new game called Cryptark for the past few months. When I first started working with them, they described the game as being 2D with a lighting system that made heavy use of normal maps. Up until that point, the only work I had done with normal maps was with one of my terrain generators. So while I understood the basic concept, I didn’t realize there would be a problem with using SpriteBatch to draw rotated, normal-mapped sprites until I started to implement the lighting system.

The Problem with Rotating Normal Maps

Normal maps encode the direction of a texture’s surface into RGB colors. Every pixel in the texture is used by the lighting system to calculate how a light’s ray influences the color of that pixel. Since the color of any pixel on a normal map defines the direction of the surface, it’s easy to visualize the normal map’s colors needing to change as the normal map is rotated. For example, if a surface normal is pointing to the right, it will need to point to the left when the normal map is rotated 180 degrees.

Example of Non-Rotated and Rotated Normal Map

Pictured above is an example of normals that aren’t rotated (left), and normals that are rotated (right). Notice how the color doesn’t change on the left image, which means that the surface normal at that point isn’t changing either.

Lighting Results for Non-Rotated and Rotated Normals

Pictured above are the results of both the non rotated (left) and rotated (right) normals when used with a stationary directional light that comes in from the top right of the screen. Notice how the one on the left has the appearance of the light following it.

Different Methods for Rotating Normals

Rotating normals is easy enough to do in a shader, but there are multiple ways to get the sprite’s rotation information into the shader:

• Custom vertex declarations, which are somewhat inconvenient to use and require you to either write your own batcher or do without.
• SpriteBatch, using an effect parameter. This is easier, but you’ll either have to call Begin() and End() between setting the effect parameter, or use SpriteSortMode.Immediate. Either way, this ruins batching.
• SpriteBatch, encoding the angle into the sprite’s color. While you lose the ability to use color to tint the normal maps, that’s not something that would really make sense to do anyway. This method doesn’t ruin batching, and is the method I ended up using.

Encoding the Angle

The color parameter only supports a range of values between 0 and 1. An angle in XNA (when using radians) is generally in the range of -pi to pi. This means we’ll have to convert the angle to a range of 0 to 1 before passing it into the shader, and then convert it back once it’s in the shader.

``````public float EncodeAngle(float angleRads)
{

```    // Range will be [0, 2*pi]

```    // Convert to [0, 1]

```    return angleRads;
}``````

Decoding the Angle

Now we need to convert the angle back to radians. Most of this shader is standard for sampling from a normal map. The only lines directly related to rotating the normal is the line `float angle = color.r * TWO_PI;` and the lines that define the `rotation` matrix.

````static const float TWO_PI = 6.2831853071795864769252867665590057683943387987502116f;`

`sampler TextureSampler : register(s0);`

```float4 PixelShaderFunction(float2 texCoord : TEXCOORD0, float4 color : COLOR0) : COLOR0
{
float angle = color.r * TWO_PI;
float cosAngle = cos(angle);
float sinAngle = sin(angle);
float3x3 rotation = {
cosAngle, -sinAngle, 0,
sinAngle, cosAngle, 0,
0, 0, 1
};
float4 normalMap = tex2D(TextureSampler, texCoord);
float3 normal = 2 * normalMap.rgb - 1;
float4 final = float4(1, 1, 1, normalMap.a);```

```    normal = mul(normal, rotation);
final.rgb = (normal + 1) * 0.5f;
final.rgb *= final.a;
return final;
}```

```technique Technique1
{
pass Pass1
{