Jump to content
  • Advertisement
Sign in to follow this  
pseudomarvin

Normal mapping erroneous results for diffuse lighting

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

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have been trying to implement normal mapping with a degree of success but it does not seem to work entirely as it should. I tried verifying that it works correctly by binding a normal map filled with values (0, 0, 1) and comparing the results of this draw call with one where I did not bind a normal map and instead just used the normals of the mesh for lighting calculations (simplified here as just diffuse lighting calculation). As I understand it, this is an 'identity normal map' since value (0, 0, 1) in the texture represents an unperturbed normal. But the results are not identical.

 

I've made some comparison pics. The direction of the camera matches the light direction in the first two, and it is exactly opposite on the second two: 

 

1a.) http://s15.postimg.org/57defxxjv/in_Light_without_Normal_Map.png

1b.) http://s14.postimg.org/bpen0tlkh/in_Light_with_Normal_Map.png

2a.) http://s21.postimg.org/3rd6mf6if/shade_without_Normal_Map.png

2b.) http://s11.postimg.org/k8u5vj9sz/shade_with_Normal_Map.png

 

It seems like the normals are inverted 'inside out' in the picture 1b.

I don't know why it looks like light hitting the tree in picture 2b.

 

Since the code calculating the tangents is just a copy of Lengyel I think it's not the source of the problem. I suspect the vertex shader(particularly the calculation of the tangent space light direction) is responsible. I make sure that all of the vectors(view, light, normal, tangent, bitangent) are in the same space (world/model space) and then transform the view and light vectors into the tangent space, constructed from the normal, tangent and bitangent vectors.

 

Then in the pixel shader depending on whether the normal map is bound or not I use the original mesh's normal and world space light direction for diffuse lighting or I sample the normal map and use the tangent space light direction. I would expect the results to be the same.

 

Am I making an unsound assumption at any point here? Thank you.

 

