Normal mapping in Shader

Started by
1 comment, last by Toastmastern 7 years, 6 months ago
I've been struggeling with this part for a good week now. I think I'm really close but missing something vital.

What I have is a planet, for this planet I have a height map and normal map. What I'm trying to achieve is to map the normal around the sphere that consist of my planet. I've read up on TBN matrix and how to calculate it. Everything I'm explaining in this post takes place in the domain shader.

I know that the normal is correct, since I get some shadow from the light on it but it looks weird since it 2D in 3D world if you know what I mean.

I will split up the shader code first and then post it all in the end.

First I take 3 points on my height map:
	heightMapCoord1 = uvwCoord.x * patch[0].heightMapCoord + uvwCoord.y * patch[1].heightMapCoord + uvwCoord.z * patch[2].heightMapCoord;
	heightMapCoord2.x = ((heightMapCoord1.x * 46080.0f) + 1.0f) / 46080.0f;
	heightMapCoord2.y = heightMapCoord1.y;
	heightMapCoord3.x = heightMapCoord1.x;
	heightMapCoord3.y = ((heightMapCoord1.y * 22528.0f) + 1.0f) / 22528.0f;
I then sample the height map to get 3 different Z values for my future vectors:
	heightMapSample1 = heightMapTexture.SampleLevel(sampleType, heightMapCoord1, 0);
	heightMapSample2 = heightMapTexture.SampleLevel(sampleType, heightMapCoord2, 0);
	heightMapSample3 = heightMapTexture.SampleLevel(sampleType, heightMapCoord3, 0);
The next step is to create the 3 different vectors
	normalMappingVector1 = float3(heightMapCoord1.x, heightMapCoord1.y, heightMapSample1);
	normalMappingVector2 = float3(heightMapCoord2.x, heightMapCoord2.y, heightMapSample2);
	normalMappingVector3 = float3(heightMapCoord3.x, heightMapCoord3.y, heightMapSample2);
I then create cross the vectors between vector 2-1 and 3-1
        normalMappingNormal = normalize(cross((normalMappingVector2 - normalMappingVector1), (normalMappingVector3 - normalMappingVector1)));
These code that follows is to calculate the tangent
	coef = 1 / ((heightMapCoord2.x * heightMapCoord3.y) - (heightMapCoord3.x * heightMapCoord2.y));

	normalMappingTangent.x = coef * ((normalMappingVector2.x * heightMapCoord3.y) + (normalMappingVector3.x * -heightMapCoord2.y));
	normalMappingTangent.y = coef * ((normalMappingVector2.y * heightMapCoord3.y) + (normalMappingVector3.y * -heightMapCoord2.y));
	normalMappingTangent.z = coef * ((normalMappingVector2.z * heightMapCoord3.y) + (normalMappingVector3.z * -heightMapCoord2.y));
Normalize it and calculate the Bi-tangent
	normalMappingTangent = normalize(normalMappingTangent);

	normalMappingBiTangent = normalize(cross(normalMappingNormal, normalMappingTangent));
I then use all the 3 vectors I have gotten and combining them to a Matrix and then mul my Normal(this normal is taken from the normal map and not calculated)
	float3x3 TBN = float3x3(normalMappingTangent, normalMappingBiTangent, normalMappingNormal);

	normal = mul(TBN, normal); 
Here is the full shader code
Texture2D<float> heightMapTexture;
Texture2D colorMapTexture;
Texture2D normalMapTexture;
SamplerState sampleType;
SamplerState colorSampleType;
SamplerState normalSampleType;

cbuffer MatrixBuffer
{
	matrix worldMatrix;
	matrix viewMatrix;
	matrix projectionMatrix;
};

cbuffer LightBuffer
{
	float4 diffuseColor;
	float3 lightDirection;
	float padding;
};

struct ConstantOutputType
{
	float edges[3] : SV_TessFactor;
	float inside : SV_InsideTessFactor;
};

struct HullOutputType
{
	float3 position : POSITION;
	float4 color : COLOR;
	float3 sphereNormal : NORMAL;
	float2 heightMapCoord : TEXCOORD0;
	float2 colorMapCoord : TEXCOORD1;
};

struct PixelInputType
{
	float4 position : SV_POSITION;
	float4 color : COLOR;
};

[domain("tri")]

