图形API中的矩阵表示与运算
图形API范畴
左乘与右乘
用列向量
用行向量
矩阵
行优先与列优先
同样将一维元素数组表示为矩阵,列优先矩阵指矩阵中的元素是按列解释的,行优先矩阵指矩阵中的元素是按行解释的。两种矩阵可以通过转置相互转换。(也翻译为行主序和列主序)
例如数组
解释为行优先矩阵
不同图形API下的情况
前面讲了左乘与右乘、行优先与列优先,这两者在数学概念上是没有关系的。可以用列向量右乘一个行优先矩阵或者列优先矩阵,没什么限制,但是翻译为汇编代码后可以看出在实现上有区别。
DirectX
DirectXMath中的的XMFLOAT4
和XMVECTOR
均是行向量。
向量与矩阵相乘需要使用函数:
1 |
|
在DirectX中,矩阵乘法的顺序是从左到右,变换生效的先后顺序也是从左到右。
DirectXMath中的XMFLOAT4X4
和XMMATRIX
均是行优先矩阵,它的数据流如下:
传递到HLSL后,若是传递给cb0的寄存器的前4个向量,那么它内存布局一定如下:
1 |
|
而在HLSL中,默认的matrix
或float4x4
采用的是列优先矩阵。
假设在HLSL的cbuffer
为:
1 |
|
如果g_World
是matrix
或float4x4
类型,由于是列优先矩阵,上面的4个寄存器存储的数据会被看作:
g_World
是row_major matrix
或row_major float4x4
类型,则为行优先矩阵,上面的4个寄存器存储的数据则依然被视作:
HLSL中的mul
函数,mul(x,y)
:
要求矩阵x的列数与矩阵y的行数相等。
如果x是一个向量,那么它将被解释为行向量。
如果y是一个向量,那么它将被解释为列向量。
它使用dp4
指令优化运算。对于dp4
来说,最好是能够对一个行向量和列优先矩阵(取列优先矩阵的列,也就是取一行寄存器向量与行向量做点乘)操作,又或者是对一个行优先矩阵(取行优先矩阵的行与列向量做点乘)和列矩阵操作,这样能避免转置。
4种正常传递与运算矩阵的情况:
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点还难受,我甚至不想去说它。引用自[1]
值得一提的是,按照矩阵预算律,对于矩阵mul
函数会自动对向量进行转置,所以可以通过调换矩阵和向量的顺序避免手动转置矩阵,
Unity Shader
Unity
Shader用名为ShaderLab的声明性语言编写,实现了跨平台。其中的CGPROGRAM
代码片段是用常规 HLSL/Cg着色语言编写
在Unity
Shader中,通常在变换顶点时,使用列向量右乘矩阵进行乘法,因为Unity提供的内置矩阵(如UNITY_MATRIX_MVP等)都是按列存储的。但有时也会使用左乘的方式,因为可以省去对矩阵转置的操作(
Unity在脚本中提供了一种矩阵类型——Matrix4x4。脚本中的这个矩阵类型则是采用列优先的方式。Unity Shader中Cg的矩阵采用行优先。
其它图形API
矩阵在OpenGL和GLSL中都是列优先的,不像DirectX和HLSL前者行优先、后者列优先。
参考资料
[1] DirectX11--HLSL中矩阵的内存布局和mul函数探讨 - X_Jun - 博客园 (cnblogs.com)
[2]《Unity Shader 入门精要》