DirectX 12 持续整理 ——2. 矩阵

版权声明:转载请标注源地址。 https://blog.csdn.net/rkexy/article/details/72865318

跳过线性代数的数学概念部分。
绝大部分内容来自于《Introduction to 3D Game Programming with DirectX12 Frank D. Luna》
引用(值得一看):http://www.360doc.com/content/14/0824/14/202378_404267225.shtml

  刚刚送走了“向量”,而现在迎来了“矩阵”。英语没过四级的我在这里遇到了前所未有的阻碍……
  理解向量还是比较容易的事情,但是矩阵,有太多的东西令我无法理解。比如矩阵可以作为什么东西的模型?为什么矩阵的乘法规则定义得如此奇怪?我在想,我是不是应该看看类似于《矩阵论》之类的东西……



2.矩阵(Matrix)

  向量可以被认为是具有n个相互独立的性质(维度)的对象的表示,它能够模拟弹道,力,物体运动等。矩阵能用来做什么?
  我们使用矩阵可以简洁地描述几何变换,如缩放、旋转和平移,也可以从一个结构到另一个结构中改变一个点或是向量的坐标。(待补充)

2.1 矩阵类型(Matrix Types)

  在DirectX Math 中,使用XMMATRIX类来表示一个4x4的矩阵。当然,它被定义在DirectXMath.h文件中。不管怎么样,先来看看它是如何定义的。虽然大多数时候这些底层的定义很难懂,但是更多的时候它会给你对一个事物更深入的理解,这样有利于思考的深度。总之我的学习习惯就是这样的。

#if (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM)) && defined(_XM_NO_INTRINSICS_)
struct XMMATRIX
#else
__declspec(align(16)) struct XMMATRIX
#endif
{
#ifdef _XM_NO_INTRINSICS_
    union
    {
        XMVECTOR r[4];
        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];
    };
#else
    XMVECTOR r[4];
#endif

    XMMATRIX() {}
    XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, CXMVECTOR R3) 
    { 
        r[0] = R0; 
        r[1] = R1; 
        r[2] = R2; 
        r[3] = R3; 
    }
    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);
    explicit XMMATRIX(_In_reads_(16) const float *pArray);

#ifdef _XM_NO_INTRINSICS_
    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]; }
#endif

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

  在这里,出现了XMVECTOR,也就是说一个XMMATRIX可能是由4个向量组成的。_XM_NO_INTRINSICS_标志指示DirectX Math不使用SSE/SSE2等增强型指令集,此时XMMATRIX只是直观上由16个float组成的矩阵。
  除了使用各种构造函数来创建XMMATRIX实例之外,也可以通过XMMatrixSet函数来创建实例。

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

  下面是此函数的实现:

inline 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 M;
#if defined(_XM_NO_INTRINSICS_)
    M.m[0][0] = m00; M.m[0][1] = m01; M.m[0][2] = m02; M.m[0][3] = m03;
    M.m[1][0] = m10; M.m[1][1] = m11; M.m[1][2] = m12; M.m[1][3] = m13;
    M.m[2][0] = m20; M.m[2][1] = m21; M.m[2][2] = m22; M.m[2][3] = m23;
    M.m[3][0] = m30; M.m[3][1] = m31; M.m[3][2] = m32; M.m[3][3] = m33;
#else
    M.r[0] = XMVectorSet(m00, m01, m02, m03);
    M.r[1] = XMVectorSet(m10, m11, m12, m13);
    M.r[2] = XMVectorSet(m20, m21, m22, m23);
    M.r[3] = XMVectorSet(m30, m31, m32, m33);
#endif
    return M;
}

  显而易见,使用了和定义XMMATRIX同样的套路。XMMatrixSet里面调用了XMVectorSet,让16个float类型的参数组成了4个向量类型,然后构成了一个XMMATRIX
  同向量一样,矩阵同样有一些类型专门用于存储数据:

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

2.2 装载(loadling)与存储(storage)函数

  Load 与 Store 之类的函数用于XMFLOAT4X4XMMATRIX之间的转换:

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

/*store data from XMMATRIX into XMFLOAT4X4. */
inline void XM_CALLCONV XMStoreFloat4x4(XMFLOAT4X4* pDestination, FXMMATRIX M);

  出于好奇心,我想看看它是怎么实现的:

