# Help with normal mapping (in tangent space)

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

## Recommended Posts

As far as I can tell, the steps are as follows:
Quote:
 Original post by Ysaneya - the binormal/tangent is passed to the vertex shader and the tangent space matrix (matrix going from object space to tangent space) is computed with a cross product. - the LightPos is passed to the vertex shader and the world-to-object matrix is applied to it. The light direction is computed in object space (LDir = LightPosObj - vertex). Then L is computed by transforming LDir with the object-to-tangent space matrix and passed to the pixel shader. - N is fetched from the normal map in the pixel shader and (N dot L) is computed (after the usual normalizations of both N and L).
However, things are still off. I've searched all over the internet, read most of the literature, and still have a problem. In that, I submit the code, and ask for your help.
//=====================================================================
//
// Globals
//
//=====================================================================
// world-matrix
float4x4 world_matrix;
// inverse
float4x4 worldi_matrix;
// these two should be concatenated
float4x4 view_matrix;
float4x4 projection_matrix;
// calculated (probably should be passed in)
float4x4 wvp_matrix;

// passed in
float4 global_ambient;
float3 camera_position;

texture texture_detail;
texture texture_normal_map;

//=====================================================================
//
// Constants
//
//=====================================================================
// directional light
float3 light_position = { 12000, 12000, 6 };
float3 light_direction = { -1.0, 0.0, 0.0 };
float4 light_diffuse = { 1.0, 1.0, 1.0, 1.0 };

float4 material_ambient = { 0.33, 0.33, 0.33, 1.0 };
float4 material_diffuse = { 1.0, 1.0, 1.0, 1.0 };

sampler texture_detail_sampler =
sampler_state
{
Texture = <texture_detail>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};

sampler normal_mapping_sampler =
sampler_state
{
Texture = <texture_normal_map>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};

//=====================================================================
//
//
//=====================================================================

//=====================================================================
// Structures
//=====================================================================
struct vs1_in
{
// position
float4 position : POSITION;
// normal (for bump mapping)
float3 normal : NORMAL;
// actual texture coordinate
float2 texcoord : TEXCOORD0;
// tangent and bitangent for tangent space
float3 tangent : TANGENT;
// one of the three of these (TBN) can be dropped,
// and calculated as a cross-product
float3 bitangent : BINORMAL;
};

struct vs1_out
{
// position
float4 vs_position : POSITION;
float4 position : TEXCOORD0;
// normal
float3 normal : TEXCOORD1;
// actual texture coordinate
float2 texcoord : TEXCOORD2;
// transformed light vector
float3 light_vector : TEXCOORD3;
};

struct ps1_in
{
float4 position : TEXCOORD0;
float3 normal : TEXCOORD1;
float2 texcoord : TEXCOORD2;
float3 light_vector : TEXCOORD3;
};

//=====================================================================
//=====================================================================
vs1_out VS1(vs1_in vin)
{
// calculate world-view-projection matrix
wvp_matrix = mul(mul(world_matrix, view_matrix), projection_matrix);
// calculate the tangent-space matrix
float3x3 tbn_matrix = float3x3(vin.tangent, vin.bitangent, vin.normal);
// put light position into object space
float3 light_position_os = mul(worldi_matrix, light_position);
// calculate light-vector (in tangent space)
float3 light_vector = mul(tbn_matrix, light_position_os - vin.position);

// our out structure
vs1_out vout;
// put position into projection coordinates
vout.position		= mul(vin.position, wvp_matrix);
vout.vs_position	= vout.position;
// not actually used at the moment
vout.normal         = mul(vin.normal, world_matrix);
// copy light across
vout.light_vector	= light_vector;
// texture coordinates are the same for both
vout.texcoord		= vin.texcoord;

return vout;
}

