Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 第一章:向量代數

原文: Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 第一章:向量代數

學習目標:

  1. 學習如何使用幾何學和數字描述 Vector;
  2. 學習 Vector 的運算方法及其在幾何學上的應用;
  3. 熟悉在 DirectXMath library 中的 Vector 相關的類和函數。


1 向量

一個向量表明的是一個擁有大小和方向的量。相似力(擁有力的大小和方向)、位移(移動的方向和距離)、速度(速度的大小和方向)等,例以下圖(圖 1.1):
幾何學中向量的表示
繪製向量的位置和向量自己無關,因此當且僅當兩個向量的大小和方形相等時,兩個向量相等;因此上圖中a和b中的向量u和向量v相等。ios


1.1 向量和座標系統

咱們如今能夠定義各類向量幾何運算來解決對應的問題,可是由於計算機沒法直接經過幾何學計算向量,因此咱們須要用數字來描述向量;
考慮到下圖中的狀況(圖1.4),同一個向量在不一樣座標系中會有不一樣的座標變現:
向量在不一樣座標系下
在計算機3D圖形學中咱們須要使用多個座標系,咱們要知道當前在哪個座標系下,而且熟悉座標系之間的轉換。windows


1.2 左手座標系 VS 右手座標系

Direct3D 使用的是左手座標系:若是你使用你的左手指向 X軸正方向,而後向Y軸正方向彎曲你的手指,此時你的大拇指指向的就是Z軸正方向,如圖1.5 所示(右手座標系相似,只不過替換爲右手)
左手座標系和右手座標系markdown


1.3 基本向量運算

基本向量運算
咱們也能夠在座標系中使用繪製的方法來表示:
座標系中表示向量加減法函數



2 長度和單位向量

向量長度計算公式:
這裏寫圖片描述
單位向量計算公式:
這裏寫圖片描述
爲了證實單位向量計算公式,咱們能夠計算單位向量的長度:
這裏寫圖片描述性能



3 向量的點積

向量的點積是一種結果爲數量值的乘法形式,其定義和計算公式爲:
這裏寫圖片描述
向量的點積的定義並無提出明顯的幾何定義,利用餘弦定理,咱們能夠找到幾何關係:
這裏寫圖片描述
這裏寫圖片描述
如圖1.9,θ是向量v和u的夾角,再根據上面的公式,咱們能夠得出向量點積的一些有用的幾何屬性:學習

  1. 當兩個向量點積爲0時,兩個向量垂直;
  2. 當兩個向量點積大於0時,兩個向量夾角小於90度;
  3. 當兩個向量點積小於0時,兩個向量夾角大於90度。

3.1 向量的分解

如圖1.10
這裏寫圖片描述
給出向量v和單位向量n,使用點積公式求出向量p:
這裏寫圖片描述
向量p也能夠表示爲:p=projn(v)p = proj_n(v)p=projn(v),那麼向量w = v - p
因此向量v就能夠分解爲:v=p+w=projn(v)+perpn(v)v = p + w = proj_n(v) + perp_n(v)v=p+w=projn(v)+perpn(v)
若是向量n不是單位向量,那麼能夠提早把n標準化:
這裏寫圖片描述優化


3.2 正交化

當一組向量相互之間都垂直,而且都是單位向量時,咱們稱他們爲標準正交。在3D計算機圖形學中,咱們剛開始可能會有一組標準正交的向量,可是因爲變量精度的問題,這些向量會在進行一系列計算後開始變得相互不垂直。因此咱們的目標就是主要考慮在3D和2D狀況下手動正交化。
咱們先從2D開始,假設擁有向量v0v_0v0v1v_1v1,咱們要將他們正交化爲標準正交集w0w_0w0w1w_1w1;首先咱們使w0=v0w_0=v_0w0=v0,而後修改v1v_1v1讓它垂直於w0w_0w0
這裏寫圖片描述
這裏寫圖片描述
在3D狀況下,咱們繼續而後修改v2v_2v2讓它同時垂直於w0w_0w0w1w_1w1
這裏寫圖片描述
這裏寫圖片描述
最後一步是標準化每個向量爲單位向量。
這套正交化流程咱們統一稱之爲 施密特正交化(Gram-Schmidt Orthogonalization)。
這裏寫圖片描述this



