Home Articles Tutorials Resources About

Normal Mapping - Add depth to your flat surfaces with normal mapping


By Pieter Germishuys, January 12 2006

Normal mapping.

What an interesting and exciting topic to talk about.
What we are going to be looking at is an implementation normal mapping which is basically a technique where we tell the light how to behave according to the map. The map is encoded with 3 channels, one each for red, green, and blue, where if we take a point on the map we can get the normal vector of that point. So how does that work? Well your red channel defines the X coordinates, 100% red being left. The green channel defines the Y direction and 100% green is up. The blue channel is your Z direction and 100% coming straight out of the surface.

Now that we know how the normal map is encoded we will look at how to create one. There is a spiffy tool from it allows you to create normal maps from textures using photoshop.

If you don't want to go through the hassle. I placed a download link for the normal map and texture I used in my temple demo.

So what we are going to go through now is probably the hardest part to understand in the beginning and i'll try not to get lost myself. heh. Firstly we need to be able to map our normal map and texture to an object. We need a space to specify this so we are going to create one called a texture space. Why do we need a texture space? We need one so that we are able to transform the light into this space and tell it how to react according to the normal map.

We need 3 axis to define this new space. So we take what they call a BiTangent, Tangent and Normal. This will be our basis of this "texture" space. a BiTangent is a line that is tangent to a curve at two distinct points. a Tangent is a line that touches a curve or solid at a single point and a Normal to a flat surface is a 3-dimensional vector that is perpendicular to that surface.

So what we are going to do is go from world space to object space then to texture space using our TBN Matrix. Lights are generally defined in world space, not object space. you need to transform them from worldspace to object space, and then to texture space, since the TBN Matrix is defined in object spaceTo illustrate this better I will use an example or schematic.

So we basically plug the texture unto the object. The normal map unto that aswell. We decompress the normal map and then with N.L ( Normal * Light ) which is the light equation.

Let's look at the code

float4x4 ModelViewProj : WORLDVIEWPROJ; //our world view projection matrix
float4x4 ModelViewIT : WORLDVIEWIT; //our inverse transpose matrix
float4x4 ModelWorld : WORLD; //our world matrix
float4 lightPos; //our light position in object space

texture texture0; //our texture
texture texture1; //our normal map

sampler2D texSampler0 : TEXUNIT0 = sampler_state
{
?????Texture = (texture0);
????MIPFILTER = LINEAR;
????MAGFILTER = LINEAR;
????MINFILTER = LINEAR;
};
sampler2D texSampler1 : TEXUNIT1 = sampler_state
{
????Texture = (texture1);
???MIPFILTER = LINEAR;
???MAGFILTER = LINEAR;
???MINFILTER = LINEAR;
};

//application to vertex structure
struct a2v
{
?????float4 position : POSITION0;
?????float3 normal : NORMAL;
?????float2 tex0 : TEXCOORD0;
???? float3 tangent : TANGENT;
?????float3 binormal : BINORMAL;
};

//vertex to pixel shader structure
struct v2p
{
?????float4 position : POSITION0;
?????float2 tex0 : TEXCOORD0;
?????float2 tex1 : TEXCOORD1;
?????float3 lightVec : TEXCOORD2;
?????float att : TEXCOORD3;
};

//pixel shader to screen
struct p2f
{
?????float4 color : COLOR0;
};

//VERTEX SHADER
void vs( in a2v IN, out v2p OUT )
{
?????//getting to position to object space
????OUT.position = mul(IN.position, ModelViewProj);

????//getting the position of the vertex in the world
????float4 posWorld = mul(IN.position, ModelWorld);

????//getting vertex -> light vector
????float3 light = normalize(lightPos - posWorld);

????//calculating the binormal and setting the Tangent Binormal and Normal matrix
????float3x3 TBNMatrix = float3x3(IN.tangent, IN.binormal , IN.normal);

????//setting the lightVector
????OUT.lightVec = mul(TBNMatrix, light);

????//calculate the attenuation
????OUT.att = 1/( 1 + ( 0.005 * distance(lightPos.xyz, posWorld) ) );
?
????OUT.tex0 = IN.tex0;
????OUT.tex1 = IN.tex0;
}

//PIXEL SHADER
void ps( in v2p IN, out p2f OUT )
{
????//calculate the color and the normal
????float4 color = tex2D(texSampler0, IN.tex0);

????/*this is how you uncompress a normal map*/
????float3 normal = 2.0f * tex2D(texSampler1, IN.tex1).rgb - 1.0f;

????//normalize the light
????float3 light = normalize(IN.lightVec);

????//set the output color
????float diffuse = saturate(dot(normal, light));

????//multiply the attenuation with the color
????OUT.color = IN.att * color * diffuse;
}

technique test
{
????pass p0
????{
????????vertexshader = compile vs_1_1 vs();
????????pixelshader = compile ps_2_0 ps();
????}
}

that's it. Easy huh? If you have any queries/comments/suggestions please do not hestitate to contact me.



Files for this tutorial

Filename Size
? normalmapping.rar 1,012.4 KB
?
MDX info is an initiative by vector4. All content is copyright ? 2005-2006 by its respective authors | About MDX info | Terms of Use |
Coming soon!