//=====================================================================
//=====================================================================
float4 PS1(vs1_out pin) : COLOR
{
// position
float3 P = pin.position.xyz;
// normal (normalize probably not necessary)
float3 N = normalize(2.0f * tex2D(normal_mapping_sampler, pin.texcoord).rgb - 1.0f);
// light vector
//float3 L = normalize(light_position - P);
float3 L = pin.light_vector;
// view vector
float3 V = normalize( P - float3(0, 0, -30));
// half vector
float3 H = normalize(L + V);

// ambient colour
float4 final_ambient_color = global_ambient * material_ambient;

// diffuse colour
// commented out for simplicity
//float4 texture_color = tex2D(texture_detail_sampler, pin.texcoord);
float4 texture_color = material_diffuse;
float NdL = saturate(dot(N, L));
float4 final_diffuse_color = texture_color * NdL * light_diffuse;

// specular colour
float NdH = saturate(dot(N, H));
float spec_light = pow(NdH, 30);
if (NdL <= 0 || spec_light < 0) spec_light = 0;
float4 final_specular_color = spec_light * light_diffuse;

// final color
float4 final_color = final_ambient_color + final_diffuse_color; // + final_specular_color;
final_color.a = material_diffuse.a;
return final_color;
}

//=====================================================================
// Technique
//=====================================================================
technique level_1
{
pass P0
{
}
}