4 向量的叉積

兩個向量的叉積(叉積不支持2D)結果爲另一個同時垂直於他倆的向量,叉積的運算公式爲:
這裏寫圖片描述
叉積不支持交換律,其交換後的結果是相反的,即:u * v = - v * u。atom


4.1 僞2D叉積

在2D狀況下,若是已知u=(ux,uy)u = (u_x, u_y)u=(ux,uy),找出垂直於u的向量v=(−uy,ux)v = (-u_y, u_x)v=(uy,ux),該公式的證實以下:
這裏寫圖片描述spa


4.2 使用叉積正交化

使用叉積正交化會形成一些偏差

  1. 首先設置 w0=v0∣∣v0∣∣w_0 = \frac{v_0}{||v_0||}w0=v0v0
  2. 設置 w2=w0×v1∣∣w0×v1∣∣w_2 = \frac{w_0 \times v_1}{||w_0 \times v_1||}w2=w0×v1w0×v1
  3. 最後 w1=w2×w0w_1 = w_2 \times w_0w1=w2×w0,由於w2w_2w2w0w_0w0都已是單位向量,因此不須要對w1w_1w1作標準化。
    這裏寫圖片描述


5 點

點在3D圖形學中要來表示位置,在座標系中,一個向量能夠表示一個位置。
一方面,咱們對向量的運算不能應用到點上(好比兩個點相加是沒有意義的);另外一方面,咱們能夠把這些運算擴展到點上。
這裏寫圖片描述



6 DIRECTX MATH 中的向量

Direct Math是Direct3D應用中的一個數學庫,而且已經內置到了Window 8以上的操做系統。

該數學庫使用 SSE2(Streaming SIMD Extensions 2) 系統指令,支持128位的 SIMD(single instruction multiple data) 寄存器。SIMD 指令能夠在一條指令中運算4個32位的浮點數和整數。這對於向量的計算很是有用。
好比作4D向量的相加,咱們不須要使用4條標量相加指令,而是1條SIMD指令便可,對於2D和3D向量也可使用SIMD,咱們能夠無視不使用的座標系。

若是想要了解DirectX Math的所有細節,推薦閱讀DirectX Math的在線文檔;
若是想要知道SIMD向量庫如何優化開發,或者瞭解它爲什麼如此設計,推薦閱讀文章:Designing Fast Cross-Platform SIMD Vector Libraries by [Oliveira2010]

使用DirectX Math時,須要的全部頭文件:

#include <DirectXMath.h>			// namespace: DirectX    			DirectX 數學庫			
#include <DirectXPackedVector.h>	// namespace: DirectX::PackedVector	一些額外附加數據類型

