【DX12】DirectX Math库 Vector和Matrix类型 XMVECTOR、XMMATRIX

DirectX Math Library 是一个3D数学库,包含在Windows SDK中。该库使用了SSE2(Streaming SIMD Extensions 2)指令集。使用128位宽的SIMD(Single Instruction Multiple Data)寄存器,因此做向量计算会更快。

注:在X86平台下,需要在VS中手动设置,启用SSE2(Enable Enhanced Instruction Set)。在任何平台下,都要手动启用fast floating point model。在project prote具体方法请查阅资料。

1 Vector

1.1 XMVECTOR

DirectX Math Library库中核心的Vector类型:XMVECTOR。是对SIMD硬件寄存器的映射。在当前CPU上SSE2指令集可用时,它是这样定义的:

typedef _m128 XMVECTOR;

_m128是由Microsoft提供的基本数据类型,用于与SSE、SSE2内部指令一起使用的。在16字节边界上自动对齐。ARM处理器不支持该类型。

虽然这是一个128位宽的寄存器,能存储一个四维的Vector,但当我们使用二维或三维的Vector运算时,为了效率,仍然会去使用XMVECTOR类型。

XMVECTOR的数据不能直接访问。可以转换成XMFLOAT类型(转换方法见下文),也可通过特定的接口get或set其某个分量:

// XM_CALLCONV 的作用之后会解释
// 为什么这个地方是FXMVECTOR,多了个‘F’,之后会解释
float XM_CALLCONV XMVectorGetX(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V, float x);

XMVECTOR重载了加减乘除等运算符,可以方便的进行运算。

1.2 XMVECTORF32

XMVECTOR的常量实例,应定义为XMVECTORF32。

例如:

static const XMVECTORF32 g_vHalfVector = {0.5f ,0.5f, 0.5f, 0.5f};

XMVECTORF32可以转换为四维float数组,或者XMVECTOR。

1.3 XMFLOATn

上文提到,XMVECTOR需要16位对齐,当定义为局部或者全局变量时,这个对齐操作是自动的。当定义为类的成员变量时,推荐使用XMFLOAT2、XMFLOAT3、XMFLOAT4来代替。这些类型的数据结构,实际只是封装了几个普通的float变量。

XMVECTOR与XMFLOATn之间可以相互转换。当要进行计算操作时,建议将XMFLOATn转换为XMVECTOR。XMFLOATn到XMVECTOR的转换,需要通过DirectX Math loading functions来完成。
XMVECTOR到XMFLOATn的转换,需要通过DirectX Math storage functions来完成。

转换函数的申明如下:

// 加载XMFLOAT2到XMVECTOR
XMVECTOR XM_CALLCONV XMLoadFloat2(cosnt XMFLOAT2 *pSource);

// 存储XMVECTOR到XMFLOAT2
void XM_CALLCONV XMStoreFloat2(XMFLOAT2 *pDestination, FXMVECTOR V); 

1.4 参数传递

由于XMVECTOR的数据存储在寄存器,而不在堆栈上,因此参数传递时会有些特殊。基本的想法是,能用寄存器传递的就用寄存器传递,不能的就用栈。具体可以通过寄存器传递的参数数量,根据平台和编译器而定。

为此,增加了几种用于参数传递的数据类型,FXMVECTOR、GXMVECTOR、HXMVECTOR、CXMVECTOR。基本的使用规则是:

  1. 前三个XMVECTOR参数使用FXMVECTOR
  2. 第四个XMVECTOR参数使用GXMVECTOR
  3. 第五六个XMVECTOR参数使用HXMVECTOR
  4. 其他使用CXMVECTOR

而宏XM_CALLCONV,就是用于告诉编译器该函数调用时,有这种特别的参数传递规则。

所以会看到这样的定义:

void XM_CALLCONV XMStoreFloat2(XMFLOAT2 *pDestination, FXMVECTOR V); 

但是,这些规则在构造函数中并不适用。在构造函数中:

  1. 前三个XMVECTOR参数使用FXMVECTOR
  2. 其他使用CXMVECTOR
  3. 不要使用XM_CALLCONV

另外,这些规则只是针对于XMVECTOR类型本身。而对于复合类型,XMVECTOR&、XMVECTOR*并不会使用寄存器传递,和普通参数同等看待。

有了上面的基础可以看两个例子。有助于理解。

#include  // 为了使用函数XMVerifyCPUSupport,检查是否支持SSE2
#include 
#include 
#include 
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
// 重载  "<<" 运算符,用于输出XMVECTOR类型的对象
ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)
{
    // XMVECTOR数据不能直接访问,因此要进行转换!
    XMFLOAT3 dest;
    XMStoreFloat3(&dest, v);
    os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ")";
    return os;
}
int main()
{
    // 检查是否支持SSE2 (Pentium4, AMD K8, and above).
    if (!XMVerifyCPUSupport())
    {
        cout << "directx math not supported" << endl;
        return 0;
    }

    // 一些常用的获取XMVECTOR对象的函数
    XMVECTOR p = XMVectorZero();
    XMVECTOR q = XMVectorSplatOne();
    XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);
    XMVECTOR v = XMVectorReplicate(-2.0f);
    XMVECTOR w = XMVectorSplatZ(u);
    cout << "p = " << p << endl;
    cout << "q = " << q << endl;
    cout << "u = " << u << endl;
    cout << "v = " << v << endl;
    cout << "w = " << w << endl;

    system("pause");
    return 0;
}
/*
output:
p = (0, 0, 0)
q = (1, 1, 1)
u = (1, 2, 3)
v = (-2, -2, -2)
w = (3, 3, 3)
*/