##### Share on other sites
Can you describe your problem (screenshot or more details of what you get) ?
Your shader code seems to be ok (but I didn't read it in details), maybe you should check your tangent and bitangent computing.

##### Share on other sites
That, and in the pixel shader, you are not normalizing L, so your NdotL will be wrong.

Y.

##### Share on other sites
Okay, here is the update.

I've adapted the code from here to calculate the tangent, putting to rest my suspicions that I was doing it wrong (I was doing it with a lot less code, however). This has the added benefit of not needing to store the bitangent, which is nice. Anyway, here is the updated source:

//=====================================================================//// Globals////=====================================================================// world-matrixfloat4x4 world_matrix;// inversefloat4x4 worldi_matrix;// these two should be concatenatedfloat4x4 view_matrix;float4x4 projection_matrix;// calculated (probably should be passed in)float4x4 wvp_matrix;// passed infloat4 global_ambient;float3 camera_position;texture texture_detail;texture texture_normal_map;//=====================================================================//// Constants////=====================================================================// directional lightfloat4 light_position = { 1000, 0, 0, 0 };float3 light_direction = { -1.0, 0.0, 0.0 };float4 light_diffuse = { 1.0, 1.0, 1.0, 1.0 };float4 material_ambient = { 0.33, 0.33, 0.33, 1.0 };float4 material_diffuse = { 1.0, 1.0, 1.0, 1.0 };sampler texture_detail_sampler = sampler_state	{		Texture = <texture_detail>;		MipFilter = LINEAR;		MinFilter = LINEAR;		MagFilter = LINEAR;	};sampler normal_mapping_sampler =	sampler_state	{		Texture = <texture_normal_map>;		MipFilter = LINEAR;		MinFilter = LINEAR;		MagFilter = LINEAR;	};//=====================================================================////  Level 1 Shading////=====================================================================//=====================================================================// Structures//=====================================================================struct vs1_in{	// position    float4 position : POSITION;	// normal (for bump mapping)	float4 normal : NORMAL;	// tangent for tangent space	float4 tangent : TANGENT;	// actual texture coordinate	float2 texcoord : TEXCOORD0;};struct vs1_out{	// position	float4 position : POSITION;	// position for ps	float4 ps_position : TEXCOORD0;	// normal	float3 normal : TEXCOORD1;	// actual texture coordinate	float2 texcoord : TEXCOORD2;	// transformed light vector	float3 light_vector : TEXCOORD3;};struct ps1_in{	float4 position : TEXCOORD0;	float3 normal : TEXCOORD1;	float2 texcoord : TEXCOORD2;	float3 light_vector : TEXCOORD3;};//=====================================================================// Vertex Shader//=====================================================================vs1_out VS1(vs1_in vin){	// homogonise TBN	float3 nw = normalize(vin.normal);	float3 tw = normalize(vin.tangent.xyz);	float3 bw = cross(nw, tw) * vin.tangent.w;	// calculate world-view-projection matrix	wvp_matrix = mul(mul(world_matrix, view_matrix), projection_matrix);	// calculate the tangent-space matrix	float3x3 tbn_matrix = float3x3( tw, bw, nw );		// get light position in object space	float3 light_position_os = mul(light_position, worldi_matrix);	// calculate light-vector (in object space)	float3 light_vector = normalize(light_position_os - vin.position).xyz;	// transform light-vector to TBN space	light_vector = mul(tbn_matrix, light_vector);	// our out structure	vs1_out vout;	// put position into projection coordinates	vout.position		= mul(vin.position, wvp_matrix);	vout.ps_position	= vout.position;	// shouldn't be an issue for tangent-space normal mapping	vout.normal			= mul(vin.normal, wvp_matrix);	// copy light across	vout.light_vector	= light_vector;	// texture coordinates are the same for both maps	vout.texcoord		= vin.texcoord;	// done!	return vout;}//=====================================================================// Pixel Shader//=====================================================================float4 PS1(vs1_out pin) : COLOR{	// position	float3 P = pin.ps_position.xyz;	// normal (normalize probably not necessary)	//float3 N = normalize(pin.normal);	float3 N = normalize(2.0f * tex2D(normal_mapping_sampler, pin.texcoord).xyz - 1.0f);	// light vector	float3 L = normalize(pin.light_vector);	// view vector	float3 V = normalize( P - float3(0, 0, -30));	// half vector	float3 H = normalize(L + V);		// ambient colour	float4 final_ambient_color = global_ambient * material_ambient;	// diffuse colour	// commented out for simplicity	//float4 texture_color = tex2D(texture_detail_sampler, pin.texcoord);	float4 texture_color = material_diffuse;	float NdL = saturate(dot(N, L));	if (NdL < 0) NdL = 0;	float4 final_diffuse_color = texture_color * NdL * light_diffuse;	// specular colour	float NdH = saturate(dot(N, H));	// we'll keep it at a constant 8 for now	float spec_light = pow(NdH, 8);	if (spec_light < 0) spec_light = 0;	float4 final_specular_color = spec_light * light_diffuse;	// final color	float4 final_color = final_ambient_color + final_diffuse_color + final_specular_color;	final_color.a = material_diffuse.a;	return final_color;}//=====================================================================// Technique//=====================================================================technique level_1{    pass P0    {        // shaders        VertexShader = compile vs_3_0 VS1();        PixelShader  = compile ps_3_0 PS1();    }  }

And here are the results:

As you can see, it kind of works, but only for about a third of the model. As it rotates, those lit parts fade in and out correctly, however, the rest of the model remains black the entire time. This is what initially led me to believe my normals/tangents/bitangents were off. When I remove the transformation by the TBN matrix, I get this:

That is to say, the entire model gets darker and brighter as a whole, as it rotates. It's really quite weird (and obviously not correct as we're not multiplying by the TBN matrix). I thought it might help you understand the problem. I've read so much material on this subject, and yet it still refuses to work correctly. :/

Any help would be much appreciated.

##### Share on other sites
When you output only the NdotL term, is it also wrong ?

Are your normal maps indeed using +Z as their up vector ?

Failing that, did you try to output your normals in 3D and verify that they're correctly oriented ? As long as the normals are correct, you shouldn't see something as bad as in those pics, even if the tangents/binormals are wrong..

If the normals are correct (and I would expect them to be), then the next serious candidate are parameters you pass to the vertex shader. This "worldi_matrix" matrix for example, are you sure it contains the correct values (the matrix going from world space to object space) ?

Finally, it could be a problem with the order of operations. In
vout.position = mul(vin.position, wvp_matrix);
you are multiplying a vector by a matrix, while in
light_vector = mul(tbn_matrix, light_vector);
you are multiplying a matrix by a vector.

Oh, and I lied. Here's another candidate that looks seriously wrong:
float3 light_vector = normalize(light_position_os - vin.position).xyz;

... you are normalizing a 4D vector. Instead you should use:
float3 light_vector = normalize(light_position_os - vin.position.xyz);

Hopefully one of those will be your problem..

Y.

• 33
• 12
• 10
• 9
• 9
• ### Forum Statistics

• Total Topics
631352
• Total Posts
2999484
×

## Important Information

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!