Code (all of it is based on Eric Lengyel's work at: http://www.terathon.com/code/tangent.html)

 

Calculating tangents (almost a direct copy of Lengyel)

// Receives 3 vertices of a triangle and their corresponding normal
Vector4 CalculateTangent(Vertex vertex1, Vertex vertex2, Vertex vertex3, Vector3 normal)
{
	Vector3 v1 = vertex1.position;
	Vector3 v2 = vertex2.position;
	Vector3 v3 = vertex3.position;
	Vector2 uv1 = vertex1.texCoord;
	Vector2 uv2 = vertex2.texCoord;
	Vector2 uv3 = vertex3.texCoord;

	// Edge v2 - v1
	float x1 = v2.x - v1.x;		
	float y1 = v2.y - v1.y;
	float z1 = v2.z - v1.z;
	float s1 = uv2.x - uv1.x;
	float t1 = uv2.y - uv1.y;

	// Edge v3 - v1
	float x2 = v3.x - v1.x;		
	float y2 = v3.y - v1.y;
	float z2 = v3.z - v1.z;
	float s2 = uv3.x - uv1.x;	
	float t2 = uv3.y - uv1.y;

	float r = 1.0f / (s1 * t2 - s2 * t1);

	Vector3 t = Vector3(t2 * x1 - t1 * x2,
		t2 * y1 - t1 * y2,
		t2 * z1 - t1 * z2) * r;

	Vector3 b = Vector3(s1 * x2 - s2 * x1,
		s1 * y2 - s2 * y1,
		s1 * z2 - s2 * z1) * r;

	// Gram-Schmidt orthogonalize.
	Vector3 tangent = Math::Normalize((t - normal * Math::Dot(normal, t)));
	// Calculate handedness.
	float handedness = (Math::Dot(Math::CrossProduct(normal, t), b) < 0.0f) ? -1.0f : 1.0f;

	return Vector4(tangent, handedness);
}

Vertex Shader (relevant parts)

cbuffer LightBuffer: register(b3)
{
	float4 lightDir;		// in model(world) space
	float4 cameraPosition;		// in model(world) space
};

struct VertexShaderInput
{
	float3 pos : POSITION;
	float3 normal : NORMAL;
	float4 tangent : TANGENT;
	float2 texCoord: TEXCOORD;
	float4 params: PARAMETERS;
	float3 instance_pos : INSTANCE_POSITION;
};

VertexShaderOutput main(VertexShaderInput input)
{
	VertexShaderOutput output;

	float4 pos = float4(input.pos, 1.0f);
	
	pos = mul(model, pos);
	pos.xyz += input.instance_pos;
	output.worldPos = pos;
	output.viewDir = cameraPosition.xyz - output.worldPos.xyz;
	pos = mul(view, pos);
	pos = mul(projection, pos);
	output.pos = pos;

	float4 normal = float4(input.normal, 0.0f);	// model/world space
	normal = mul(model, normal);
	output.normal = normal.xyz;
	normal = normalize(normal);

	float4 tangent = float4(input.tangent.xyz, 0);
	tangent = mul(model, tangent);	// model/world space
	tangent = normalize(tangent);

	float3 bitangent = cross(input.normal, input.tangent.xyz) * input.tangent.w; // tangent.w == handedness
	bitangent = mul(model, float4(bitangent, 0)).xyz;	// model/world space
	bitangent = normalize(bitangent);

	float3 viewDir = output.viewDir;	// in model/world space

	output.tangentSpaceViewDir = float3(dot(normal.xyz, viewDir),
					    dot(tangent.xyz, viewDir),
					    dot(bitangent, viewDir));

	output.tangentSpaceLightDir = float3(dot(normal.xyz, lightDir.xyz),
					     dot(tangent.xyz, lightDir.xyz),
					     dot(bitangent, lightDir.xyz));
	output.worldSpaceLightDir = lightDir.xyz;
...
}

Pixel Shader

struct PixelShaderInput
{
	float4 pos : SV_POSITION;
	float2 texCoord : TEXCOORD;
	float4 worldPos: WORLDPOS;
	float4 shadowPos : POSITION1;
	float3 normal : NORMAL;
	float3 lightDir : LIGHT;
	float4 diffuseReflect : DIFFUSE;
	float4 ambientReflect : AMBIENT;
	float4 specularReflect: SPECULAR;
	float3 viewDir : VIEWDIR;
	float3 tangentSpaceViewDir : TANGENT_SPACE_VIEWDIR;
	float3 tangentSpaceLightDir : TANGENT_SPACE_LIGHTDIR;
	float transparency : TRANSP;
	bool useTexture : USETEX;
};

// Only the barest lighting calculation for simplicity's sake. View direction is not used.
float4 main(PixelShaderInput input) : SV_TARGET
{
	float3 normal;
	float3 surfaceToLight;
	float3 viewDir;

	// Brightness(either with normal map + tangent space light and view vectors)
        // or with mesh normals + world space light and view vectors
	if (input.useTexture)	// Normal map
	{
		surfaceToLight = -normalize(input.tangentSpaceLightDir);
		viewDir = input.tangentSpaceViewDir;
		normal = colorTexture.Sample(texSampler, input.texCoord).rgb;
		normal = normalize(2.0f * normal - 1.0f);
	}
	else
	{
		viewDir = normalize(input.viewDir);
		surfaceToLight = -normalize(input.lightDir);
		normal = normalize(input.normal);
	}

	float toLightDot = dot(normal, surfaceToLight);
	float brightness = max(0.1f, saturate(toLightDot));

	float4 result = input.ambientReflect + input.diffuseReflect * brightness;
	result.a = 1;
	return result;
}

Share this post


Link to post
Share on other sites
Advertisement

Just a quick thought while I'm passing by, but:

 


output.tangentSpaceViewDir = float3(dot(normal.xyz, viewDir),
dot(tangent.xyz, viewDir),
dot(bitangent, viewDir));

output.tangentSpaceLightDir = float3(dot(normal.xyz, lightDir.xyz),
dot(tangent.xyz, lightDir.xyz),
dot(bitangent, lightDir.xyz));

 

Are you sure this is the correct order? I'm wondering if maybe instead of moving from WS to tangent space, you're doing the opposite (WS to whatever space). Maybe you need to transpose this matrix?

Share this post


Link to post
Share on other sites

No it is not the correct order :). Thank you Styves. 

 

The correct order should look like this (it's not that it should be transposed, I just messed up the order of the vectors):

	output.tangentSpaceViewDir = float3(dot(tangent.xyz, viewDir),
					    dot(bitangent, viewDir),
				            dot(normal.xyz, viewDir));
	
	output.tangentSpaceLightDir = float3(dot(tangent.xyz, lightDir.xyz),
					     dot(bitangent, lightDir.xyz),
					     dot(normal.xyz, lightDir.xyz));

The lighting looks correct now.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!