• 13
• 16
• 27
• 9
• 9

# Problem with normal mapping

This topic is 2564 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

hey folks,

I'm trying to create a normal mapped cylinder (dx 9 btw) with simple diffuse + ambient light.But what im getting is this ......The part which is facing the light is getting uniformly lit and the part which is away from it is only shaded with the ambient term , and there is an abrupt transition between the two , not a continous one.And the part which is getting lit is simply getting lit , seems as if the normal map isnt working . I've attached the screen shot below(sorry but i cant seem to open imageshack on my college net and i dont know of any other site)

And also , im not using effect files(once again ) but separate pixel and vertex shaders.

 matrix world,view,proj; matrix worldviewproj,viewproj; matrix worldInv,rotation; float4 LDir = {1.0f,0.0f,0.0f,0.0f}; vector EyePos; struct input { vector position : POSITION0; float3 tangent : TANGENT0; float3 binormal : BINORMAL0; float3 normal : NORMAL0; float2 texcoord : TEXCOORD0; }; struct output { vector position : POSITION0; float2 texcoord : TEXCOORD0; float3 toEye : TEXCOORD1; float3 LDir : TEXCOORD2; }; output VSMain(input ip) { float3x3 TBN; TBN[0] = ip.tangent; TBN[1] = ip.binormal; TBN[2] = ip.normal; float3x3 TSpace = transpose(TBN); //setup the object to tangent space matrix output op = (output)0; worldviewproj = mul(mul(world,view),proj); op.position = mul(ip.position,worldviewproj); EyePos = mul(EyePos,worldInv); // transform the eye pos from world space to object space vector toEye = EyePos - ip.position; op.toEye = mul(toEye,TSpace); //transform to tangent space op.texcoord = ip.texcoord*4.0f; // pass the tiled texture coordinates LDir = mul(LDir,rotation); LDir = mul(LDir,worldInv); //transform light direction from world to object space op.LDir = mul(LDir,TSpace); // transform to tangent space return op; } 

 float4 LAmb = {0.3f,0.3f,0.3f,0.0f},LDff = {1.0f,1.0f,1.0f,0.0f}; float4 Mtrl = {1.0f,1.0f,1.0f,0.0f}; sampler Tex; sampler NMap; struct input { float2 TexC : TEXCOORD0; float3 toEye : TEXCOORD1; float3 LDir : TEXCOORD2; }; struct output { float4 color : COLOR0; }; output PSMain(input ip) { output op = (output)0; ip.toEye = normalize(ip.toEye); ip.LDir = normalize(ip.LDir); float3 normal = tex2D(NMap,ip.TexC); //sample the normal mal normal = 2.0f*normal - 1.0f; normal = normalize(normal); float s = max(dot(ip.LDir,normal),0.0f); vector a = tex2D(Tex,ip.TexC); //get the texture color op.color = a*Mtrl*(LAmb + (LDff * s)); return op; } 

Note : you'll see that even though im not doing any specular calculation , ive still included the toEye term,thats because earlier i had included the specular term but then omitted it since i couldnt get even the diffuse to work so decided to fix it first

manpreet

##### Share on other sites
try sending the norma, bi tangent (sometimes referred to as bi normal, but that's a misnomer), and the tangent to the pixel shader. Then sample your normal map, and do the *2 -1, which you do already.
Once that is done, use the normal that you sampled and use it in this code

normal = normalize(Tangent * normal.x + Bitangent * normal.y + Normal * normal.z);

Most normal maps are all in tangent space, and you have to transform the normal into world space, assuming that is the space your LDIR is located.

##### Share on other sites

try sending the norma, bi tangent (sometimes referred to as bi normal, but that's a misnomer), and the tangent to the pixel shader. Then sample your normal map, and do the *2 -1, which you do already.
Once that is done, use the normal that you sampled and use it in this code

normal = normalize(Tangent * normal.x + Bitangent * normal.y + Normal * normal.z);

Most normal maps are all in tangent space, and you have to transform the normal into world space, assuming that is the space your LDIR is located.

Hmm correct me if I'm wrong here , so you are saying that instead of transforming the light direction to tangent space , i should just transform everything to world space and then do the lighting stuff?
I'll surely try that (once this stupid exam of mine gets over).

Apart from that , something else has come up.

I saw Ben cloward's normal map shaders and what he has done is that instead of just writing

LDir = normalize(LDir);

in the pixel shader , he wrote

LDir = normalize(2*LDir - 1.0f);

Doing this has solved the problem for me though , and it makes some sense to convert the unit vectors from [0,1] to [-1,1] , but i still cant fully understand how this is working .
Any insights on this guys?

Thanks!
Manpreet

##### Share on other sites

Hmm correct me if I'm wrong here , so you are saying that instead of transforming the light direction to tangent space , i should just transform everything to world space and then do the lighting stuff?
I'll surely try that (once this stupid exam of mine gets over).
[/quote]
Yes that is exactly the idea. Working in TS is possible, but as i recently learned can be fraught with problems, especially if there is any scaling involved in the world transform.

Apart from that , something else has come up.

I saw Ben cloward's normal map shaders and what he has done is that instead of just writing

LDir = normalize(LDir);

in the pixel shader , he wrote

LDir = normalize(2*LDir - 1.0f);

Doing this has solved the problem for me though , and it makes some sense to convert the unit vectors from [0,1] to [-1,1] , but i still cant fully understand how this is working .
Any insights on this guys?

Thanks!
Manpreet
[/quote]

Assuming LDir is direction the light is travelling ( a directional light in this case) I can't see why this is even wanted. If Lights are being read from a Texture(such as in a deferred lighting context) I could see the expansion as necessary, but not in this case. What is Matrix4 rotation used as?

##### Share on other sites

Assuming LDir is direction the light is travelling ( a directional light in this case) I can't see why this is even wanted. If Lights are being read from a Texture(such as in a deferred lighting context) I could see the expansion as necessary, but not in this case. What is Matrix4 rotation used as?

That is exactly whats confusing me

About the "rotation" i just wanted to experiment with the light a bit so i made it rotate around the global y axis , hence the rotation matrix.

Anyway , just want to ask how to go about it then ? assuming that i dont have any scaling in my world matrix,working in tangent space or in world?

Although i understand that either of the method will do fine , but i just felt like asking ( also i can see that working in world space will mean more input data for the pixel shader , but i dont think it wil have some serious side effect , would it ? )

Manpreet

##### Share on other sites
Ok, a little update

So I tried the method above of transforming the normals from tangent space to world space and doing all lighting stuff in the latter space but here comes another problem.
Now the diffuse lighting is working perfectly as it should be(ie no abrupt transition) but it seems that the normal map isnt being sampled at all.
In other words , the cylinder looks like its is being lit with its standard mesh normals , not the N-Map ones.

ill just list the changed vs/ps code here

the vs
 /*everything before the output struct remains unchanged*/ struct output { /* structure is same , just added these 3 */ float3 tangent : TEXCOORD3; float3 binormal : TEXCOORD4; float3 normal : TEXCOORD5; }; output VSMain(input ip) { /* Omitted the calculation of the TBN basis as well as the transformation of the light direction LDir and the camera position EyePos to the object space Rest everything same ( double checked ) */ .......... ip.position = mul(ip.position,world); // transform the position to world space , wasnt doing this earlier , was instead multiplying directly with worldviewprojection matrix vector toEye = EyePos - ip.position; ........ op.LDir = normalize(LDir); //simply pass on the normalized light direction vector op.tangent = ip.tangent; op.binormal = ip.binormal; op.normal = ip.normal; return op; } 

the ps
 /* everything same before input */ struct input { /*struct is same , just these 3 added */ float3 tangent : TEXCOORD3; float3 binormal : TEXCOORD4; float3 normal : TEXCOORD5; }; struct output { float4 color : COLOR0; }; output PSMain(input ip) { output op = (output)0; /* calculate the tbn basis , which was previously done in the vertex shader */ float3x3 TBN; TBN[0] = ip.tangent; TBN[1] = ip.binormal; TBN[2] = ip.normal; ip.toEye = normalize(ip.toEye); ip.LDir = normalize(ip.LDir); float3 normal = tex2D(NMap,ip.TexC); //sample the normal map normal = 2.0f*normal - 1.0f; normal = normalize(mul(mul(normal,TBN),world)); //transform the normsl from tangent to object space , and then to world space float s = max(dot(ip.LDir,normal),0.0f); vector a = tex2D(Tex,ip.TexC); //sample the texture color op.color = a*Mtrl*(LAmb + LDff*s); return op; } 

It was working fine if i did it in tangent space but doesnt seem to work here.
I know that probably somewhere I've done some tiny little mistake but cant seem to figure it out.
Maybe the shaders are fine but the problem lies somewhere in the main application code?

Sorry to bother again and Thanks for all the help till now
Manpreet

EDIT : Fixed the image link

##### Share on other sites
Try this
 output VSMain(input ip) { /* Omitted the calculation of the TBN basis as well as the transformation of the light direction LDir and the camera position EyePos to the object space Rest everything same ( double checked ) */ .......... ip.position = mul(ip.position,world); // transform the position to world space , wasnt doing this earlier , was instead multiplying directly with worldviewprojection matrix vector toEye = EyePos - ip.position; ........ op.LDir = normalize(LDir); <----------------- DELETE THIS NOT NEEDED HERE, Should be a global variable op.tangent = ip.tangent; op.binormal = ip.binormal; op.normal = ip.normal; return op; } output PSMain(input ip) { output op = (output)0; /* calculate the tbn basis , which was previously done in the vertex shader */ ip.toEye = normalize(ip.toEye); ip.LDir = normalize(LDir); <----Changed to reflect the fact it should be a global variable float3 normal = tex2D(NMap,ip.TexC)*2.0f -1.0f; //sample the normal map <-------------Added the *2.0f-1.0f to here to shorten the code normal= normalize(ip.tangent * normal.x + ip.binormal* normal.y + ip.normal * normal.z);<-----This is the proper way to do it . . . float s = max(dot(ip.LDir,normal),0.0f); vector a = tex2D(Tex,ip.TexC); //sample the texture color op.color = a*Mtrl*(LAmb + LDff*s); return op; } 

try that out. If that doesnt work, then output the normals as color and take a screen shot and post it. like this

op.color = normal;

##### Share on other sites
hi smasher , thanks for the reply

I tried exactly what you've suggested , but still no different
Also the normal map is being displayed correctly as a color texture

Should be a global variable[/quote]

what do you mean by this????Like i said earlier , im not using effect files , but separate text files for the vertex and pixel shaders.
So I'm assuming you meant that move the Light direction LDir declaration to the pixel shader , is that correct?

normal= normalize(ip.tangent * normal.x + ip.binormal* normal.y + ip.normal * normal.z);<-----This is the proper way to do it . . .
[/quote]

Ah apologies for not mentioning it earlier , but my cylinder is rotated 90 degrees along the x axis , so just doing what you suggested will result in the normals being in object space whereas the light dir is in world space , so thats why i multiplied it with the world matrix.
Otherwise the line

normal= normalize(ip.tangent * normal.x + ip.binormal* normal.y + ip.normal * normal.z);

was giving me the same result as writing

normal= normalize(mul(normal,TBN));

that is , instead of rotating a complete 360 around the cylinder , the light was rotating 180 around it and then coming back ( oscillating if you will )
Just a trivial detail .

is there something wrong with the TBN matrix or the way im calculating it????

I guess that, for me, settling for the tangent space approach for now might be the best bet ,although i'd still like to know why this isn't working

Manpreet

##### Share on other sites
Ahhhh okie.

I cant believe I forgot to mention this. In the vertex shader, you have to transfer your tangent, normal and bi tangent by the InverseTranspose of your world matrix

So, in your c++ code, create your world matrix, then take the inverse of that, then do the transpose of it.
Then create another global variable to be used in your vertex shader say.....

matrix InverseTransposeWorld;

make sure to set this each time you have a different world matrix

op.tangent = mul(ip.tangent, (float3x3)InverseTransposeWorld);

op.binormal = mul(ip.binormal, (float3x3)InverseTransposeWorld);

op.normal = mul(ip.normal, (float3x3)InverseTransposeWorld);

Then in your pixel shader, use the code I posted earlier and you should be good.

doing 3 matrix mul and a vector mul is more expensive than doing 3 vector scalings and 3 additions to get your normal in world space.

##### Share on other sites
Make sure that you have tangent, binormal and normal vectors calculated on your mesh.

If you're using ID3DXMesh object, you can call D3DXComputeTangentFrameEx() to compute TBN basis vectors; but if you're using a simple vertex buffer and calling Draw...Primitive() or Draw...PrimitiveUP() to draw your cylinder, you have to calculate tangent, normal and binormal vectors manually for it.

hth.
-R