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