DirectXMath 中的矩阵

625 阅读5分钟

在我的另一篇文章《DirectXMath 中的向量》中,我讨论了一些 DirectXMath 中向量相关的一些内容。这篇文章将会承接上一篇文章,讨论 DirectXMath 中矩阵相关的一些内容,包括矩阵类型、编译优化以及常用的函数运算等(其中一些在《DirectXMath 中的向量》中已经讨论过的一些编译方面的问题,这里不会再重复叙述,有需要的可以参阅上一篇文章)。

矩阵类型

在 DirectXMath 中,我们使用 XMMATRIX 类来表示一个 4 × 4 矩阵(图形学中操作的矩阵一般都是 4 × 4 矩阵),其定义如下:

#if (defined(_M_IX86)|| defined(_M_X64) || defined(_M_ARM)) && defined(_XM_NO_INTRINSICS_)
struct XMMATRIX
#else
__declspec(align(16)) struct XMMATRIX
#endif
{
    // Use 4 XMVECTORs to represent the matrix for SIMD.
    XMVECTOR r[4];
    
    XMMATRIX() {}
    
    // Initialize matrix by specifying 4 row vectors.
    XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, CXMVECTOR R3) {
        r[0] = R0; r[1] = R1; r[2] = R2; r[3] = R3; 
    }
    
    // Initialize matrix by specifying 4 row vectors.
    XMMATRIX(
        float m00, float m01, float m02, float m03,
        float m10, float m11, float m12, float m13,
        float m20, float m21, float m22, float m23,
        float m30, float m31, float m32, float m33
    );
    
    // Pass array of sixteen floats to construct matrix.
    explicit XMMATRIX(_In_reads(16) const float *pArray);
    
    XMMATRIX& operator= (const XMMATRIX &M) {
        r[0] = M.r[0]; r[1] = M.r[1]; 
        r[2] = M.r[2]; r[3] = M.r[3];
        return *this;
    }
    
    XMMATRIX operator+ () const { return *this; }
    XMMATRIX operator- () const;
    
    XMMATRIX& XM_CALLCONV operator+= (FXMMATRIX M);
    XMMATRIX& XM_CALLCONV operator-= (FXMMATRIX M);
    XMMATRIX& XM_CALLCONV operator*= (FXMMATRIX M);
    XMMATRIX& operator*= (float S);
    XMMATRIX& operator/= (float S);
    
    XMMATRIX XM_CALLCONV operator+ (FXMMATRIX M) const;
    XMMATRIX XM_CALLCONV operator- (FXMMATRIX M) const;
    XMMATRIX XM_CALLCONV operator* (FXMMATRIX M) const;
    XMMATRIX operator* (float S) const;
    XMMATRIX operator/ (float S) const;
    
    friend XMMATRIX XM_CALLCONV operator* (float S, FXMMATRIX M);
};

XMMATRIX XM_CALLCONV XMMatrixSet(
    float m00, float m01, float m02, float m03,
    float m10, float m11, float m12, float m13,
    float m20, float m21, float m22, float m23,
    float m30, float m31, float m32, float m33
);

可以看到 XMMATRIX 类型包含四个 XMVECTOR 实例,从而以达到利用 SIMD 的目的。XMMATRIX 重载了一系列的运算符以执行相应的矩阵运算。另外,我们也可以通过调用 XMMatrixSet 函数来创建 XMMATRIX 实例。

XMVECTOR 类型类似,当 XMMATRIX 实例作为全局变量和局部变量,编译器会自动实现内存对齐,而当 XMMATRIX 实例作为类的数据成员时,则可能会造成内存对齐不正确的问题。因此,一般建议使用 XMFLOAT4X4 类型来在类的数据成员中存储矩阵实例,其定义如下:

struct XMFLOAT4X4 {
    union {
        struct {
            float _11, _12, _13, _14;
            float _21, _22, _23, _24;
            float _31, _32, _33, _34;
            float _41, _42, _43, _44;
        };
        float m[4][4];
    };
    
    XMFLOAT4X4 () {}
    XMFLOAT4X4(
        float m00, float m01, float m02, float m03,
        float m10, float m11, float m12, float m13,
        float m20, float m21, float m22, float m23,
        float m30, float m31, float m32, float m33
    );
    explicit XMFLOAT4X4(_In_reads(16) const float *pArray);
    
