DX12龍書 02 – DirectXMath 庫中與向量有關的類和函數

0x00 需要用到的頭文件


#include <DirectXMath>
#include <DirectXPackedVector.h>

using namespace DirectX;
using namespace DirectX::PackedVector;

0x01 針對不同平台的設置


針對 x86 平台

需要啟用 SSE2 指令集(Project Properties(工程屬性) -> Configuration Properties(配置屬性) -> C/C++ -> Code Generation(程式碼生成) -> Enable Enhanced Instruction Set(啟用增強指令集)

針對所有平台

應當啟用快速浮點模型 /fp:fast (Project Properties(工程屬性) -> Configuration Properties(配置屬性) -> C/C++ -> Code Generation(程式碼生成) -> Floating Point Model(浮點模型)

針對 x64 平台

不必開啟 SSE2 指令集,因為所有的 x64 CPU 對此均有支援。

0x02 充分利用 SIMD 技術


什麼是 SIMD ?

SIMD 全稱 Single Instruction Multiple Data,單指令多數據流,能夠複製多個操作數,並把它們打包在大型暫存器的一組指令集,可一次性獲得所有操作數進行運算。

在 DirectXMath 中,核心向量類型是 XMVECTOR,它將被映射到 SIMD 硬體暫存器。在計算向量的過程中,必須通過此類型才可以充分地利用 SIMD 技術。

XMVECTOR 類型的數據需要按 16 位元組對齊,這對局部變數和全局變數都是自動實現的。至於類中的數據成員,建議分別使用 XMFLOAT2、XMFLOAT3 和 XMFLOAT4 類型來加以代替。

總結:

  1. 局部變數或全局變數用 XMVECTOR 類型。
  2. 對於類中的數據成員,使用 XMFLOAT2、XMFLOAT3 和 XMFLOAT4 類型。
  3. 在運算之前,通過載入函數將 XMFLOATn 類型轉換為 XMVECTOR 類型。
  4. 用 XMVECTOR 實例來進行運算。
  5. 通過存儲函數將 XMVECTOR 類型轉換為 XMFLOATn 類型。

0x03 載入和存儲方法


使用下面的方法將數據從 XMFLOATn 類型載入到 XMVECTOR 類型:

XMVECTOR XM_CALLCONV XMLoadFloatn(const XMFLOATn *pSource);

使用下面的方法將數據從 XMVECTOR 類型存儲到 XMFLOATn 類型:

void XM_CALLCONV XMStoreFloatn(XMFLOATn *pDestination, FXMVECTOR V);

如果只希望從 XMVECTOR 中得到一個向量的分量或將一個向量的分量轉換為 XMVECTOR 類型,可以使用下面的方法:

float XM_CALLCONV XMVectorGetX(FXMVECTOR V);
float XM_CALLCONV XMVectorGetY(FXMVECTOR V);
float XM_CALLCONV XMVectorGetZ(FXMVECTOR V);
float XM_CALLCONV XMVectorGetW(FXMVECTOR V);

XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V, float x);
XMVECTOR XM_CALLCONV XMVectorSetY(FXMVECTOR V, float y);
XMVECTOR XM_CALLCONV XMVectorSetZ(FXMVECTOR V, float z);
XMVECTOR XM_CALLCONV XMVectorSetW(FXMVECTOR V, float w);

0x04 參數的傳遞


為了提高效率,可以將 XMVECTOR 類型的值作為函數的參數,直接傳送至 SSE/SSE2 暫存器里,而不存於棧(stack)內。

這裡有幾個使用 XMVECTOR 參數的規則:

  1. 將約定註解 XM_CALLCONV 加在函數名前;
  2. 前 3 個 XMVECTOR 參數應當用類型 FXMVECTOR;
  3. 第 4 個 XMVECTOR 參數應當用類型 GXMVECTOR;
  4. 第 5、6 個 XMVECTOR 參數應當用類型 HXMVECTOR;
  5. 其餘的 XMVECTOR 參數應當用類型 CXMVECTOR。

構造函數注意事項

在編寫構造函數時,前 3 個 XMVECTOR 參數用 FXMVECTOR 類型,其餘 XMVECTOR 參數則用 CXMVECTOR 類型。

0x05 常向量


XMVECTOR 類型的常量實例應當用 XMVECTORF32 類型來表示。在初始化的時候就要使用 XMVECTORF32 類型。

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

XMVECTORF32 是一種按 16 位元組對齊的結構體,數學庫中還提供了將它轉換至 XMVECTOR 類型的運算符。其定義如下:

__declspec(align(16)) struct XMVECTORF32
{
    union
    {
        float f[4];
        XMVECTOR v;
    };

    inline operator XMVECTOR() const { return v; }
    inline operator const float*() const { return f; }
#if !defined(_XM_NO_INTRINSICS_) && defined(_XM_SSE_INTRINSICS_)
    inline operator __m128i() const { return _mm_castps_si128(v); }
    inline operator __m128d() const { return _mm_castps_pd(v); }
#endif
};

另外,也可以通過 XMVECTORU32 類型來創建由整型數據構成的 XMVECTOR 常向量:

static const XMVECTORU32 vGrabY = {0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000};

0x06 向量函數


DirectXMath 庫除了提供常見的向量加減法和標量運算的運算符重載之外,還提供了一些函數來執行各種向量運算。

值得注意的是,即使在數學上計算的結構是標量,比如點積,但這些函數所返回的類型依舊是 XMVECTOR,得到的標量結果則被複制到 XMVECTOR 中的各個分量之中。這樣做的原因之一是:將標量和 SIMD 向量的混合運算次數降到最低,全程使用 SIMD 技術,以提升計算效率。

DirectXMath 庫也提供一些估算方法,精度低但速度快。如:

// 返回估算值 ||V||
XMVECTOR XM_CALLCONV XMVector2LengthEst(FXMVECTOR V);

0x07 浮點數誤差


在比較浮點數時,一定要注意浮點數存在的誤差。我們認為相等的兩個浮點數可能會存在細微的差別。為了彌補浮點數精確性上的不足,我們通過比較兩個浮點數是否近似相等來加以解決。在比較時,需要定義一個非常小的常量 Epsilon。如果兩個數相差小於 Epsilon,就說這兩個數是近似相等的。

對此, DirectXMath 庫提供了 XMVector3NearEqual 函數,用於以 Epsilon 作為容差,測試比較的向量是否相等:

// return
// abs(V1.x - V2.x) <= Epsilon.x &&
// abs(V1.y - V2.y) <= Epsilon.y &&
// abs(V1.z - V2.z) <= Epsilon.z
bool XM_CALLCONV XMVector3NearEqual(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR Epsilon);

Tags: