Sign in to follow this  
_goat

Help with normal mapping (in tangent space)

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


//=====================================================================
//
//  Level 1 Shading
//
//=====================================================================

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

//=====================================================================
// Vertex Shader
//=====================================================================
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;
}

//=====================================================================
// Pixel Shader
//=====================================================================
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
    {
        // shaders
        VertexShader = compile vs_3_0 VS1();
        PixelShader  = compile ps_3_0 PS1();
    }  
}

Share this post


Link to post
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-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
float4 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 this post


Link to post
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.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this