HLSL法线贴图矩阵乘法

7

我是一名IT从业者,目前正在使用DirectX9技术。以下是我的法线贴图代码:

(顶点着色器):

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

(像素着色器)

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

代码能够运行,但我不是非常明白为什么我需要在像素着色器中执行 TBN = transpose(TBN);
我通过顶点着色器传递的TBN值是世界空间中的值(因此我要乘以gWorldMatrix),然而我被告知: float3x3 TBN = float3x3(normalize(Input.T), normalize(Input.B), normalize(Input.N)); 将世界坐标系转换为切线坐标系。
这是为什么呢?
1个回答

5
你需要这行代码。
TBN = transpose( TBN ); 

因为你将切线空间法线从右侧乘以矩阵。因此可以被视为列向量,而基向量在矩阵的行中。因此,必须转置矩阵以应用基本变换。如果您将乘法切换到左侧,则可以省略转置。

float3 worldNormal = mul( tangentNormal, TBN );

由于您将T、N和B向量与世界矩阵相乘,因此您的TBN矩阵从切线空间变换到世界空间(TBN转换为对象空间后,再由世界变换到世界空间)。其他实现会将TBN与世界逆转置矩阵相乘。通过生成的TBN,您可以将光矢量从世界空间变换到切线空间并将其与切线法线进行比较。所以我认为告诉您TBN将世界转换为切线空间的人使用了这种方法(它可以节省一些性能,因为重要的矩阵操作是在顶点着色器中完成的)。

所以基本上如果我没有进行转置,而只是执行 worldNormal = mul( TBN, tangentNormal ),由于我将其用作行主要矩阵而不是列主要矩阵,TBN 作为一个矩阵会将世界空间转换为切线空间,对吗?(因此计算不会返回世界空间中的法线,而是一些奇怪的随机值) - dk123
1
只有求逆才能切换变换,转置仅适用于正确的乘法顺序。如果没有转置,您将把向量映射到一个基础,它不一定是正交基础(例如,(1,0,0) 将被映射到(T.X,B.X,N.X)),这将导致奇怪的拉伸或可能是无意义的结果。但我不是数学家,也许它会变成真正伟大的东西 ;) - Gnietschow
+1 感谢。接下来,我的顶点着色器使用 Output.mPosition = mul( Input.mPosition, gWorldViewProjectionMatrix ); 是因为通过 ID3DXBaseEffect::SetMatrix() 传递的 gWorldProjectionMatrix 的值是行主序矩阵,对吗? - dk123
是的,正如文档(http://msdn.microsoft.com/de-de/library/windows/desktop/bb205720(v=vs.85).aspx)中所述,`ID3DXBaseEffect::SetMatrix()`函数期望行主序矩阵,并将其设置到效果中。 - Gnietschow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接