Skip to main content
GameDev.net gamedev.net
🔒 Locked

[HLSL] Normal mapping hates me

Started by n3Xus Aug 24, 2008 at 2:34 PM 8 replies 5.9k views
Original Post
n3Xus
n3Xus
I spent the last two days trying to figure out normal mapping without much success. I had it partially working a few times but the lighting is still incorrect. A question: why must I multiply my normal, tangent and binormal with the inverse transpose of the view*world matrix? Anyway, I went through two or 3 different tuts and one of them uses the inverse transpose matrix and some other don't so I'm very confused ;S Anyway, here is some code, what am I doing wrong?? GLOBAL SHADER VALUES:

float4x4 world;        // World matrix
float4x4 viewproj;     // View*projection matrix
float4x4 worldviewit;  // The inverse transpose of the view*world matrix
float4 WorldLight;     // The light position in world



VERTEX SHADER:

	// Multiply vertex by world & viewproj matrix
	output.pos=mul(float4(input.pos,1.0f),world);
	output.pos=mul(output.pos,viewproj);
	
	// Get the vertex normal and multiply it by the world matrix
	float4 normal=mul(float4(input.norm,1.0f),world);
	
	// Get the position of the vertex in world space
	float4 posWorld=mul(float4(input.pos,1.0f),world);
	
	// Get light vector
	float3 lightv=normalize(WorldLight-posWorld);
	
	// Calculate tan, binormal and normal by multiplying them with the inverse transpose of the view*world matrix 
	float3 tan=mul(float4(input.tan,1.0f),worldviewit).xyz;
	float3 bin=mul(float4(input.binorm,1.0f),worldviewit).xyz;
	float3 norm=mul(float4(input.norm,1.0f),worldviewit).xyz;
	
	// Tangent space matrix
	float3x3 tbnm=float3x3(tan,bin,norm);
	
	// Put light into tangent space
	output.light=mul(tbnm,lightv);
	
	// Texcoord
	output.texcoord=input.texcoord;

PIXEL SHADER:

// Get the normal from the normal map
	float3 bump=2*normalmap.Sample(sampler1,input.texcoord).rgb-1.0f;
	
	// Light vector
	float3 light=normalize(input.light);
	
	// Light power 
	float diff=saturate(dot(bump,light));	
	
	// Color*light power	
	return float4(0.8f,0.0f,0.0f,1.0f)*diff;

AvengerDr
AvengerDr
A suggestion: it may be easier to calculate tangents, normals and binormals on the CPU upon loading the mesh. Since it is a task that is only done one time, I see no particular reason not to take advantage of it (but maybe there are?).

Anyway this is the code I use to generate tangents, normals and binormals (in C#/SlimDX):

public void ComputeTangentsAndBinormal(){    VertexElement[] elements = new VertexElement[]{        new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),        new VertexElement(0, 12, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Normal, 0),        new VertexElement(0, 24, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),        new VertexElement(0, 32, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Tangent, 0),        new VertexElement(0, 44, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Binormal, 0),        VertexElement.VertexDeclarationEnd    };    Mesh tempMesh = meshObject.Clone(Game.Device, MeshFlags.Managed, elements);    meshObject.Dispose();    meshObject = tempMesh;    meshObject.ComputeTangent(0, 0, 0, false);}


The reason for this suggestion is that you can be sure that all of those elements will be correct when they are used into a shader and you won't have to worry about recalculating them each time in every shader that makes use of them.
Namethatnobodyelsetook
Namethatnobodyelsetook
You must do all the calculations in the same space. You've moved the tan, bin, and norm into view space, but the light was in world space. Either transform the light into viewspace, or just use the world inv.transpose so that everything is done in world space.

While the inverse transpose is technically correct, the cases where just using the regular matrix fails are rare and can be entirely avoided. Cases such as a world matrix containing a non-uniform scale (different scales on 1 or more axes), shears (y=x*0.1f), break when not using the inverse transose. A regular rotation, translation, and a uniform scale can get away without inverse transpose matrices, which becomes a nice thing once you start dealing with large arrays of bone matrices, especially on older hardware.
n3Xus
n3Xus
Thanks for your replies but it still doesn't work as it should.

Namethatnobodyelsetook: I've put my light into view space but now the light somehow circles around the sphere (although it is static). And when I tried using the world inverse transpose matrix the normal map was lit incorrect-I'm using a normal map with a bump texture, and the edges of the bumps were lit incorrectly (at the beginning they were lit correctly, but than it was like if the light source started to dance around them, as if the light started to rotated :S

AvengerDr: I'm not sure what you mean by calculating them at startup? I load tangents and binormals from the mesh, than I do the required multiplication (which obviously has an error somewhere...).

So if you have any other suggestions, please write them down since I'm beginning to lose my nerves here...

EDIT: I've used a blank normal map and the light "dances" a bit on the surface and every once in a while a small very bright dot appears :S


[Edited by - n3Xus on August 25, 2008 2:51:38 AM]
n3Xus
n3Xus
I'm also wondering how do the tangent and the binormal vector look like in world space? I've done some tests this way and before I got the results a thought that the normal and the binormal vectors would be in the same plane as a single face, but they werent. They were both a bit off. Should it be like that?
MJP
MJP
Your binormal points in the direction of increasing V coordinates, and the tangent in the direction of increasing U coordinates.

[Edited by - MJP on August 25, 2008 8:09:05 AM]
greenhybrid
greenhybrid
Quote:
Original post by AvengerDr
A suggestion: it may be easier to calculate tangents, normals and binormals on the CPU upon loading the mesh. Since it is a task that is only done one time, I see no particular reason not to take advantage of it (but maybe there are?).


One (two) possible reason(s): save memory to crunch as much vertices into memory as possible, or bandwidth saving (CPU power grows faster than bandwidth). At least calculating normals is cheap (the Möller/Trumbore-Ray/Triangle intersection routine does by default).
n3Xus
n3Xus
Quote:
Original post by MJP
Your binormal points in the direction of increasing U coordinates, and the tangent in the direction of increasing V coordinates.


Thanks, this means than my tangents & binormals are incorrect...
MJP
MJP
Quote:
Original post by n3Xus

Thanks, this means than my tangents & binormals are incorrect...


Actually what I posted was backwards, the tangent points in the U direction while the binormal is in the V direction. Terribly sorry about that. [embarrass]



n3Xus
n3Xus
No problem XD, that didn't matter much since they weren't alligned with the UV coordinates anyway.

It's kinda funny if I look at it now...all this trouble I had with normal mapping was because my tangents and binormals were "broken". When I was testing them a couple of days ago I thought they were ok, but now that I've done a bit more thorough tests I noticed they were totally off.. :@

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.