1.1 向量
向量是兼具大小(模)和方向的物理量。具有这种两种属性的都称之为向量值物理量(例如:作用力、位移、速度、方向等)。
1.1.1向量坐标系
向量的表示:
在3D坐标系中,通过平移操作使向量的尾部移动至原点位置,并将记录为v=(x,y,z)
,注:z,y,z
是三个浮点数,如下图所示:
1.1.2 左手坐标系与右手坐标系
Direct3D
当中我们采用的是左手坐标系
左手坐标系:
如果我们伸出左手,并拢手指,假设他们的指向的是x轴的正方向,再弯曲四指指向y轴正方向,最后伸直拇指的方向大约是z轴的正方向,如下图所示。
右手坐标系:
此坐标系中x轴,y轴和z轴的正方向是如下规定的:把右手放在原点的位置,使大拇指,食指和中指互成直角,把大拇指指向x轴的正方向,食指指向y轴的正方向时,中指所指的方向就是z轴的正方向。
1.1.3 向量的基本运算
假设两个坐标 u=(U1,U2,U3)、v=(V1,V2,V3)
4种基本定义:
1 向量相等
两个向量相等,当且仅当他们的对应分量分别相等。及u=v,当且仅当U1=V1、U2=V2、U3=V3。
2 加法运算
向量加法即令两个向量的对应分量分别相加:u+v=(U1+V1,U2+V2,U3+V3),注:只有同维的向量之间才可以进行加法运算
。
3 标量乘法
向量可以与一个标量相乘,所得结果仍然是一个向量。例如:设一个标量为k,那么ku=(kU1,kU2,kU3)。
4 减法运算
向量减法可以通过向量加法和标量乘法表示,及u-v=u+(-1·v)=u+(-v)=(U1-V1,U2-V2,U3-V3) 。
1.2 长度和单位向量
假设现有一个向量 u=(x,y,z)
1.2.1 向量的长度
使用两次毕达哥拉斯定理可以得出:
1.2.2 向量的单位向量
对于某个向量的单位向量=此向量/向量模,如下所示:
1.3 点积
假设两个坐标 u=(U1,U2,U3)、v=(V1,V2,V3)
1.3.1 向量点积的基本概念
概念:点积是一种计算结果为标值量的向量乘法运算,因此有时也称之为标量积。
举例:u·v=U1V1+U2V2+U3*V3
1.3.2 向量点积的几何性质
<1>当u·v=0时,u与v相垂直
<2>当u·v>0时,u与v的夹角小于90
<3>当u·v<0时,u与v相夹角大于90
1.3.2 向量的正交
<1> 正交,正交是线性代数的概念,是垂直这一直观概念的推广。作为一个形容词,只有在一个确定的内积空间中才有意义。若内积空间中两向量的内积为0,则称它们是正交的。如果能够定义向量间的夹角,则正交可以直观的理解为垂直。
<2>正交投影,投影线垂直于投影面的投影,使proj
去表示
<3>正交化一个集合里的向量都与一向量正交,且这个集合的向量都为单位向量,那我们将此集合称为规范正交,而将普通向量集正交化为规范正交集的过程叫做正交化。而这个过程就要使用格拉姆——施密特正交化(Gram-Schmidt Orthogonalization)方法进行处理。
1.4 向量的叉积
1.4.1 叉积
<1>叉积,数学中又称外积、向量积,物理中称矢积、叉乘,是一种在向量空间中向量的二元运算。与点积不同,它的运算结果是一个向量而不是一个标量。并且两个向量的叉积与这两个向量和垂直。其应用也十分广泛,通常应用于物理学光学和计算机图形学中。
<2>如下图所示:
1.5 利用DirectXMath库对向量运算
1.5.1 环境的配置
注:X86平台需要在启用SSE2指令集
1.5.2 部分头文件的引入
#include<DirectXMath.h>//DirectX的数学函数库相关的头文件
#include<DirectXPackedVector.h>//数据类型相关的头文件
1.5.3 向量类型
在DirectXMath库中,核心向量类型是XMVECTOR
(注意:其命名空间为DirectX
),它将会映射到SIMD硬件指令集内,这个是一个128位的类型能一次性处理4个32位的浮点数 。
当开启SSER2后,X86和X64平台对其的定义如下:
using XMVECTOR = __m128;
XMVECTOR
类型需要按16字节对齐,对于局部变量和全局变量来说是自动实现的。至于类中的成员,建议分辨使用XMFLOAT2
、XMFLOAT3
、XMFLOAT4
来实现,他们的结构体定义如下:
XMFLOAT2(2D向量)
// 2D Vector; 32 bit floating point components
struct XMFLOAT2
{
float x;
float y;
XMFLOAT2() = default;
XMFLOAT2(const XMFLOAT2&) = default;
XMFLOAT2& operator=(const XMFLOAT2&) = default;
XMFLOAT2(XMFLOAT2&&) = default;
XMFLOAT2& operator=(XMFLOAT2&&) = default;
constexpr XMFLOAT2(float _x, float _y) noexcept : x(_x), y(_y) {}
explicit XMFLOAT2(_In_reads_(2) const float* pArray) noexcept : x(pArray[0]), y(pArray[1]) {}
};
XMFLOAT3(3D向量)
// 3D Vector; 32 bit floating point components
struct XMFLOAT3
{
float x;
float y;
float z;
XMFLOAT3() = default;
XMFLOAT3(const XMFLOAT3&) = default;
XMFLOAT3& operator=(const XMFLOAT3&) = default;
XMFLOAT3(XMFLOAT3&&) = default;
XMFLOAT3& operator=(XMFLOAT3&&) = default;
constexpr XMFLOAT3(float _x, float _y, float _z) noexcept : x(_x), y(_y), z(_z) {}
explicit XMFLOAT3(_In_reads_(3) const float* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]) {}
};
XMFLOAT4(4D向量)
// 4D Vector; 32 bit floating point components
struct XMFLOAT4
{
float x;
float y;
float z;
float w;
XMFLOAT4() = default;
XMFLOAT4(const XMFLOAT4&) = default;
XMFLOAT4& operator=(const XMFLOAT4&) = default;
XMFLOAT4(XMFLOAT4&&) = default;
XMFLOAT4& operator=(XMFLOAT4&&) = default;
constexpr XMFLOAT4(float _x, float _y, float _z, float _w) noexcept : x(_x), y(_y), z(_z), w(_w) {}
explicit XMFLOAT4(_In_reads_(4) const float* pArray) noexcept : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {}
};
1.5.4 加载和存储方式
<1>加载:
注意此处的n为1,2,3
//将数据从 XMFLOATn 类型加载到 XMVECTOR 类型
XMVECTOR XM_CALLCONV XMLoadFloatn(const XMFLOATn *pSourse);
以XMFLOAT2为例:
#include<DirectXMath.h>
#include<DirectXPackedVector.h>
int main() {
DirectX::XMFLOAT2 b = { 1.1f,1.1f };
DirectX::XMVECTOR a = DirectX::XMLoadFloat2(&b);
return 0;
}
<2>存储:
注意此处的n为1,2,3
//将数据从 XMVECTOR 存储到 XMFLOATn 当中
void XM_CALLCONV XMStoreFloatn(XMFLOATn *pDesttination,FXMVECTOR V);
以XMFLOAT2为例:
#include<DirectXMath.h>
#include<DirectXPackedVector.h>
using namespace DirectX;
int main() {
XMFLOAT2 a;
XMVECTOR b = XMVectorSet(1.0f, 2.0f, 0.0f, 0.0f);
XMStoreFloat2(&a, b);
return 0;
}
<3>补充:单个分量的加载与存储
注意此处的n为X,Y,Z,W
1)加载:
//将数据从 XMVECTOR 存储到 XMFLOATn 当中
float XM_CALLCONV XMVectorGetn(FXMVECTOR V);
举例:
#include<DirectXMath.h>
#include<DirectXPackedVector.h>
using namespace DirectX;
int main() {
XMVECTOR b = XMVectorSet(1.0f, 2.0f, 0.0f, 0.0f);
float a = XMVectorGetX(b);
return 0;
}
2)存储:
XMVECTOR XM_CALLCONV XMVectorSetn(FXMVECTOR V, float n);
举例:
#include<DirectXMath.h>
#include<DirectXPackedVector.h>
using namespace DirectX;
int main() {
XMVECTOR b = XMVectorSet(1.0f, 2.0f, 0.0f, 0.0f);
XMVectorSetX(b, 1.1f);
return 0;
}
1.5.6 参数的传递
<1>
为了提高效率,可以将XMVECTOR类型的值作为函数的参数,直接送至SSE/SSE2寄存器里,而不存于栈内。以此方式传递的参数数量取决于用户使用的平台和编译器。因此为了使代码更具通用性,不受具体平台、编译器的影响,我们将利用FXMVECTOR、GXMVECTOR、HXMECTOR和CXMVECTOR
类型来传递XMVECTOR
类型的参数。基于特定的平台和编译器,它们会被自动地定义为适当的类型。此外,一定吧调用约束XM_CALLCONV
加在函数名之前,它会根据编译器的版本确定出调用的约束属性。
传递XMVECTOR
的规则如下:
前3个XMVECTOR参数的类型都是应当用类型:FXMVECTOR;
第4个XMVECTOR参数应当用类型:GXMVECTOR;
第5、6个XMVECTOR参数应当用类型:HXMVECTOR;
其余的XMVECTOR参数应当用类型:CXMVECTOR;
下方为32位window上的调用约束:
#if _XM_VECTORCALL_
#define XM_CALLCONV __vectorcall
#elif defined(__GNUC__)
#define XM_CALLCONV
#else
#define XM_CALLCONV __fastcall
#endif
<2>
构造函数对于这些规则来说是个例外,在编写时,前三个XMVECTOR参数用FXMVECTOR类型
,其余用CXMVECTOR类型
,另外对于构造函数,不要用XM_CALLCONV注解。
<3>传递XMVECTOR参数规则仅适用于“输入”参数,”输出“的XMVECTOR参数则不会占用寄存器,所以他们的处理方式与非XMVECTOR类型处理方式是一致的
1.6 常向量
XMVECTOR类型的常量实例
应当用XMVECTORF32
类型来表示。例如下方:
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字节对齐的结构体,此外提供了将XMVECTORF32 转换成XMVECTOR类型
的运算符:
// Conversion types for constants
XM_ALIGNED_STRUCT(16) XMVECTORF32
{
union
{
float f[4];
XMVECTOR v;
};
inline operator XMVECTOR() const noexcept { return v; }
inline operator const float* () const noexcept { return f; }
#ifdef _XM_NO_INTRINSICS_
#elif defined(_XM_SSE_INTRINSICS_)
inline operator __m128i() const noexcept { return _mm_castps_si128(v); }
inline operator __m128d() const noexcept { return _mm_castps_pd(v); }
#elif defined(_XM_ARM_NEON_INTRINSICS_) && defined(__GNUC__)
inline operator int32x4_t() const noexcept { return vreinterpretq_s32_f32(v); }
inline operator uint32x4_t() const noexcept { return vreinterpretq_u32_f32(v); }
#endif
};
1.7 重载运算符
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);
1.8 杂项
<1>与π
有关的常用常量近似值
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;
<2>弧度和角度间的互相转化
inline float XMConvertToRadians(float fDegrees)
{ return fDegrees * (XM_PI / 180.0f); }
inline float XMConvertToDegrees(float fRadians)
{ return fRadians * (180.0f / XM_PI); }
<3>俩个数间较大值及较小值的函数
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; }
1.9 setter函数
DirectXMath
库提供了下列函数,以设置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);
1.10 向量函数
DirectXMath
库提供了下面的函数来执行向量的各种运算,类似的有2D、3D、4D,下面的就是3D的部分函数:
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);
XMVECTOR XM_CALLCONV XMVector3Orthogonal(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVector3AngleBetweenVectors(FXMVECTOR V1,FXMVECTOR V2);
void XM_CALLCONV XMVector3ComponentsFromNormal(XMVECTOR* pParallel,XMVECTOR* pPerpendicular,FXMVECTOR V,FXMVECTOR Normal);
bool XM_CALLCONV XMVector3Equal(FXMVECTOR V1,FXMVECTOR V2);
bool XM_CALLCONV XMVector3NotEqual(FXMVECTOR V1,FXMVECTOR V2);
1.11 浮点数误差
为了弥补浮点数精确性上的不足,我们通过比较俩个浮点数是否近似相等来加以解决。在比较的时候,我们需要定义一个Epsilon常量,我们就说这俩个数是近似相等的。换句话说,Epsilon
是针对浮点数的误差问题所指定的容差。龙书当中用来如下例子来解释了如何利用Epsilon
来检测俩个浮点数是否相等:
const float Epsilon = 0.001f;
bool Equals(float lhs, float rhs)
{
//lhs与rhs的相差值是否小于EPSILON?
return fabs(lhs - rhs) < Epsilon ? true : false;
}
对此,DirectXMath
库提供了XMVector3NearEqual
函数,用于以Epsilon
作为容差,测试比较的向量是否相等:
// 返回
// 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);
1.12 附:龙书第一章书后小结
写在最后
参考文献:DirectX12 3D游戏开发实践(龙书)第一章 向量代数