    float operator() (size_t Row, size_t Column) const {
        return m[Row][Column];
    }
    float& operator() (size_t Row, size_t Column) {
        return m[Row][Column];
    }
    
    XMFLOAT4X4& operator= (const XMFLOAT4X4 &Float4x4);
};

类似的,DirectXMath 也提供了相应的 Loading 和 Storage 方法来进行 XMMATRIXXMFLOAT4X4 类型的相互转换:

// Loading methods: load data from XMFLOAT4X4 into XMMATRIX
inline XMMATRIX XM_CALLCONV XMLoadFloat4x4(const XMFLOAT4X4 *pSource);

// // Stroing methods: store XMMATRIX into XMFLOAT4X4
inline void XM_CALLCONV XMStoreFloat4x4(XMFLOAT4X4 *pDestination, FXMMATRIX M);

函数参数传递及调用约定

XMVECTOR 类型类似,为了提高效率,在函数调用中 XMMATRIX 类型的值作为参数被传递到函数中时,可以直接传递到 SSE/SSE2 寄存器中,而不用存到栈内存中。不同的是,XMMATRIX 类型的参数在计数上是算作 4 个 XMVECTOR 参数。XMMATRIX 参数类型别名的使用遵循一下规则:

  • 当函数的参数列表中 XMVECTOR 类型的参数不超过两个时,第一个 XMMATRIX 参数应使用 FXMMATRIX 类型,其余的 XMMATRIX 参数使用 CXMMATRIX 类型;
  • 当函数的参数列表中 XMVECTOR 类型的参数超过两个时,所有的 XMMATRIX 参数均使用 CXMMATRIX 类型。

采用上述规则的原因有点微妙:《DirectXMath 中的向量》中有提到,最多只能有 6 个 XMVECTOR 参数可以支持传递到 SSE/SSE2 寄存器中,当 XMVECTOR 参数个数不超过两个时,除去 XMVECTOR 参数占用掉的 SSE/SSE2 寄存器后,最多还可能有 4 个 SSE/SSE2 寄存器可以,可以容纳下一个 XMMATRIX 参数,故第一个 XMMATRIX 参数可使用 FXMMATRIX 类型;而当 XMVECTOR 参数个数超过两个时,剩余的 SSE/SSE2 寄存器不足以容纳一个 XMMATRIX 参数,故所有的 XMMATRIX 参数均使用 CXMMATRIX 类型。

另外,关于调用约定(Calling Conventions)的修饰符 XM_CALLCONV,可参阅《DirectXMath 中的向量》中的讨论。

实际上 FXMMATRIX 等类型别名在不同的平台和编译器上的实现各不相同,关于参数传递和调用约定的细节和详细规则,有兴趣的可以去查阅微软官方提供的文档。

常用的矩阵函数

DirectXMath 提供了许多函数以执行矩阵相关的运算操作,下面列出了一些常用的矩阵函数:

XMMATRIX XM_CALLCONV XMMatrixIdentity(); // Returns the identity matrix I
bool XM_CALLCONV XMMatrixIsIdentity( // Returns true if M is the identity matrix
    FXMMATRIX M  // Input M
); 
XMMATRIX XM_CALLCONV XMMatrixMultiply( // Returns the matrix product AB
    FXMMATRIX A, // Input A
    CXMMATRIX B  // Input B
);
XMMATRIX XM_CALLCONV XMMatrixTranspose( // Returns M^T
    FXMMATRIX M  // Input M
);
XMVECTOR XM_CALLCONV XMMatrixDeterminant( // Returns (det M, det M, det M, det M)
    FXMMATRIX M  // Input M
);
XMMATRIX XM_CALLCONV XMMatrixInverse( // Returns M^−1
    XMVECTOR* pDeterminant, // Input (det M, det M, det M, det M)
    FXMMATRIX M  // Input M
);

总结

本文主要首先讨论了 DirectXMath 中的矩阵类型 XMMATRIXXMFLOAT4X4,接着讨论了函数参数传递及调用约定的一些问题,最后介绍了 DirectXMath 提供的一些常用的矩阵运算函数。

参考文献