DX12龍書 02 - DirectXMath 庫中與向量有關的類和函式
阿新 • • 發佈:2020-10-13
# 0x00 需要用到的標頭檔案
---
```cpp
#include
#include
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 型別:
```cpp
XMVECTOR XM_CALLCONV XMLoadFloatn(const XMFLOATn *pSource);
```
使用下面的方法將資料從 XMVECTOR 型別儲存到 XMFLOATn 型別:
```cpp
void XM_CALLCONV XMStoreFloatn(XMFLOATn *pDestination, FXMVECTOR V);
```
如果只希望從 XMVECTOR 中得到一個向量的分量或將一個向量的分量轉換為 XMVECTOR 型別,可以使用下面的方法:
```cpp
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 型別。 ```cpp static const XMVECTORF32 g_vHalfVector = {0.5f, 0.5f, 0.5f, 0.5f}; ``` XMVECTORF32 是一種按 16 位元組對齊的結構體,數學庫中還提供了將它轉換至 XMVECTOR 型別的運算子。其定義如下: ```cpp __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 常向量: ```cpp static const XMVECTORU32 vGrabY = {0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000}; ```
# 0x06 向量函式 --- DirectXMath 庫除了提供常見的向量加減法和標量運算的運算子過載之外,還提供了一些函式來執行各種向量運算。 > 值得注意的是,即使在數學上計算的結構是標量,比如點積,但這些函式所返回的型別依舊是 XMVECTOR,得到的標量結果則被複制到 XMVECTOR 中的各個分量之中。這樣做的原因之一是:將標量和 SIMD 向量的混合運算次數降到最低,全程使用 SIMD 技術,以提升計算效率。 DirectXMath 庫也提供一些估算方法,精度低但速度快。如: ```cpp // 返回估算值 ||V|| XMVECTOR XM_CALLCONV XMVector2LengthEst(FXMVECTOR V); ```
# 0x07 浮點數誤差 --- 在比較浮點數時,一定要注意浮點數存在的誤差。我們認為相等的兩個浮點數可能會存在細微的差別。為了彌補浮點數精確性上的不足,我們通過比較兩個浮點數是否近似相等來加以解決。在比較時,需要定義一個非常小的常量 Epsilon。如果兩個數相差小於 Epsilon,就說這兩個數是近似相等的。 對此, DirectXMath 庫提供了 XMVector3NearEqual 函式,用於以 Epsilon 作為容差,測試比較的向量是否相等: ```cpp // 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); ```
# 0x01 針對不同平臺的設定 --- **針對 x86 平臺**: 需要啟用 SSE2 指令集(`Project Properties(工程屬性)` -> `Configuration Properties(配置屬性)` -> `C/C++` -> `Code Generation(程式碼生成)` -> `Enable Enhanced Instruction Set(啟用增強指令集)`。 **針對所有平臺**: 應當啟用快速浮點模型 /fp:fast (`Project Properties(工程屬性)` ->
# 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 型別。
# 0x05 常向量 --- XMVECTOR 型別的常量例項應當用 XMVECTORF32 型別來表示。在初始化的時候就要使用 XMVECTORF32 型別。 ```cpp static const XMVECTORF32 g_vHalfVector = {0.5f, 0.5f, 0.5f, 0.5f}; ``` XMVECTORF32 是一種按 16 位元組對齊的結構體,數學庫中還提供了將它轉換至 XMVECTOR 型別的運算子。其定義如下: ```cpp __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 常向量: ```cpp static const XMVECTORU32 vGrabY = {0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000}; ```
# 0x06 向量函式 --- DirectXMath 庫除了提供常見的向量加減法和標量運算的運算子過載之外,還提供了一些函式來執行各種向量運算。 > 值得注意的是,即使在數學上計算的結構是標量,比如點積,但這些函式所返回的型別依舊是 XMVECTOR,得到的標量結果則被複制到 XMVECTOR 中的各個分量之中。這樣做的原因之一是:將標量和 SIMD 向量的混合運算次數降到最低,全程使用 SIMD 技術,以提升計算效率。 DirectXMath 庫也提供一些估算方法,精度低但速度快。如: ```cpp // 返回估算值 ||V|| XMVECTOR XM_CALLCONV XMVector2LengthEst(FXMVECTOR V); ```
# 0x07 浮點數誤差 --- 在比較浮點數時,一定要注意浮點數存在的誤差。我們認為相等的兩個浮點數可能會存在細微的差別。為了彌補浮點數精確性上的不足,我們通過比較兩個浮點數是否近似相等來加以解決。在比較時,需要定義一個非常小的常量 Epsilon。如果兩個數相差小於 Epsilon,就說這兩個數是近似相等的。 對此, DirectXMath 庫提供了 XMVector3NearEqual 函式,用於以 Epsilon 作為容差,測試比較的向量是否相等: ```cpp // 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); ```