Problem with normal mapping

Started by
12 comments, last by FrogJohnson 13 years, 1 month ago
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.


the vertex shader

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;
}



the pixel shader


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


thanks in advance
manpreet
Advertisement
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.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
Thanks for the reply smasher!



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

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?

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 ? )


Thanks for your replies!

Manpreet
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.

normalmappic.png



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
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 :P

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;
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
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

shaderproj5201103070043.png


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

thanks for all your help
Manpreet
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
Then in the vertex shader add this

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.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.
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
There's no "hard", and "the impossible" takes just a little time.

This topic is closed to new replies.

Advertisement