0%

DirectX 12 龙书读书笔记(1)

前三章主要是一些数学概念和定理,主要记录了一下名词与基本概念。

向量

向量 (Vector)兼具大小(或称,magnitude)和方向。

向量与坐标系

当一个向量的尾部位于原点时,成该向量位于 标准位置(standard position)。

书中术语“框架”(frame)、“参考系”(frame of reference)、“空间”(space)和“坐标系”(coordinate system)表示相同的意义。

左手坐标系与右手坐标系

Direct3D 采用的是左手坐标系。

长度和单位向量

把一个向量的长度变为单位长度成为向量的 规范化(normalizing)处理。规范化的实现方法:

u^=uu=(xu,yu,zu)\widehat{\boldsymbol{u}}=\frac{\boldsymbol{u}}{\left\|\boldsymbol{u}\right\|}=\left(\frac{x}{\left\|\boldsymbol{u}\right\|},\frac{y}{\left\|\boldsymbol{u}\right\|},\frac{z}{\left\|\boldsymbol{u}\right\|}\right)

点积

点积 (dot product,又称数量积或内积),有时也称为 标量积(scalar product)。点积的定义:

uv=uxvx+uyvy+uzvz\boldsymbol{u}\cdot\boldsymbol{v}=u_xv_x+u_yv_y+u_zv_z

余弦定理(law of cosines):

uv=uvcosθ\boldsymbol{u}\cdot\boldsymbol{v}=\left\|\boldsymbol{u}\right\|\left\|\boldsymbol{v}\right\|\cos\theta

如果 uv=0\boldsymbol{u}\cdot\boldsymbol{v}=0,称两个向量 正交(orthogonal)或垂直(perpendicular)。

向量 v\boldsymbol{v} 落在向量 n\boldsymbol{n} 上的 正交投影 (orthogonal projection)p\boldsymbol{p} 通常表示为:

p=projn(v)\boldsymbol{p}=proj_{\boldsymbol{n}}(\boldsymbol{v})

通过把向量 n\boldsymbol{n} 替换为单位向量 nn\frac{\boldsymbol{n}}{\left\|\boldsymbol{n}\right\|} 可得到一般性的投影公式:

p=projn(v)=(vnn)nn=(vn)n2n\boldsymbol{p}=proj_{\boldsymbol{n}}(\boldsymbol{v})=\left(\boldsymbol{v}\cdot\frac{\boldsymbol{n}}{\left\|\boldsymbol{n}\right\|}\right)\frac{\boldsymbol{n}}{\left\|\boldsymbol{n}\right\|}=\frac{(\boldsymbol{v}\cdot\boldsymbol{n})}{\left\|\boldsymbol{n}\right\|^2}\boldsymbol{n}

正交化

如果向量集 {v0,,vn1}\{\boldsymbol{v_0},\ldots,\boldsymbol{v_{n-1}}\} 中任意向量两两正交且皆具单位长度,成此集合是 规范正交(orthonormal)的。

规范正交集有时由于处理过程中数值精度的问题,会逐步变成非规范正交集。这是需要正交化。

设有向量集{v0,v1,v2}\{\boldsymbol{v_0},\boldsymbol{v_1},\boldsymbol{v_2}\},希望将它正交化为正交集{w0,w1,w2}\{\boldsymbol{w_0},\boldsymbol{w_1},\boldsymbol{w_2}\},则令:

w0=v0w1=v1projw0(v1)w2=v2projw0(v2)projw1(v2)\begin{aligned} \boldsymbol{w_0}&=\boldsymbol{v_0}\\ \boldsymbol{w_1}&=\boldsymbol{v_1}-proj_{\boldsymbol{w_0}}(\boldsymbol{v_1}) \\ \boldsymbol{w_2}&=\boldsymbol{v_2}-proj_{\boldsymbol{w_0}}(\boldsymbol{v_2})-proj_{\boldsymbol{w_1}}(\boldsymbol{v_2}) \end{aligned}