PixelInputType PlanetDomainShader(ConstantOutputType input, float3 uvwCoord : SV_DomainLocation, const OutputPatch<HullOutputType, 3> patch)
{
	float3 vertexPosition;
	float3 sphereNormal;
	float2 heightMapCoord1;
	float2 heightMapCoord2;
	float2 heightMapCoord3;
	float2 colorMapCoord;
	PixelInputType output;
	float heightMapSample1;
	float heightMapSample2;
	float heightMapSample3;
	float4 colorMapSample;
	float3 normalMapSample;
	float3 sunLightDir;
	float sunLightIntensity;
	float4 sunLightColor;
	float3 normalMappingVector1;
	float3 normalMappingVector2;
	float3 normalMappingVector3;
	float3 normalMappingNormal;
	float coef;
	float3 normalMappingTangent;
	float3 normalMappingBiTangent;
	float3 normal;

	sunLightDir = -lightDirection;

	heightMapCoord1 = uvwCoord.x * patch[0].heightMapCoord + uvwCoord.y * patch[1].heightMapCoord + uvwCoord.z * patch[2].heightMapCoord;
	heightMapCoord2.x = ((heightMapCoord1.x * 46080.0f) + 1.0f) / 46080.0f;
	heightMapCoord2.y = heightMapCoord1.y;
	heightMapCoord3.x = heightMapCoord1.x;
	heightMapCoord3.y = ((heightMapCoord1.y * 22528.0f) + 1.0f) / 22528.0f;
	colorMapCoord = uvwCoord.x * patch[0].colorMapCoord + uvwCoord.y * patch[1].colorMapCoord + uvwCoord.z * patch[2].colorMapCoord;
	vertexPosition = uvwCoord.x * patch[0].position + uvwCoord.y * patch[1].position + uvwCoord.z * patch[2].position;
	sphereNormal = uvwCoord.x * patch[0].sphereNormal + uvwCoord.y * patch[1].sphereNormal + uvwCoord.z * patch[2].sphereNormal;

	heightMapSample1 = heightMapTexture.SampleLevel(sampleType, heightMapCoord1, 0);
	heightMapSample2 = heightMapTexture.SampleLevel(sampleType, heightMapCoord2, 0);
	heightMapSample3 = heightMapTexture.SampleLevel(sampleType, heightMapCoord3, 0);
	colorMapSample = colorMapTexture.SampleLevel(colorSampleType, colorMapCoord, 0);
	normalMapSample = normalMapTexture.SampleLevel(normalSampleType, heightMapCoord1, 0).rgb;

	normal.x = (normalMapSample.r * 2) - 1;
	normal.y = (normalMapSample.g * 2) - 1;
	normal.b = (normalMapSample.b * 2) - 1;

	normalMappingVector1 = float3(heightMapCoord1.x, heightMapCoord1.y, heightMapSample1);
	normalMappingVector2 = float3(heightMapCoord2.x, heightMapCoord2.y, heightMapSample2);
	normalMappingVector3 = float3(heightMapCoord3.x, heightMapCoord3.y, heightMapSample2);

	normalMappingNormal = normalize(cross((normalMappingVector2 - normalMappingVector1), (normalMappingVector3 - normalMappingVector1)));

	coef = 1 / ((heightMapCoord2.x * heightMapCoord3.y) - (heightMapCoord3.x * heightMapCoord2.y));

	normalMappingTangent.x = coef * ((normalMappingVector2.x * heightMapCoord3.y) + (normalMappingVector3.x * -heightMapCoord2.y));
	normalMappingTangent.y = coef * ((normalMappingVector2.y * heightMapCoord3.y) + (normalMappingVector3.y * -heightMapCoord2.y));
	normalMappingTangent.z = coef * ((normalMappingVector2.z * heightMapCoord3.y) + (normalMappingVector3.z * -heightMapCoord2.y));

	normalMappingTangent = normalize(normalMappingTangent);

	normalMappingBiTangent = normalize(cross(normalMappingNormal, normalMappingTangent));

	vertexPosition.x = vertexPosition.x + (sphereNormal.x * ((heightMapSample1 * 29429.0f) - 8200.0f));
	vertexPosition.y = vertexPosition.y + (sphereNormal.y * ((heightMapSample1 * 29429.0f) - 8200.0f));
	vertexPosition.z = vertexPosition.z + (sphereNormal.z * ((heightMapSample1 * 29429.0f) - 8200.0f));

	output.position = mul(float4(vertexPosition, 1.0f), worldMatrix);
	output.position = mul(output.position, viewMatrix);
	output.position = mul(output.position, projectionMatrix);

	float3x3 TBN = float3x3(normalMappingTangent, normalMappingBiTangent, normalMappingNormal);

	TBN = transpose(TBN);

	normal = mul(TBN, normalMappingNormal); 

	sunLightIntensity = saturate(dot(normal, sunLightDir));

	sunLightColor = saturate(diffuseColor * sunLightIntensity);

	output.color = sunLightColor * colorMapSample;

	return output;
}
I hope anyone can recognize what I'm trying to do here. I've read on many sites that precalculating the normal, tangent and bitangent is easier on the GPU but I'm
deadset on getting this to work.

Worth noting is that I have taken this code from http://www.fabiensanglard.net/bumpMapping/index.php, and then trying to combine it with other things I've read:

