ANOTHER SAVAGE DAY ON PLANET EARTH (RELOADED)

Tutorial de Normal Mapping


In this page you will find the necesary code to create a shader completely functional which uses a customized version of the cubic environment mapping technique. As we know, the cubic environment map is used to create accurate reflections of an environment far better than the traditional spherical mapping environment map, using a special texture which creates an environment. The main problem would be that if our engine didn't support them, we could have a problem, and thus, we create this shader. This code is HLSL (DirectX tm) Although is quite simple, and probably can be ported to OPEN GL without much troubles.

This is the Vertex Shader

float3 fvLightPosition1;
                 float3 fvLightPosition2;
                 float3 fvEyePosition;
                 float4x4 matWorld;
                 float4x4 matWorldViewProjection;
                 float4x4 matWorldInv;
struct VS_INPUT 
                   {
                   float4 Position : POSITION0;
                   float2 Texcoord : TEXCOORD0;
                   float3 Normal :   NORMAL0;
                   float3 Binormal : BINORMAL0;
                   float3 Tangent :  TANGENT0;
   
                   };
struct VS_OUTPUT 
                   {
                   float4 Position :        POSITION0;
                   float2 Texcoord :        TEXCOORD0;
                   float3 ViewDirection :   TEXCOORD1;
                   float3 LightDirection1:   TEXCOORD2;
                   float3 LightDirection2:   TEXCOORD3;
                   float3 truViewDir:        TEXCOORD4;
                   float3 truNormal:         TEXCOORD5;
   
                   };
VS_OUTPUT vs_main( VS_INPUT IN )
                   {
                   VS_OUTPUT OUT;
                   OUT.Position = mul( IN.Position, matWorldViewProjection );
                   OUT.Texcoord = IN.Texcoord;
   
                   float3 RealPos = mul(IN.Position, matWorld);
   
                   float3 viewVec = fvEyePosition - RealPos;
                   OUT.truViewDir=viewVec;
                   float3 lightVec1 = fvLightPosition1 - RealPos;
                   float3 lightVec2 = fvLightPosition2 - RealPos;
   
                   float3 tViewVec;
                   float3 tLightVec1;
                   float3 tLightVec2;
 float3 Tangent = normalize(IN.Tangent);
                   float3 Normal = normalize(IN.Normal);
                   float3 Binormal = cross( Normal , Tangent );
                   Tangent = cross( Normal,Binormal);
   
                   Tangent = mul( matWorldInv,Tangent );
                   Binormal = mul( matWorldInv,Binormal );
                   Normal = mul( matWorldInv,Normal );
                   OUT.truNormal=Normal;
   
                   tViewVec.x = dot(viewVec,Tangent);
                   tViewVec.y = dot(viewVec,Binormal);
                   tViewVec.z = dot(viewVec,Normal);
   
                   tLightVec1.x = dot(lightVec1,Tangent);
                   tLightVec1.y = dot(lightVec1,Binormal);
                   tLightVec1.z = dot(lightVec1,Normal);
 tLightVec2.x = dot(lightVec2,Tangent);
                   tLightVec2.y = dot(lightVec2,Binormal);
                   tLightVec2.z = dot(lightVec2,Normal);
   
                   OUT.ViewDirection = tViewVec;
                   OUT.LightDirection1 = tLightVec1;
                   OUT.LightDirection2 = tLightVec2;
   
                   return( OUT );
                   }

And this, the pixel Shader

float4 LightColor1;
float4 LightColor2;
sampler2D baseMap;
sampler2D bumpMap;
sampler2D refMap;
float2 cube2uv(float3 IN){
                   float2 uv=0;
                   float3 inSQ=IN;
if(inSQ.x<0){inSQ.x=-inSQ.x;}
                   if(inSQ.y<0){inSQ.y=-inSQ.y;}
                   if(inSQ.z<0){inSQ.z=-inSQ.z;}
//given we positively know that we will never divide by 0, we can safely do these calculations.
//we map the texture to 6 squares of an 8 squares 2x1 pot texture. This is done like this to avoid
//rounding errors, although we are wasting a 25% of the texture space.
if(inSQ.x>=inSQ.y&&inSQ.x>=inSQ.z){
                   if(IN.x>0){
                   uv.x=0.125;
                   uv.y=0.25;
                   uv.x+=IN.z/IN.x*0.125;
                   uv.y-=IN.y/IN.x*0.25;
                   }else{
                   uv.x=0.625;
                   uv.y=0.25;
                   uv.x+=IN.z/IN.x*0.125;
                   uv.y+=IN.y/IN.x*0.25;
                   }
                   }
                   if(inSQ.y>inSQ.x&&inSQ.y>=inSQ.z){
                   if(IN.y>0){
                   uv.x=0.125;
                   uv.y=0.75;
                   uv.x-=IN.x/IN.y*0.125;
                   uv.y+=IN.z/IN.y*0.25;
                   }else{
                   uv.x=0.625;
                   uv.y=0.75;
                   uv.x+=IN.x/IN.y*0.125;
                   uv.y+=IN.z/IN.y*0.25;
                   }
                   }
                   if(inSQ.z>inSQ.x&&inSQ.z>inSQ.y){
                   if(IN.z>0){
                   uv.x=0.375;
                   uv.y=0.25;
                   uv.x-=IN.x/IN.z*0.125;
                   uv.y-=IN.y/IN.z*0.25;
                   }else{
                   uv.x=0.375;
                   uv.y=0.75;
                   uv.x-=IN.x/IN.z*0.125;
                   uv.y+=IN.y/IN.z*0.25;
 }
                   }
                 
return uv; 
                   }
struct PS_INPUT 
                   {
                   float2 Texcoord :        TEXCOORD0;
                   float3 ViewDirection :   TEXCOORD1;
                   float3 LightDirection1:   TEXCOORD2;
                   float3 LightDirection2:   TEXCOORD3;
                   float3 truViewDir:        TEXCOORD4;
                   float3 truNormal:         TEXCOORD5;
   
                   };
float4 ps_main( PS_INPUT IN ) : COLOR0
                   { 
                   float4 base = tex2D(baseMap,IN.Texcoord);
                   float3 normal = tex2D(bumpMap,IN.Texcoord);
                   normal = normal*2-1;
   
                   float3 viewDir = normalize(IN.ViewDirection);
   
                   float3 lightDir1 = normalize(IN.LightDirection1);
                   float3 lightDir2 = normalize(IN.LightDirection2);
   
                   normal = normalize(normal);
                   float3 truNormal=normalize(IN.truNormal);
   
                   float3 halfView1 = normalize(viewDir+lightDir1);
                   float3 halfView2 = normalize(viewDir+lightDir2);
                   float3 reflVec = reflect(-IN.truViewDir,truNormal);
                   float4 refl = tex2D(refMap,cube2uv(reflVec));
   
                   float diffuse1 = dot(normal,lightDir1)*1;
                   float specular1 = pow(dot(halfView1,normal),68)*base.w;
   
                   float diffuse2 = dot(normal,lightDir2)*1;
                   float specular2 = pow(dot(halfView2,normal),68)*base.w;
   
                   return(saturate(base *(diffuse1+specular1)*LightColor1 + 
                   base *(diffuse2+specular2)*LightColor2+refl*0.95));
                   }

The code marked in blue is the most important part of these shaders, There, we calculate how to pass a 3D vector to a 2D mapping coordinates specifically sorted, as follows:

no Image

And this render would be the resulting effect.

No Image

Copyright 2008/2009 Santiago A. Navascués González.