对于 nn 个向量的一般集合 {v0,,vn1}\{\boldsymbol{v_0},\ldots,\boldsymbol{v_{n-1}}\},为将其正交化为规范正交集{w0,,wn1}\{\boldsymbol{w_0},\ldots,\boldsymbol{w_{n-1}}\},则使用 格拉姆 - 施密特正交化 方法:

基本步骤:设w0=v0w_0=v0

对于1in11\le i\le n-1,令wi=vij=0i1projwj(vi)\boldsymbol{w_i}=\boldsymbol{v_i}-\sum_{j=0}^{i-1}proj_{\boldsymbol{w_j}}(\boldsymbol{v_i})

规范化步骤:令wi=wiwi\boldsymbol{w_i}=\frac{\boldsymbol{w_i}}{\left\|\boldsymbol{w_i}\right\|}

叉积

乘法的第二种形式是 叉积(cross product,亦称向量积、外积)。如果u=(ux,uy,uz)\boldsymbol{u}=(u_x,u_y,u_z)v=(vx,vy,vz)\boldsymbol{v}=(v_x,v_y,v_z),那么叉积的计算方法为:

w=u×v=(uyvZuzvy,uzvxuxvz,uxvyuyvx)\boldsymbol{w}=\boldsymbol{u}\times\boldsymbol{v}=(u_yv_Z-u_zv_y,u_zv_x-u_xv_z,u_xv_y-u_yv_x)

如果伸出左手,大拇指伸出,向量 u\boldsymbol{u} 按四指旋转方向旋转到向量 v\boldsymbol{v} 时,且小于等于 180°,则向量 w\boldsymbol{w} 沿着大拇指方向,反之则与大拇指反方向。这就是 左手拇指法则(left-hand-thumb rule),也称左手定则。

叉积具有反交换律:

u×v=v×u\boldsymbol{u}\times\boldsymbol{v}=-\boldsymbol{v}\times\boldsymbol{u}

使用叉积处理将近乎规范正交的向量集 {v0,v1,v2}\{\boldsymbol{v_0},\boldsymbol{v_1},\boldsymbol{v_2}\} 完全正交化:

  1. w0=v0v0\boldsymbol{w_0}=\frac{\boldsymbol{v_0}}{\left\|\boldsymbol{v_0}\right\|}
  2. w2=w0×v1w0×v1\boldsymbol{w_2}=\frac{\boldsymbol{w_0}\times\boldsymbol{v_1}}{\left\|\boldsymbol{w_0}\times\boldsymbol{v_1}\right\|}
  3. w1=w2×w0\boldsymbol{w_1}=\boldsymbol{w_2}\times\boldsymbol{w_0}

此时向量积 {w0,w1,w2}\{\boldsymbol{w_0},\boldsymbol{w_1},\boldsymbol{w_2}\} 是规范正交的。

DirectXMath 库

该数学库采用 SSE2 指令集,借助 128 位宽的 SIMD 寄存器,利用一条指令即可同时对 4 个 32 位浮点数或整数进行运算。

向量类型

DirectXMath 中核心的向量类型是XMVECTOR。开启 SSE2 后,该类型在 x86 和 x64 的定义是:

1
typedef __m128 XMVECTOR;

类中的成员变量,建议分别使用 XMFLOAT2(2D 向量)、XMFLOAT3(3D 向量)和XMFLOAT4(4D 向量)来加以代替。转化过程可通过库的加载函数(loading function)实现。相反地,库提供了将XMVECTOR 类型转换为 XMFLOATn 类型地存储函数(storage function)。

总结:

  1. 局部变量或全局变量使用 XMVECTOR 类型。
  2. 对于类中的数据成员,使用 XMFLOAT2XMFLOAT3)和XMFLOAT4 类型。
  3. 运算前通过加载函数将 XMFLOATn 转换为XMVECTOR
  4. XMVECTOR 实例进行运算。
  5. 通过存储函数将 XMVECTOR 转换为XMFLOATn

加载方法和存储方法

1
2
3
4
// 加载
XMVECTOR XM_CALLCONV XMLoadFloat3 (const XMFLOAT3 *pSource);
// 存储
void XM_CALLCONV XMStoreFloat3 (XMFLOAT3 *pDestination, FXMVECTOR V);