對於X86系統,須要開啓SSE2(Project Properties > Configuration Properties > C/C++ > Code Generation > Enable Enhanced Instruction Set);
對於全部系統,還須要開啓快速浮點數模式**(Project Properties > Configuration Properties > C/C++ > Code Generation > Floating Point Model**);
對於64位系統不須要開啓SSE2,由於全部64位CPU都支持SSE2(http://en.wikipedia.org/wiki/SSE2)。


6.1 向量的類型

DirectX Math 的核心類型是映射到SIMD硬件寄存器的 XMVECTOR,它是一個128位,可使用單個指令計算4個32位浮點數的類型。對於X86和64位系統中,它的定義以下:

typedef __m128 XMVECTOR;

__m128是SIMD專用的類型。當咱們計算的時候,向量必須聲明位該類型才能利用SIMD的優勢。

XMVECTOR的局部和所有變量會自動被16位對其;對於類的成員變量,使用XMFLOAT2 (2D),XMFLOAT3 (3D),和XMFLOAT4 (4D) 來替換;

struct XMFLOAT2
{
	float x;
	float y;
	
	XMFLOAT2() {}
	XMFLOAT2(float _x, float _y) : x(_x), y(_y) {}
	explicit XMFLOAT2(_In_reads_(2) const float *pArray) : x(pArray[0]), y(pArray[1]) {}
		
	XMFLOAT2& operator= (const XMFLOAT2& Float2) { x = Float2.x; y = Float2.y; return *this; }
};

struct XMFLOAT3
{
	float x;
	float y;
	float z;
	
	XMFLOAT3() {}
	XMFLOAT3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
	explicit XMFLOAT3(_In_reads_(3) const float *pArray) : x(pArray[0]), y(pArray[1]), z(pArray[2]) {}
	
	XMFLOAT3& operator= (const XMFLOAT3& Float3) { x = Float3.x; y = Float3.y; z = Float3.z; return *this; }
};

struct XMFLOAT4
{
	float x;
	float y;
	float z;
	float w;
	
	XMFLOAT4() {}
	XMFLOAT4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) {}
	explicit XMFLOAT4(_In_reads_(4) const float *pArray) : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {}
	
	XMFLOAT4& operator= (const XMFLOAT4& Float4) { x = Float4.x; y = Float4.y; z = Float4.z; w = Float4.w; return *this; }
};

若是直接利用這些類型進行計算,就沒法利用SIMD的優勢,因此咱們須要進行類型的轉換;DirectX Math中提供了Loading函數能夠將XMFLOATn類型數據加載到XMVECTOR;Storage函數能夠將XMVECTOR類型數據保存到XMFLOATn。

總結以下:

  1. 對於局部或者全局變量,使用XMVECTOR;
  2. 對於類的成員變量,使用XMFLOATn;
  3. 使用Loading和Storage函數對數據進行加載和保存;
  4. 計算的時候使用XMVECTOR類型;

6.2 Loading 和 Storage 方法

將數據從XMFLOATn加載到XMVECTOR的Loading方法以下:

// Loads XMFLOAT2 into XMVECTOR
XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2 *pSource);

// Loads XMFLOAT3 into XMVECTOR
XMVECTOR XM_CALLCONV XMLoadFloat3(const XMFLOAT3 *pSource);

// Loads XMFLOAT4 into XMVECTOR
XMVECTOR XM_CALLCONV XMLoadFloat4(const XMFLOAT4 *pSource);

將數據從XMVECTOR保存到XMFLOATn的Storage方法以下:

// Loads XMVECTOR into XMFLOAT2
void XM_CALLCONV XMStoreFloat2(XMFLOAT2 *pDestination, FXMVECTOR V);

// Loads XMVECTOR into XMFLOAT3
void XM_CALLCONV XMStoreFloat3(XMFLOAT3 *pDestination, FXMVECTOR V);

// Loads XMVECTOR into XMFLOAT4
void XM_CALLCONV XMStoreFloat4(XMFLOAT4 *pDestination, FXMVECTOR V);

有時咱們只想修改或者獲取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);

6.3 參數傳遞

爲了優化性能爲目的,XMVECTOR能夠做爲函數參數直接傳遞到SSE/SSE2寄存器中(而不是堆棧內存),參數傳遞的數量依賴於平臺(例如:32/64位 Windows,Windows RT)和編譯器。因此根據不一樣平臺/編譯器,咱們使用FXMVECTOR,GXMVECTOR,HXMVECTOR 和 CXMVECTOR類型來傳遞XMVECTOR參數;此外,在函數名前要指明調用註釋XM_CALLCONV

XMVECTOR類型參數傳遞規則以下:

  1. 前三個參數類型要定義爲FXMVECTOR;
  2. 第四個要定義爲GXMVECTOR;
  3. 第五個和第六個要定義爲HXMVECTOR;
  4. 其餘參數要定義爲CXMVECTOR。

在32爲Windows下,支持__fastcall調用約定和支持更新的__vectorcall調用約定編譯中,參數定義以下:

