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 類型來加以代替。
總結:
- 局部變數或全局變數用 XMVECTOR 類型。
- 對於類中的數據成員,使用 XMFLOAT2、XMFLOAT3 和 XMFLOAT4 類型。
- 在運算之前,通過載入函數將 XMFLOATn 類型轉換為 XMVECTOR 類型。
- 用 XMVECTOR 實例來進行運算。
- 通過存儲函數將 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 參數的規則:
- 將約定註解 XM_CALLCONV 加在函數名前;
- 前 3 個 XMVECTOR 參數應當用類型 FXMVECTOR;
- 第 4 個 XMVECTOR 參數應當用類型 GXMVECTOR;
- 第 5、6 個 XMVECTOR 參數應當用類型 HXMVECTOR;
- 其餘的 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);