参数的传递

可将 XMVECTOR 的值作为函数的参数,直接传送至 SSE/SSE2 寄存器,而不是栈内。

一定要把约定注解 XM_CALLCONV 加在函数名前,它会根据编译器版本确定对应的调用约定属性。构造函数除外。

传递 XMVECTOR 参数的规定如下:

  1. 前 3 个参数用类型FXMVECTOR
  2. 第 4 个参数用类型GXMVECTOR
  3. 第 5、6 个参数用类型HXMVECTOR
  4. 其余参数用CXMVECTOR

32 位 Windows 上的调用约定:

1
2
3
4
5
6
7
8
9
10
11
// __fastcall 将前 3 个 XMVECTOR 参数传递到寄存器中,其余参数存在栈上
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR& GXMVECTOR;
typedef const XMVECTOR& HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;

// __vectorcall 将前 6 个 XMVECTOR 参数传递到寄存器中,其余参数存在栈上
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR GXMVECTOR;
typedef const XMVECTOR HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;

常向量

XMVECTOR类型的常量实例应当用 XMVECTORF32 类型表示。例:

1
static const XMVECTORF32 g_vHalfVector = {0.5f, 0.5f, 0.5f, 0.5f};

重载运算符

XMVECTOR类型针对加法、剑法、标量乘法提供了对应的重载运算符。

杂项

库中定义了一组与 π\pi 有关的常量XM_PIXM_2PIXM_1DIVPIXM_1DIV2PIXM_PIDIV2XM_PIDIV4

内联函数 XMConvertTORadiansXMConvertTODegrees 实现了弧度与角度的相互转化。

Setter

1
2
3
4
5
6
7
8
// 零向量
XMVECTOR XM_CALLCONV XMVectorZero ();
// 向量(1, 1, 1, 1)
XMVECTOR XM_CALLCONV XMVectorSplatOne ();
// 向量(x, y, z, w)
XMVECTOR XM_CALLCONV XMVectorSet (float x, float y, float z, float w);
// 向量(Value, Value, Value, Value)
XMVECTOR XM_CALLCONV XMVectorReplicate (float Value);

向量函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ||v||
XMVECTOR XM_CALLCONV XMVector3Length (FXMVECTOR V);
// ||v||^2
XMVECTOR XM_CALLCONV XMVector3LengthSq (FXMVECTOR V);
// v1 · v2
XMVECTOR XM_CALLCONV XMVector3Dot (FXMVECTOR V1, FXMVECTOR V2);
// v1 x v2
XMVECTOR XM_CALLCONV XMVector3Cross (FXMVECTOR V1, FXMVECTOR V2);
// v/||v||
XMVECTOR XM_CALLCONV XMVector3Normalize (FXMVECTOR V);
// 正交 v 的向量
XMVECTOR XM_CALLCONV XMVector3Orthogonal (FXMVECTOR V);
// v1 和 v2 间的夹角
XMVECTOR XM_CALLCONV XMVector3AngleBetweenVectors (FXMVECTOR V1, FXMVECTOR V2);
// proj_n(v) 和 perp_n(v)
XMVECTOR XM_CALLCONV XMVector3ComponentsFromNormal (XMVECTOR* pParallel, XMVECTOR* Perpendicular, FXMVECTOR V1, FXMVECTOR V2);
// v1==v2?
bool XM_CALLCONV XMVector3Equal (FXMVECTOR V1, FXMVECTOR V2);
// v1!=v2?
bool XM_CALLCONV XMVector3NotEqual (FXMVECTOR V1, FXMVECTOR V2);

浮点数误差

比较浮点数时,需要定义一个 Epsilon 常量,作为针对浮点数的误差问题的容差(tolerance)。

对此,库提供了 XMVector3NearEqual 函数:

1
XMFINLINE bool XM_CALLCONV XMVector3NearEqual (FXMVECTOR U, FXMVECTOR V, FXMVECTOR Epsilon);