// 32-bit Windows __fastcall passes first 3 XMVECTOR arguments
// via registers, the remaining on the stack.
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR& GXMVECTOR;
typedef const XMVECTOR& HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;

// 32-bit Windows __vectorcall passes first 6 XMVECTOR arguments
// via registers, the remaining on the stack.
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR GXMVECTOR;
typedef const XMVECTOR HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;

想了解在其餘平臺定義的更多細節,能夠閱讀DirectX Math的文檔,「Library Internals」 下的 「Calling Conventions」 ;
在構造函數中,這些規則是例外:文檔推薦前三個參數使用FXMVECTOR,其餘參數使用CXMVECTOR,而且不要爲構造函數添加XM_CALLCONV;

inline XMMATRIX XM_CALLCONV XMMatrixTransformation(
	FXMVECTOR ScalingOrigin,
	FXMVECTOR ScalingOrientationQuaternion, .
	FXMVECTOR Scaling,
	GXMVECTOR RotationOrigin,
	HXMVECTOR RotationQuaternion,
	HXMVECTOR Translation);

函數調用時也能夠添加非XMVECTOR類型參數,XMVECTOR參數定義規則相同,非XMVECTOR類型參數不計數:

inline XMMATRIX XM_CALLCONV XMMatrixTransformation2D(
	FXMVECTOR ScalingOrigin,
	float ScalingOrientation,
	FXMVECTOR Scaling,
	FXMVECTOR RotationOrigin,
	float Rotation,
	GXMVECTOR Translation);

這些規則只使用於輸入參數,輸出參數不使用SSE/SSE2寄存器,因此會被對待爲和非XMVECTOR類型參數同樣。


6.4 常量向量

常量向量的實例須要使用XMVECTORF32類型,下面是一些在DirectX SDK裏CascadedShadowMaps11 Demo 下的例子:

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

XMVECTORF32 vRightTop = {
	vViewFrust.RightSlope,
	vViewFrust.TopSlope,
	1.0f,1.0f
	};
	
XMVECTORF32 vLeftBottom = {
	vViewFrust.LeftSlope,
	vViewFrust.BottomSlope,
	1.0f,1.0f
	};

其實全部相似的初始化操做均可以使用XMVECTORF32類型,它是一個16位對齊並帶有XMVECTOR轉換的結構體,它的定義以下:

// Conversion types for constants
__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 };

6.5 重載運算符

XMVECTOR有幾個重載運算符來計算向量的加減和量乘法:

XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V);
XMVECTOR XM_CALLCONV operator- (FXMVECTOR V);
XMVECTOR& XM_CALLCONV operator+= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator-= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator*= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator/= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR& operator*= (XMVECTOR& V, float S);
XMVECTOR& operator/= (XMVECTOR& V, float S);
XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator- (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator* (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator* (FXMVECTOR V, float S);
XMVECTOR XM_CALLCONV operator* (float S, FXMVECTOR V);
XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V, float S);

6.6 其它

DirectX Math定義了一些有用的常量來近似的表現和 π 相關的值:

const float XM_PI = 3.141592654f;
const float XM_2PI = 6.283185307f;
const float XM_1DIVPI = 0.318309886f;
const float XM_1DIV2PI = 0.159154943f;
const float XM_PIDIV2 = 1.570796327f;
const float XM_PIDIV4 = 0.785398163f;

另外,還定義了下面的內斂函數用以在轉換角度和弧度:

inline float XMConvertToRadians(float fDegrees) { return fDegrees * (XM_PI / 180.0f); }
inline float XMConvertToDegrees(float fRadians) { return fRadians * (180.0f / XM_PI); }

還定義了min/max函數:

template<class T> inline T XMMin(T a, T b) { return (a < b) ? a : b; }
template<class T> inline T XMMax(T a, T b) { return (a > b) ? a : b; }

6.7 Setter 函數

DirectX Math提供了下面函數用來修改XMVECTOR的值:

// Returns the zero vector 0
XMVECTOR XM_CALLCONV XMVectorZero();
// Returns the vector (1, 1, 1, 1)
XMVECTOR XM_CALLCONV XMVectorSplatOne();
// Returns the vector (x, y, z, w)
XMVECTOR XM_CALLCONV XMVectorSet(float x, float y, float z, float w);
// Returns the vector (s, s, s, s)
XMVECTOR XM_CALLCONV XMVectorReplicate(float Value);
// Returns the vector (vx, vx, vx, vx)
XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V);
// Returns the vector (vy, vy, vy, vy)
XMVECTOR XM_CALLCONV XMVectorSplatY(FXMVECTOR V);
// Returns the vector (vz, vz, vz, vz)
XMVECTOR XM_CALLCONV XMVectorSplatZ(FXMVECTOR V);

