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

drnkcff發表於2020-10-13

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

相關文章