_Use_decl_annotations_
inline XMMATRIX XM_CALLCONV XMLoadFloat4x4
(
    const XMFLOAT4X4* pSource
)
{
    assert(pSource);
#if defined(_XM_NO_INTRINSICS_)

    XMMATRIX M;
    M.r[0].vector4_f32[0] = pSource->m[0][0];
    M.r[0].vector4_f32[1] = pSource->m[0][1];
    M.r[0].vector4_f32[2] = pSource->m[0][2];
    M.r[0].vector4_f32[3] = pSource->m[0][3];

    M.r[1].vector4_f32[0] = pSource->m[1][0];
    M.r[1].vector4_f32[1] = pSource->m[1][1];
    M.r[1].vector4_f32[2] = pSource->m[1][2];
    M.r[1].vector4_f32[3] = pSource->m[1][3];

    M.r[2].vector4_f32[0] = pSource->m[2][0];
    M.r[2].vector4_f32[1] = pSource->m[2][1];
    M.r[2].vector4_f32[2] = pSource->m[2][2];
    M.r[2].vector4_f32[3] = pSource->m[2][3];

    M.r[3].vector4_f32[0] = pSource->m[3][0];
    M.r[3].vector4_f32[1] = pSource->m[3][1];
    M.r[3].vector4_f32[2] = pSource->m[3][2];
    M.r[3].vector4_f32[3] = pSource->m[3][3];
    return M;

#elif defined(_XM_ARM_NEON_INTRINSICS_)
    XMMATRIX M;
    M.r[0] = vld1q_f32( reinterpret_cast<const float*>(&pSource->_11) );
    M.r[1] = vld1q_f32( reinterpret_cast<const float*>(&pSource->_21) );
    M.r[2] = vld1q_f32( reinterpret_cast<const float*>(&pSource->_31) );
    M.r[3] = vld1q_f32( reinterpret_cast<const float*>(&pSource->_41) );
    return M;
#elif defined(_XM_SSE_INTRINSICS_)
    XMMATRIX M;
    M.r[0] = _mm_loadu_ps( &pSource->_11 );
    M.r[1] = _mm_loadu_ps( &pSource->_21 );
    M.r[2] = _mm_loadu_ps( &pSource->_31 );
    M.r[3] = _mm_loadu_ps( &pSource->_41 );
    return M;
#elif defined(XM_NO_MISALIGNED_VECTOR_ACCESS)
#endif // _XM_VMX128_INTRINSICS_
}

//-----------------_mm_loadu_ps-------------------
extern __m128 _mm_loadu_ps(float const*_A);

  可以看到,pSource被暴力地塞进了XMMATRIX M中并且直接返回。
  XMMATRIX的基础完全是依靠XMVECTOR来实现的,也就是说,关于矩阵的真正难点在于它本身的数学性质(对我来说)。搞清楚矩阵的本质,用起来才能更加得心应手。另外,如果能摸透向量的特性,是不是使用矩阵会有出其不意的效果?
  矩阵的特性之一就是可以分块来计算,这对于在DirectX Math中仅仅存在4x4矩阵的情况下,想要使用2x2或者3x3的矩阵,使用XMMATRIX类型完全可行。既然搞清了XMMATRIX类型是由4个4维向量组成的,那么完全可以自己造轮子,模仿XMMATRIX创造出类似XMMATRIX2或者XMMATRIX3的类型为自己所用。
  不过话说回来,为什么必须要用4x4的矩阵来进行运算?
  作为一个负基础的菜鸟,我总是有各种各样的问题。于是我带着问题找到了一些答案。


2.3 为什么DirectX里表示三维坐标要建一个4*4的矩阵?

  为什么DirectX里表示三维坐标要建一个4*4的矩阵?
  引用:https://segmentfault.com/a/1190000004361135
  引用:https://www.zhihu.com/question/36296104?sort=created

(待补充)
  1. 矩阵是用于表示变换而不是坐标的;
  2. 对于图形的变换,我们是完全可以通过数学公式计算来完成,但实际的情况却是在变换十分复杂时,直接使用数学表达式来进行运算也是相当繁琐的。因此,在现实中常常使用矩阵(由m × n个标量组成的长方形数组)来表示诸如平移、旋转以及缩放等线性变换。
  3. 有些情况下,使用3x3的矩阵无法搞定,它只能解决矩阵的线性变换。此时需要借助4x4的矩阵来完成。(理解太过浅显,我先去补习矩阵的知识了。查看上面的引用网站会了解更多)