下面的代碼解釋了大部分函數的使用:

#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>

using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

// Overload the "<<" operators so that we can use cout to
// output XMVECTOR objects.
ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)
{
	XMFLOAT3 dest;
	XMStoreFloat3(&dest, v);
	os << "(" << dest.x << ", " << dest.y << ", "
	<< dest.z << ")";
	return os;
} 

int main()
{
	cout.setf(ios_base::boolalpha);
	
	// Check support for SSE2 (Pentium4, AMD K8, and above).
	if (!XMVerifyCPUSupport())
	{
		cout << "directx math not supported" << endl;
		return 0;
	}
	
	XMVECTOR p = XMVectorZero();
	XMVECTOR q = XMVectorSplatOne();
	XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);
	XMVECTOR v = XMVectorReplicate(-2.0f);
	XMVECTOR w = XMVectorSplatZ(u);
	
	cout << "p = " << p << endl;
	cout << "q = " << q << endl;
	cout << "u = " << u << endl;
	cout << "v = " << v << endl;
	cout << "w = " << w << endl;
	return 0;
}

這裏寫圖片描述


6.8 向量的函數

DirectX Math提供了下面的函數來處理各類向量運算,這裏介紹3D版本,2D和4D版本於3D相似:
有些能夠直接返回值的函數依然返回的是XMVECTOR,好比向量的點積,這樣作是爲了儘量減小SIMD和其它值的混合,從而提升性能

XMVECTOR XM_CALLCONV XMVector3Length( 
	// Returns ||v||
	FXMVECTOR V); // Input v
	
XMVECTOR XM_CALLCONV XMVector3LengthSq( 
	// Returns ||v||2
	FXMVECTOR V); // Input v
	
XMVECTOR XM_CALLCONV XMVector3Dot( 
	// Returns v1·v2
	FXMVECTOR V1, // Input v1
	FXMVECTOR V2); // Input v2
	
XMVECTOR XM_CALLCONV XMVector3Cross( 
	// Returns v1 × v2
	FXMVECTOR V1, // Input v1
	FXMVECTOR V2); // Input v2
	
XMVECTOR XM_CALLCONV XMVector3Normalize( 
	// Returns v/||v||
	FXMVECTOR V); // Input v
	
XMVECTOR XM_CALLCONV XMVector3Orthogonal( 
	// Returns a vector orthogonal to v
	FXMVECTOR V); // Input v

XMVECTOR XM_CALLCONV XMVector3AngleBetweenVectors( 
	// Returns the angle between v1 and v2
	FXMVECTOR V1, // Input v1
	FXMVECTOR V2); // Input v2

void XM_CALLCONV XMVector3ComponentsFromNormal( 
	XMVECTOR* pParallel, // Returns projn(v)
	XMVECTOR* pPerpendicular, // Returns perpn(v)
	FXMVECTOR V, // Input v
	FXMVECTOR Normal); // Input n

bool XM_CALLCONV XMVector3Equal( 
	// Returns v1 = v2
	FXMVECTOR V1, // Input v1
	FXMVECTOR V2); // Input v2

bool XM_CALLCONV XMVector3NotEqual( 
	// Returns v1 ≠ v2
	FXMVECTOR V1, // Input v1
	FXMVECTOR V2); // Input v2

下面的示例程序展現了大部分函數和一些重載雲算符的用法:

