HLSL Normal Mapping Matrix Multiplication
Asked Answered
B

1

7

I'm currently working in directx9 and have the following code for my normal mapping:

(Vertex Shader):

float4x4 gWorldMatrix;
float4x4 gWorldViewProjectionMatrix;

float4 gWorldLightPosition;
float4 gWorldCameraPosition;

struct VS_INPUT 
{
   float4 mPosition : POSITION;
   float3 mNormal: NORMAL;
   float3 mTangent: TANGENT;
   float3 mBinormal: BINORMAL;
   float2 mUV: TEXCOORD0;
};

struct VS_OUTPUT 
{
   float4 mPosition : POSITION;
   float2 mUV: TEXCOORD0;
   float3 mLightDir: TEXCOORD1;
   float3 mViewDir: TEXCOORD2;
   float3 T: TEXCOORD3;
   float3 B: TEXCOORD4;
   float3 N: TEXCOORD5;
};

VS_OUTPUT vs_main( VS_INPUT Input )
{
   VS_OUTPUT Output;

   Output.mPosition = mul( Input.mPosition, gWorldViewProjectionMatrix );

   Output.mUV = Input.mUV;

   float4 worldPosition = mul( Input.mPosition, gWorldMatrix );

   float3 lightDir = worldPosition.xyz - gWorldLightPosition.xyz;
   Output.mLightDir = normalize( lightDir );

   float3 viewDir = normalize( worldPosition.xyz - gWorldCameraPosition.xyz );
   Output.mViewDir = viewDir;

   //object space=>world space
   float3 worldNormal = mul( Input.mNormal, (float3x3)gWorldMatrix );
   Output.N = normalize( worldNormal );

   float3 worldTangent = mul( Input.mTangent, (float3x3)gWorldMatrix );
   Output.T = normalize( worldTangent );

   float3 worldBinormal = mul( Input.mBinormal, (float3x3)gWorldMatrix );
   Output.B = normalize( worldBinormal);

   return Output;
}

(Pixel Shader)

struct PS_INPUT
{
   float2 mUV : TEXCOORD0;
   float3 mLightDir: TEXCOORD1;
   float3 mViewDir: TEXCOORD2;
   float3 T: TEXCOORD3;
   float3 B: TEXCOORD4;
   float3 N: TEXCOORD5;
};

sampler2D DiffuseSampler;
sampler2D SpecularSampler;
sampler2D NormalSampler;

float3 gLightColor;

float4 ps_main(PS_INPUT Input) : COLOR
{
   //read normal from tex
   float3 tangentNormal = tex2D( NormalSampler, Input.mUV ).xyz;
   tangentNormal = normalize( tangentNormal * 2 - 1 ); //convert 0~1 to -1~+1.

   //read from vertex shader
   float3x3 TBN = float3x3( normalize(Input.T), normalize(Input.B),
      normalize(Input.N) ); //transforms world=>tangent space

   TBN = transpose( TBN ); //transform tangent space=>world

   float3 worldNormal = mul( TBN, tangentNormal ); //note: mat * scalar
   //(since TBN is row matrix)

   float3 lightDir = normalize( Input.mLightDir ); 
   float3 diffuse = saturate( dot(worldNormal, -lightDir) );

   float4 albedo = tex2D( DiffuseSampler, Input.mUV );
   diffuse = gLightColor * albedo.rgb * diffuse;

   float3 specular = 0;
   if ( diffuse.x > 0 )
   {
      float3 reflection = reflect( lightDir, worldNormal );
      float3 viewDir = normalize( Input.mViewDir );

      specular = saturate( dot(reflection, -viewDir) );
      specular = pow( specular, 20.0f );

      //further adjustments to specular (since texture is 2D)
      float specularIntensity = tex2D( SpecularSampler, Input.mUV );
      specular *= specularIntensity * gLightColor;
   }

   float3 ambient = float3(0.1f, 0.1f, 0.1f) * albedo;

   return float4(ambient + diffuse + specular, 1);
}

The code works, but I don't quite understand why I need to do the

TBN = transpose( TBN ); in the pixel shader.

The TBN values I passed through the Vertex Shader are those in world space (hence why I multiplied gWorldMatrix), yet I'm told that

float3x3 TBN = float3x3( normalize(Input.T), normalize(Input.B), normalize(Input.N) );

transforms world=>tangent(surface) space.

Why is this?

Butadiene answered 15/5, 2013 at 2:12 Comment(0)
A
5

You need the line

TBN = transpose( TBN ); 

because you're multipling your tangent-space normal from the right to the matrix. Therefore it's considered as a column vector, while the base vectors are in the rows of the matrix. So the matrix must be transposed, that the base transformation can be applied. You can omit the transposition, if youre switch the multiplication to

float3 worldNormal = mul( tangentNormal, TBN );

Because your multiplied the T,N and B vector with the worldmatrix your TBN matrix transforms from tangent space to world space (TBN transforms into object-space, after that world transforms into worldspace). Other implementations multiply the TBN with the world inverse transpose matrix. With the resulting TBN you can transform the light vector from world into tangent space and compare it to the tangent normal. So I think the one who told you that TBN transforms world to tangent space uses this approach (It saves some performance, because the heavy matrix-operations are done in the vertexshader).

Aureus answered 15/5, 2013 at 9:21 Comment(4)
+1 So basically if I didn't do the transpose, and just did worldNormal = mul( TBN, tangentNormal ), TBN acts as a matrix that transforms world=>tangent since I'm using it as a row major matrix instead of a column major matrix, right? (hence the calculation wouldn't be returning the normal in world space, but some weird random value)Butadiene
Only the inverse would switch the transformation, the transposition is only for the right multiplication order. Without the transposition you would map your vector to a base, which needn't to be an orthonormal base (e.g. the (1,0,0) would be mapped to (T.X,B.X,N.X)), which would result in weird stretching or maybe nonsense results. But I'm not a mathematician, maybe it transform into something really great ;)Aureus
+1 Thanks. And following through: my vertex shader uses Output.mPosition = mul( Input.mPosition, gWorldViewProjectionMatrix ); because the value of gWorldProjectionMatrix passed in by ID3DXBaseEffect::SetMatrix() is a row-major matrix, right?Butadiene
Yes, as stated in the documentation (msdn.microsoft.com/de-de/library/windows/desktop/…) ID3DXBaseEffect::SetMatrix() expects row-major matrices and sets them in the effect.Aureus

© 2022 - 2024 — McMap. All rights reserved.