Normal mapping light doesn't match world light

Started by
6 comments, last by DerMeister 17 years, 1 month ago
I'm rendering a cube using dot3 normal mapping in a hlsl shader. The normal mapping looks fine, except that the world lighting doesn't match up with the normal mapping lighting. For instance, if I rotate the light to the right, the normal map shows that the light is rotating upwards. I'm not sure why this is occurring, all my lighting is done on the single normal mapping pass. Perhaps the problem is in my vertex shader space matrix multiplications, or perhaps i'm generating the tangent space coordinates wrong. Any advice would be great. vertex shader

// normal mapping vertex shader
VSOutput VS_NormalMapping(VSInput a_Input)
{
	VSOutput output;

	// calculate homogenous position
	output.pos = mul(float4(a_Input.pos, 1.0f), g_matWVP);

	// copy texture coordinates across
	output.tex = a_Input.tex;

	// calculate matrix to tangent space
	float3x3 toTangentSpace = transpose(float3x3(a_Input.tan, a_Input.bin, a_Input.nor));

	// calculate light's direction relative to tangent space
	float3 lightDirL = mul(float4(g_lightDir, 0.0f), g_matWI);
	output.lightDir = mul(lightDirL, toTangentSpace);

	return output;
}

pixel shader

// normal mapping pixel shader
float4 PS_NormalMapping(VSOutput a_Input) : COLOR
{
	// index into textures
	float4 colour = tex2D(sampTexture, a_Input.tex);
	float3 normal = tex2D(sampNormalMap, a_Input.tex);


	// convert normal from (0 - 1) to (-1 - 1)
	normal = 2.0f * (normal - 0.5f);

	// renormalise light direction
	float3 light = normalize(a_Input.lightDir);

	// calculate diffuse component
	float diffuse = max(dot(normal, light), 0.0f);

	// return texture colour modified by diffuse component
	return (colour * diffuse);
}

TBN coordinates generation -

	// create box with texture coordinates
	V_RETURN(CreateBox(5.0f, 5.0f, 5.0f, &g_pMeshBox))

	// calculate tangential coordinates
	D3DVERTEXELEMENT9 VertexElems[] = 
	{
		{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},	// position
		{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0},
		{0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BINORMAL, 0},
		{0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
		{0, 48, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},	// texture coords
		D3DDECL_END()
	};
	LPD3DXMESH pMeshClonedBox = NULL;
	V_RETURN(g_pMeshBox->CloneMesh(D3DXMESH_MANAGED, VertexElems, pd3dDevice, &pMeshClonedBox))

	SAFE_RELEASE(g_pMeshBox);

	V_RETURN(D3DXComputeTangentFrameEx(pMeshClonedBox,	D3DDECLUSAGE_TEXCOORD, 0,
														D3DDECLUSAGE_BINORMAL, 0,
														D3DDECLUSAGE_TANGENT, 0,
														D3DDECLUSAGE_NORMAL, 0, 0, 0,
														0.01f, 0.25f, 0.01f, &g_pMeshBox, 0))

	SAFE_RELEASE(pMeshClonedBox);

Cheers.
Advertisement
Hi, in your vertex shader code you calculate the world space to tangent space matrix. So to move a vector from world space to tangent space you need to multiply it by that basis change matrix. BUT you do

float3 lightDirL = mul(float4(g_lightDir, 0.0f), g_matWI);
which applies the inverse rotation and scaling applied to your vertices.

before

output.lightDir = mul(lightDirL, toTangentSpace);
which does what I stated first.

Try commenting that line in your effect code.

I think that's the problem with your code, correct me if I am wrong.


Good luck
No matter where you go... &this
Thanks for the reply. I've tried your suggestion but the lighting directions still don't match. I was always under the assumption that local space and tangent space were different. And the order went - WORLD -> LOCAL -> TANGENT, which is why I transformed the light to local space before transforming it into tangent space.

Any other suggestions?

When I move the light - up, left, down, right - in world space, the light moves - left, up, right, down - on the normal map, respectively. Any other suggestions would be great. Cheers.
You have a directional light source, so you have its direction declared in world space, that is, a vector whose coordinates are relative to an origin, common to all objects in the scene.

Your normal map stores surface normals, which are vectors declared in tangent space. Tangent space defines an origin for each triangle, so, to be able to calculate a dot product between the light direction and the tangent space normal map, they have to be declared in the same space (using the same basis vectors). So you have two options:

- Move the tangent space normal into world space OR
- Move your light direction into tangent space.

The last one is easier and more convenient.

By the way, you don't need to move the light direction into local space.

Two questions arise:

- Are you sure your normal maps are in tangent space. There are software packages that export normal maps in world space, like Blender3D... and
- Are you transforming the tangent|binormal|normal vector properly, that is, if
a vertex is rotated or scaled (translation has no effect) you must multiply each basis vector by the world transform and normalize them.

Hope this helps
No matter where you go... &this
Thanks for the info. I'm still not certain why you don't have to perform the LOCAL (inverse-world) transform as aren't the tangent frame coordinates relative to the local space? Isn't that what you do to ensure you if you adjust the vertex scale or rotation, the TBN coordinates are adjusted as well?

I'm relatively certain that the normal maps are fine, I have four different ones from different sources and they all perform the same way.

I appreciate the help though, anything else you might know would be great.
Cheers.
I've been playing around with the vertex shader, and found that when I changed the inputs to the TBN matrix to be BTN (bi, tan, norm), I got the correct results - that is the world lighting matches the normal lighting. Thus, the TBN coordinates that I generate are mixing the T and B vectors. I'm still using the D3DXComputeTangentFrameEx i mentioned above and my inputs are still the same. I've searched the MSDN but can't seem to find any solutions. Any suggestions would be great.
Ok figured it out. I just had my D3DDECLUSAGE_TANGENT and D3DDECLUSAGE_BINORMAL around the wrong way in the D3DXComputeTangentFrameEx function. Thanks for the help. So the function became -

	// compute tangent space coordinates	V_RETURN(D3DXComputeTangentFrameEx(pMeshClonedBox,	D3DDECLUSAGE_TEXCOORD, 0,														D3DDECLUSAGE_TANGENT, 0,														D3DDECLUSAGE_BINORMAL, 0,														D3DDECLUSAGE_NORMAL, 0, 0, 0,														0.01f, 0.25f, 0.01f, &g_pMeshBox, 0))


Thanks again.
You are right, moving your light direction from world space to local space is the same as moving the tangent basis vectors into world space (check my 2nd suggestion).
Any of these operations are correct before moving to tangent space.

Anyway, it´s great you figured it out.



No matter where you go... &this

This topic is closed to new replies.

Advertisement