第二个例子中将使用一些常用的Vector运算。

注意,即使运算结果为标量,返回值仍然是XMVECTOR,因为运算都是通过SIMD进行的。这也有利于继续使用这个标量进行Vector运算。

#include  // 为了使用函数XMVerifyCPUSupport,检查是否支持SSE2
#include 
#include 
#include 
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
// 重载  "<<" 运算符,用于输出XMVECTOR类型的对象
ostream& XM_CALLCONV operator << (ostream& os, FXMVECTOR v)
{
    // XMVECTOR数据不能直接访问,因此要进行转换!
    XMFLOAT3 dest;
    XMStoreFloat3(&dest, v);
    os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ")";
    return os;
}
int main()
{
    cout.setf(ios_base::boolalpha);
    // 检查是否支持SSE2 (Pentium4, AMD K8, and above).
    if (!XMVerifyCPUSupport())
    {
        cout << "directx math not supported" << endl;
        return 0;
    }
    XMVECTOR n = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);
    XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);
    XMVECTOR v = XMVectorSet(-2.0f, 1.0f, -3.0f, 0.0f);
    XMVECTOR w = XMVectorSet(0.707f, 0.707f, 0.0f, 0.0f);
    // Vector addition: XMVECTOR operator +
    XMVECTOR a = u + v;
    // Vector subtraction: XMVECTOR operator -
    XMVECTOR b = u - v;
    // Scalar multiplication: XMVECTOR operator *
    XMVECTOR c = 10.0f*u;
    // 向量的模 ||u||
    XMVECTOR L = XMVector3Length(u);
    // d = u / ||u||
    XMVECTOR d = XMVector3Normalize(u);
    // s = u dot v
    XMVECTOR s = XMVector3Dot(u, v);
    // e = u x v
    XMVECTOR e = XMVector3Cross(u, v);
    // 计算w平行于n、垂直于n的两个分量
    XMVECTOR projW;
    XMVECTOR perpW;
    XMVector3ComponentsFromNormal(&projW, &perpW, w, n);
    // Does projW + perpW == w?
    bool equal = XMVector3Equal(projW + perpW, w) != 0;
    bool notEqual = XMVector3NotEqual(projW + perpW, w) != 0;
    //  projW 和 perpW 成90度.
    XMVECTOR angleVec = XMVector3AngleBetweenVectors(projW, perpW);
    float angleRadians = XMVectorGetX(angleVec);
    float angleDegrees = XMConvertToDegrees(angleRadians);
    cout << "u                   = " << u << endl;
    cout << "v                   = " << v << endl;
    cout << "w                   = " << w << endl;
    cout << "n                   = " << n << endl;
    cout << "a = u + v           = " << a << endl;
    cout << "b = u - v           = " << b << endl;
    cout << "c = 10 * u          = " << c << endl;
    cout << "d = u / ||u||       = " << d << endl;
    cout << "e = u x v           = " << e << endl;
    cout << "L  = ||u||          = " << L << endl;
    cout << "s = u.v             = " << s << endl;
    cout << "projW               = " << projW << endl;
    cout << "perpW               = " << perpW << endl;
    cout << "projW + perpW == w  = " << equal << endl;
    cout << "projW + perpW != w  = " << notEqual << endl;
    cout << "angle               = " << angleDegrees << endl;
    system("pause");
    return 0;
}
/*
output:
u                   = (1, 2, 3)
v                   = (-2, 1, -3)
w                   = (0.707, 0.707, 0)
n                   = (1, 0, 0)
a = u + v           = (-1, 3, 0)
b = u - v           = (3, 1, 6)
c = 10 * u          = (10, 20, 30)
d = u / ||u||       = (0.267261, 0.534522, 0.801784)
e = u x v           = (-9, -3, 5)
L  = ||u||          = (3.74166, 3.74166, 3.74166)
s = u.v             = (-9, -9, -9)
projW               = (0.707, 0, 0)
perpW               = (0, 0.707, 0)
projW + perpW == w  = true
projW + perpW != w  = false
angle               = 90
*/

Matrix

XMMATRIX类型用于存储矩阵,有了上面的基础,也就比较简单了。

XMMATRIX实际是存储了4个XMVECTOR。也有对应的非寄存器类型XMFLOAT4X4,有16个float组成。有相互转换的函数。

在函数的参数传递中,第一个XMMATRIX类型要用FXMMATRIX,其他用CXMMATRIX。

看一个例子

#include 
#include 
#include 
#include 
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
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()
{
    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;
}

reference:
Introdunction to 3D Game Programming with DirectX 12

你可能感兴趣的:(图形学,DirextX)