#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>

using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

// Overload the "<<" operators so that we can use cout to
// output XMVECTOR objects.
ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)
{
	XMFLOAT3 dest;
	XMStoreFloat3(&dest, v);
	os << "(" << dest.x << ", " << dest.y << ", "
	<< dest.z << ")";
	return os;
}

int main()
{
	cout.setf(ios_base::boolalpha);
	// Check support for SSE2 (Pentium4, AMD K8, and above).
	if (!XMVerifyCPUSupport())
	{
		cout << "directx math not supported" << endl;
		return 0;
	}
	
	XMVECTOR n = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);
	XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);
	XMVECTOR v = XMVectorSet(-2.0f, 1.0f, -3.0f, 0.0f);
	XMVECTOR w = XMVectorSet(0.707f, 0.707f, 0.0f, 0.0f);
	
	// Vector addition: XMVECTOR operator +
	XMVECTOR a = u + v;
	// Vector subtraction: XMVECTOR operator -
	XMVECTOR b = u - v;
	// Scalar multiplication: XMVECTOR operator *
	XMVECTOR c = 10.0f*u;
	// ||u||
	XMVECTOR L = XMVector3Length(u);
	// d = u / ||u||
	XMVECTOR d = XMVector3Normalize(u);
	// s = u dot v
	XMVECTOR s = XMVector3Dot(u, v);
	// e = u x v
	XMVECTOR e = XMVector3Cross(u, v);
	// Find proj_n(w) and perp_n(w)
	XMVECTOR projW;
	XMVECTOR perpW;
	XMVector3ComponentsFromNormal(&projW, &perpW, w, n);
	// Does projW + perpW == w?
	bool equal = XMVector3Equal(projW + perpW, w) != 0;
	bool notEqual = XMVector3NotEqual(projW + perpW, w) != 0;
	// The angle between projW and perpW should be 90 degrees.
	XMVECTOR angleVec = XMVector3AngleBetweenVectors(projW, perpW);
	float angleRadians = XMVectorGetX(angleVec);
	float angleDegrees = XMConvertToDegrees(angleRadians);
	
	cout << "u = " << u << endl;
	cout << "v = " << v << endl;
	cout << "w = " << w << endl;
	cout << "n = " << n << endl;
	cout << "a = u + v = " << a << endl;
	cout << "b = u - v = " << b << endl;
	cout << "c = 10 * u = " << c << endl;
	cout << "d = u / ||u|| = " << d << endl;
	cout << "e = u x v = " << e << endl;
	cout << "L = ||u|| = " << L << endl;
	cout << "s = u.v = " << s << endl;
	cout << "projW = " << projW << endl;
	cout << "perpW = " << perpW << endl;
	cout << "projW + perpW == w = " << equal << endl;
	cout << "projW + perpW != w = " << notEqual << endl;
	cout << "angle = " << angleDegrees << endl;
	return 0;
}

這裏寫圖片描述

DirectX Math還包含一些求近似值的函數,它們準確度較低,可是更快;若是你願意犧牲準確度去追求速度,能夠考慮使用它們,下面是其中的2個例子:

XMVECTOR XM_CALLCONV XMVector3LengthEst( 
	// Returns estimated ||v||
	FXMVECTOR V); // Input v
	
XMVECTOR XM_CALLCONV XMVector3NormalizeEst( 
	// Returns estimated v/||v||
	FXMVECTOR V); // Input v

6.9 浮點數偏差

DirectX Math庫提供了一個函數XMVector3NearEqual用以判斷兩個向量在可容許的偏差下是否相等:

// Returns
// abs(U.x – V.x) <= Epsilon.x &&
// abs(U.y – V.y) <= Epsilon.y &&
// abs(U.z – V.z) <= Epsilon.z
XMFINLINE bool XM_CALLCONV XMVector3NearEqual(
	FXMVECTOR U,
	FXMVECTOR V,
	FXMVECTOR Epsilon);