generateNormalAndTangent(float3 v1, float3 v2, text2 st1, text2 st2)
	{
		float3 normal = v1.crossProduct(v2);
		
		float coef = 1/ (st1.u * st2.v - st2.u * st1.v);
		float3 tangent;

		tangent.x = coef * ((v1.x * st2.v)  + (v2.x * -st1.v));
		tangent.y = coef * ((v1.y * st2.v)  + (v2.y * -st1.v));
		tangent.z = coef * ((v1.z * st2.v)  + (v2.z * -st1.v));
		
		float3 binormal = normal.crossProduct(tangent);
	}
Thanks in advance
Toastmastern
Advertisement
Found some errors in my code while sitting at work:


	normal.x = (normalMapSample.r * 2) - 1;
	normal.y = (normalMapSample.g * 2) - 1;
	normal.b = (normalMapSample.b * 2) - 1;
I need to normalize this vector.


        normal = mul(TBN, normalMappingNormal); 
Here I need to use normal instead of normalMappingNormal.

I remember trying a lot of different things last night, I hope these things weren't part of those changes :P

//Toastmastern
Almost got it working now. Only issue left is that the sun seems to be the camera, so that when I rotate and move around the sun moves a
long with me.

I've narrowed it down to being that the normal moves with the camera for some reason even tho I don't do any translation on it. Here is the updated
code in the domain shader:


	float3 vertexPosition;
	float3 sphereNormal;
	float2 heightMapCoord;
	float2 colorMapCoord;
	PixelInputType output;
	float heightMapSample;
	float4 colorMapSample;
	float3 normalMapSample;
	float3 normal;

	heightMapCoord = uvwCoord.x * patch[0].heightMapCoord + uvwCoord.y * patch[1].heightMapCoord + uvwCoord.z * patch[2].heightMapCoord;
	colorMapCoord = uvwCoord.x * patch[0].colorMapCoord + uvwCoord.y * patch[1].colorMapCoord + uvwCoord.z * patch[2].colorMapCoord;
	vertexPosition = uvwCoord.x * patch[0].position + uvwCoord.y * patch[1].position + uvwCoord.z * patch[2].position;
	sphereNormal = uvwCoord.x * patch[0].sphereNormal + uvwCoord.y * patch[1].sphereNormal + uvwCoord.z * patch[2].sphereNormal;

	heightMapSample = heightMapTexture.SampleLevel(sampleType, heightMapCoord, 0);
	colorMapSample = colorMapTexture.SampleLevel(colorSampleType, colorMapCoord, 0);
	normalMapSample = normalMapTexture.SampleLevel(normalSampleType, heightMapCoord, 0).rgb;

	normal = normalize((normalMapSample * 2) - 1);

	vertexPosition.x = vertexPosition.x + (sphereNormal.x * ((heightMapSample * 29429.0f) - 8200.0f));
	vertexPosition.y = vertexPosition.y + (sphereNormal.y * ((heightMapSample * 29429.0f) - 8200.0f));
	vertexPosition.z = vertexPosition.z + (sphereNormal.z * ((heightMapSample * 29429.0f) - 8200.0f));

	output.position = mul(float4(vertexPosition, 1.0f), worldMatrix);
	output.position = mul(output.position, viewMatrix);
	output.position = mul(output.position, projectionMatrix);

	//output.normal = mul(float4(normal, 0.0f), viewMatrix);
	output.normal = float4(normal, 0.0f);

	output.texCoord = heightMapCoord;

	output.color =  colorMapSample;

	return output;
Notice that the normal is just sampled from the texture and then sent to the pixel shader as a direction and not a position. I believe that's the way you do it tho
with a 0.0f instead of a 1.0f as the last parameter.

Here comes the pixel shader code, what I've tried here is to just set the color to the normal value and I can see that it is changing with
the camera movement. Which leads me to believe something is funcy about the normal.

    float3 sunLightDir;
    float sunLightIntensity;
    float4 sunLightColor;
    float4 color = input.color;
    float3 normal = input.normal;

    float3 dp1 = ddx_fine(input.position);
    float3 dp2 = ddy_fine(input.position);
    float2 duv1 = ddx_fine(input.texCoord);
    float2 duv2 = ddy_fine(input.texCoord);

    float3 dp2perp = cross(dp2, normal);
    float3 dp1perp = cross(normal, dp1);
    float3 T = dp2perp * duv1.x + dp1perp * duv2.x;
    float3 B = dp2perp * duv1.y + dp1perp * duv2.y;

    float invmax = rsqrt(max(dot(T,T), dot(B,B)));
    float3x3 TBN = float3x3(T * invmax, B * invmax, normal);

    normal = mul(normal, TBN);

    sunLightDir = -lightDirection;
    sunLightIntensity = saturate(dot(normal, sunLightDir));

    sunLightColor = saturate(diffuseColor * sunLightIntensity);

    color = sunLightColor * color;

    return color;

This topic is closed to new replies.

Advertisement