2.4 矩阵运算函数

/*返回一个单位矩阵 */
XMMATRIX XM_CALLCONV XMMatrixIdentity();

/*如果传入的矩阵为单位矩阵,返回true */
bool XM_CALLCONV XMMatrixIsIdentity( FXMMATRIX M ); 

/*返回矩阵的乘积 AB */
XMMATRIX XM_CALLCONV XMMatrixMultiply( FXMMATRIX A, CXMMATRIX B ); 

/*传入一个矩阵,返回它的转置矩阵 */
XMMATRIX XM_CALLCONV XMMatrixTranspose( FXMMATRIX M );

/*传入一个矩阵,返回它的行列式 */
XMVECTOR XM_CALLCONV XMMatrixDeterminant( FXMMATRIX M ); 

/**
    Input (det M, det M, det M, det M)
    Input M
    传入一个矩阵与它的行列式,返回它的逆矩阵
*/
XMMATRIX XM_CALLCONV XMMatrixInverse( XMVECTOR* pDeterminant, FXMMATRIX M );

  于是,一个熟悉的身影出现了:FXMMATRIX。那么,它和向量的参数传递规则有什么不同,先看看它的实现。

// Fix-up for (1st) XMMATRIX parameter to pass in-register on Xbox 360 and vector call; by reference otherwise
#if ( defined(_XM_VMX128_INTRINSICS )|| _XM_VECTORCALL_ ) && !defined(_XM_NO_INTRINSICS_)
typedef const XMMATRIX FXMMATRIX;
#else
typedef const XMMATRIX& FXMMATRIX;
#endif

// Fix-up for (2nd+) XMMATRIX parameters to pass in-register on Xbox 360, by reference otherwise
#if defined(_XM_VMX128_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_)
typedef const XMMATRIX CXMMATRIX;
#else
typedef const XMMATRIX& CXMMATRIX;
#endif

  第一个XMMATRIX 类型的参数使用FXMMATRIX传递,从第二个开始,使用CXMMATRIX传递。正好与一个XMMATRIX由4个XMVECTOR相对应。

  以下是书中提供的小例子。

#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
// Overload the "<<" operators so that we can use cout to
// output XMVECTOR and XMMATRIX objects.
ostream& XM_CALLCONV operator << (ostream& os, FXMVECTOR v)
{
    XMFLOAT4 dest;
    XMStoreFloat4(&dest, v);
    os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ", " << dest.w << ")";
    return os;
}
ostream& XM_CALLCONV operator << (ostream& os, FXMMATRIX m)
{
    for (int i = 0; i < 4; ++i)
    {
        os << XMVectorGetX(m.r[i]) << "\t";
        os << XMVectorGetY(m.r[i]) << "\t";
        os << XMVectorGetZ(m.r[i]) << "\t";
        os << XMVectorGetW(m.r[i]);
        os << endl;
    }
    return os;
}
int main()
{
    // Check support for SSE2 (Pentium4, AMD K8, and above).
    if (!XMVerifyCPUSupport())
    {
        cout << "directx math not supported" << endl;
        return 0;
    }
    XMMATRIX A(
        1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 2.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 4.0f, 0.0f,
        1.0f, 2.0f, 3.0f, 1.0f
    );
    XMMATRIX B = XMMatrixIdentity();
    XMMATRIX C = A * B;
    XMMATRIX D = XMMatrixTranspose(A);
    XMVECTOR det = XMMatrixDeterminant(A);
    XMMATRIX E = XMMatrixInverse(&det, A);
    XMMATRIX F = A * E;
    cout << "A = " << endl << A << endl;
    cout << "B = " << endl << B << endl;
    cout << "C = A*B = " << endl << C << endl;
    cout << "D = transpose(A) = " << endl << D << endl;
    cout << "det = determinant(A) = " << det << endl << endl;
    cout << "E = inverse(A) = " << endl << E << endl;
    cout << "F = A*E = " << endl << F << endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/rkexy/article/details/72865318