7 總結

  1. 向量用來模擬在物理學中同時具備方向和長度的量;在幾何學上,咱們使用一個具備方向的線段來表示向量。當一個向量平行移動到使其尾部和座標系原點重合的時候,它就是一個標準向量,標準向量可使用它頭部座標來表示;
  2. 向量的基本運算公式:
    這裏寫圖片描述
  3. 當進行向量運算的時候,咱們使用XMVECTOR類型來進行高效的SIMD操做;對於類的成員變量,咱們使用XMFLOAT2,XMFLOAT3,和XMFLOAT4類型;而後使用Loading和Storage方法來進行它們之間的轉化;
  4. 出於對效率考慮,XMVECTOR類型的數據能夠做爲函數參數直接傳到SSE/SSE2寄存器中,爲了獨立於平臺,咱們使用FXMVECTOR,GXMVECTOR,HXMVECTOR 和 CXMVECTOR類型來傳遞XMVECTOR參數。傳遞的規則爲:前三個參數使用FXMVECTOR類型,第四個參數使用GXMVECTOR類型,第五個和第六個參數使用HXMVECTOR類型,其它的使用CXMVECTOR類型;
  5. XMVECTOR類重載了算術運算符來計算加減和標量乘法;DirectX Math庫還提供了不少有用的函數來計算向量的長度,向量長度的平方,向量的點積和叉積,向量標準化:
