HLSL Tutorial - Normal mapping (Bump mapping technique)
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 NVIDIA.
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.