在csdn博客上看到这篇文章DirectX11--HLSL中矩阵的内存布局和mul函数探讨
通过对这篇文章的学习,大致了解了矩阵内存布局和mul函数的使用情况,为了让自己以后能快速理解这篇博文的知识点,所以特别写了这篇博文来总结自己对博文的一些理解:
- 数据如何从C++的DirectXMath中传入HLSL的matrix对象中:
假设我们在C++的DirectXMath中有矩阵数据如下(XMFLOAT4X4/XMMATRIX
),矩阵数据在内存中是依次排列的:
m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44.
上述数据流传递到HLSL后,若是传递给cb的寄存器的前4个向量,那么它内存布局一定如下:
cb[0].xyzw = (m11, m12, m13, m14);
cb[1].xyzw = (m21, m22, m23, m24);
cb[2].xyzw = (m31, m32, m33, m34);
cb[3].xyzw = (m41, m42, m43, m44);
数据传入到寄存器以后,HLSL中的矩阵变量如何解析寄存器的数据,和矩阵变量前面的声明标识有关系。
- 如果是行主序(row_major)矩阵,则把
cb[0]寄存器保存的数据解析为矩阵的第1行,
cb[1]寄存器保存的数据解析为矩阵的第2行,
cb[2]寄存器保存的数据解析为矩阵的第3行,
cb[3]寄存器保存的数据解析为矩阵的第4行.
例如HLSL中矩阵定义如下:
cbuffer cb : register(b0)
{
(row_major) matrix g_World;//定义为行主序矩阵
}
则:
g_World[0]表示矩阵的第1行,读取到的数据为cb[0]寄存器保存的数据。
g_World[1]表示矩阵的第2行,读取到的数据为cb[1]寄存器保存的数据。
g_World[2]表示矩阵的第3行,读取到的数据为cb[2]寄存器保存的数据。
g_World[3]表示矩阵的第4行,读取到的数据为cb[3]寄存器保存的数据。
- 如果是列主序(column_major)矩阵,则把
cb[0]寄存器保存的数据解析为矩阵的第1列,
cb[1]寄存器保存的数据解析为矩阵的第2列,
cb[2]寄存器保存的数据解析为矩阵的第3列,
cb[3]寄存器保存的数据解析为矩阵的第4列.
例如HLSL中矩阵定义如下:
cbuffer cb : register(b0)
{
matrix g_World;//定义为列主序矩阵
}
则:
g_World矩阵的第1列为cb[0]寄存器保存的数据。
g_World矩阵的第2列为cb[1]寄存器保存的数据。
g_World矩阵的第3列为cb[2]寄存器保存的数据。
g_World矩阵的第4列为cb[3]寄存器保存的数据。
g_World[0]表示矩阵的第1行,读取到的数据为cb[0],cb[1],cb[2],cb[3]这4个寄存器中的第1个值.
g_World[1]表示矩阵的第2行,读取到的数据为cb[0],cb[1],cb[2],cb[3]这4个寄存器中的第2个值。
g_World[2]表示矩阵的第3行,读取到的数据为cb[0],cb[1],cb[2],cb[3]这4个寄存器中的第3个值。
g_World[3]表示矩阵的第4行,读取到的数据为cb[0],cb[1],cb[2],cb[3]这4个寄存器中的第4个值。
row_major和column_major标识只对数据从C++传入HLSL以后如何解析数据有影响,后续在HLSL中使用矩阵数据不受这个标识的影响。
- HLSL中mul是如何执行运算的
HLSL中mul函数执行运算和矩阵前面的标识符无关,它都是用第一个参数矩阵的行和第二个参数矩阵的列相乘来得到最终矩阵里面的某个值。
例如HLSL代码如下:
cbuffer cb : register(b0)
{
row_major matrix gView;
row_major matrix gProj;
}
// 顶点着色器
float4 VS(float3 PosL : POSITION): SV_POSITION
{
row_major matrix viewProj = mul(gView, gProj);
return mul(float4(pOut.PosW, 1.0f), viewProj);
}
viewProj._11=g_View矩阵的第一行和g_Proj矩阵的第一列点乘的值。
viewProj._mn=g_View矩阵的第m行和g_Proj矩阵的第n列点乘的值。
最终总结HLSL矩阵使用方法如下(参考:DirectX11--HLSL中矩阵的内存布局和mul函数探讨_X_Jun96的博客-CSDN博客):
经过组合,就一共有四种能够正常绘制的情况:
- C++代码端不进行转置,HLSL中使用row_major matrix(行主序矩阵),mul函数让向量放在左边(行向量),这样实际运算就是(行向量 X 行主序矩阵) 。这种方法易于理解,但是这样做dp4运算取矩阵的列很不方便,在HLSL中会产生用于转置矩阵的大量指令,性能上有损失。
- C++代码端进行转置,HLSL中使用matrix(列主序矩阵) ,mul函数让向量放在左边(行向量),这样就是(行向量 X 列主序矩阵),但C++这边需要进行一次矩阵转置,HLSL内部不产生转置 。这是官方例程所使用的方式,这样可以使得dp4运算可以直接取列主序矩阵的行,从而避免内部产生大量的转置指令。后续我会将教程的项目也使用这种方式。
- C++代码端不进行转置,HLSL中使用matrix(列主序矩阵),mul函数让向量放在右边(列向量),实际运算是(列主序矩阵 X 列向量)。这种方法的确可行,取列矩阵的行也比较方便,效率上又和2等同,就是HLSL那边的矩阵乘法都要反过来写,然而DX本身就是崇尚行主矩阵的,把OpenGL的习惯带来这边有点。。。
- C++代码端进行转置,HLSL中使用row_major matrix(行主序矩阵),mul函数让向量放在右边(列向量),实际运算是(行主序矩阵 X 列向量)。 就算这种方法也可以绘制出来,但还是很让人难受,比第2点还难受,我甚至不想去说它。
可参考博文:矩阵:行主序、列主序、行向量、列向量