XMVECTOR XM_CALLCONV XMVector3Length(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVector3LengthSq(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVector3Dot(FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV XMVector3Cross(FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV XMVector3Normalize(FXMVECTOR V);


8 練習題

  1. 令u = (1, 2)、v = (3, −4),計算下面的值:
    這裏寫圖片描述
  2. 令u = (−1, 3, 2)、v = (3, −4, 1),計算下面的值:
    這裏寫圖片描述
  3. 假設u = (ux, uy, uz),v = (vx, vy, vz)和w = (wx, wy, wz)。而且c和k是標量,證實下面的等式:
    這裏寫圖片描述
  4. 解方程2((1, 2, 3) – x) − (−2, 0, 4) = −2(1, 2, 3):
    這裏寫圖片描述
  5. 令u = (−1, 3, 2)、v = (3, −4, 1),標準化u和v:
    這裏寫圖片描述
  6. 令k是一個標量,u = (ux, uy, uz),證實||ku|| = |k|||u||:
    這裏寫圖片描述
  7. 求u和v的夾角是垂直、銳角仍是鈍角:
    這裏寫圖片描述
  8. 令u = (−1, 3, 2)和v = (3, −4, 1),求u和v的夾角:
    這裏寫圖片描述
  9. 令u = (ux, uy, uz)、v = (vx, vy, vz)和w = (wx, wy, wz),c和是標量,證實下面等式:
    這裏寫圖片描述
  10. 利用餘弦法則(c2c^2c2 = a2a^2a2 + b2b^2b2 – 2abcosθ)證實uxvx+uyvy+uzvz=∣∣u∣∣∣∣v∣∣cosθu_xv_x + u_yv_y + u_zv_z = ||u||||v|| cosθuxvx+uyvy+uzvz=uvcosθ
    這裏寫圖片描述
  11. 令n = (−2, 1),將g = (0, −9.8)分解爲2個向量,其中一個平行於n,另外一個垂直於n:
    這裏寫圖片描述
  12. 令u = (−2, 1, 4)、v = (3, −4, 1),求w = u × v,而且證實w · u = 0和w · v = 0:
    這裏寫圖片描述
  13. 3個點A = (0, 0, 0),B = (0, 1, 3)和C = (5, 1, 0)在某座標系中定義了一個三角形,找到一個垂直於該三角形的向量:
    這裏寫圖片描述
  14. 證實∣∣∣u×v∣∣=∣∣u∣∣∣∣v∣∣sinθ|||u\times v|| = ||u||||v||sinθu×v=uvsinθ
    這個沒有想到太好的辦法,就是把sinθ轉化爲1−cos2θ\sqrt{1-cos^2θ}1cos2θ,而後所有展開爲uvxyzuv_{xyz}uvxyz,由於這種暴力破解法展開後等式會變得很是長,我懶得展了,看起來是同樣哈哈。
  15. 證實||u ×v||的值是由u和v組成的平行四邊形的面積:
    這裏寫圖片描述
    這裏寫圖片描述
  16. 給出一組3D向量u,v和w,證實u×(v×w)!=(u×v)×wu \times (v \times w) != (u \times v) \times wu×(v×w)!=(u×v)×w,這個表明了叉積不符合組合率:
    這裏寫圖片描述
  17. 證實兩個非0平行向量的叉積是0:u × ku = 0。
    這裏寫圖片描述
  18. 使用施密特正交化方法,標準正交化下列向量:{(1, 0, 0), (1, 5, 0), (2, 1, −4)}
    這裏寫圖片描述
  19. 根據下列代碼的輸出,猜想每一個XMVector*函數的做用,而後查閱DirectXMath的文檔:
    https://docs.microsoft.com/zh-cn/windows/desktop/dxmath/ovw-xnamath-reference-functions-vector
#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>

using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

// Overload the "<<" operators so that we can use cout to
// output XMVECTOR objects.
ostream& XM_CALLCONV operator<<(ostream& os, FXMVECTOR v)
{
	XMFLOAT4 dest;
	XMStoreFloat4(&dest, v);
	os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ", " << dest.w << ")";
	return os;
}

int main()
{
	cout.setf(ios_base::boolalpha);
	
	// Check support for SSE2 (Pentium4, AMD K8, and above).
	if (!XMVerifyCPUSupport())
	{
		cout << "directx math not supported" << endl;
		return 0;
	}
	
	XMVECTOR p = XMVectorSet(2.0f, 2.0f, 1.0f, 0.0f);
	XMVECTOR q = XMVectorSet(2.0f, -0.5f, 0.5f, 0.1f);
	XMVECTOR u = XMVectorSet(1.0f, 2.0f, 4.0f, 8.0f);
	XMVECTOR v = XMVectorSet(-2.0f, 1.0f, -3.0f, 2.5f);
	XMVECTOR w = XMVectorSet(0.0f, XM_PIDIV4, XM_PIDIV2, XM_PI);

	cout << "XMVectorAbs(v) = " << XMVectorAbs(v) << endl;			// 對每個份量求絕對值
	cout << "XMVectorCos(w) = " << XMVectorCos(w) << endl;			// 對每個份量求arccosine值
	cout << "XMVectorLog(u) = " << XMVectorLog(u) << endl;			// 對每個份量求log2的值
	cout << "XMVectorExp(p) = " << XMVectorExp(p) << endl;			// 對每個份量乘2的值
	cout << "XMVectorPow(u, p) = " << XMVectorPow(u, p) << endl;	// 對u的每個份量作p對應份量的平方
	cout << "XMVectorSqrt(u) = " << XMVectorSqrt(u) << endl;		// 對每個份量開平方
	cout << "XMVectorSwizzle(u, 2, 2, 1, 3) = " << XMVectorSwizzle(u, 2, 2, 1, 3) << endl;	// XMVectorSwizzle 沒看懂 - -!
	cout << "XMVectorSwizzle(u, 2, 1, 0, 3) = " << XMVectorSwizzle(u, 2, 1, 0, 3) << endl;	// XMVectorSwizzle 沒看懂 - -!
	cout << "XMVectorMultiply(u, v) = " << XMVectorMultiply(u, v) << endl;	// 對每個對應份量相乘
	cout << "XMVectorSaturate(q) = " << XMVectorSaturate(q) << endl;	// 把每個份量修改成0~1的值
	cout << "XMVectorMin(p, v = " << XMVectorMin(p, v) << endl;			// 對每個份量求 最小/最大 的值
	cout << "XMVectorMax(p, v) = " << XMVectorMax(p, v) << endl;		// 對每個份量求 最小/最大 的值

	system("pause");
	
	return 0;
}

這裏寫圖片描述

相關文章
相關標籤/搜索