学习内容
- D3D的数学基础,诸如向量、点、矩阵、仿射变换与坐标变换
- D3D提供的数学函数的使用
内容
注意,所有的数学运算函数皆为DirectXMath库的内容,务必包含DirectXMath库
关于浮点类型误差
由于在计算机上表示一个浮点数并进行运算总是存在着误差,随运算次数增多,这些误差将会变得越来越大
解决方案
-
从运算顺序上:先对小值之间进行运算,再将大值之间进行运算,也就是相互进行运算的数值不建议相差过大
-
从数据本身上设置缓冲:设定一个ε常量,当两个数之间的差值小于这个数值时,将这两个数视为相等,D3D提供了这样的函数
XMFINLINE bool XM_CALLCONV XMVector3NearEqual(FXMVECTOR U,FXMVECTOR V,FXMVECTOR Epsilon)
参数与D3D数据结构
向量类型
XMVECTOR与XMFLOATn:
按16字节对齐,被映射到SIMD硬件寄存器,能够更加快捷地处理浮点数据,SIMD指令与_m128类型(开启SSE2后的一种128位的类型)配合使用,可以一次性处理4个32位浮点型数据,D3D提供了几种向量类型以在非局部变量和全局变量那样能够自动实现16字节对齐的时候(比如在类中的数据成员)使用:**XMFLOAT2(**2D向量)、**XMFLOAT3(**3D向量)、XMFLOAT4(4D向量)
XMVECTOR与XMFLOATn的相互转化:
使用从XMFLOATn转化到XMVECTOR的函数使其能够通过SIMD技术加速运算
XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2 *pSource);
把2改成其他数字就可以使用把其他类型的转换成XMVECTOR
将XMVECTOR转化为XMFLOATn,储存在指针所指空间
void XM_CALLCONV XMStoreFloat2(XMFLOAT2 *pDestination,FXMVECTOR V);
取得某个分量或者将某个分量转换为XMVECTOR类型:
float XM_CALLCONV XMVectorGetX(FXMVECTOR V);//把这里面的X换成Y、Z、W就可以获得其他的坐标值
XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V,float x)//同理,换成其他符号使用其他函数
参数
为了提高效率,可以将几个XMVECTOR类型的值直接送到SSE/SSE2寄存器而不存储于栈中,这个传送的数量取决于用户使用的平台和编译器,为了较大限度的避免平台与编译器差异造成的问题,使用FXMVECTOR,GXMVECTOR,HXMVECTOR,CMXVECTOR来传递XMVECTOR类型的值,基于特定的平台与编译器,它们会被自动定义为合适的类型,此外为了让这些类型随编译器版本确定合适的调用约定属性,需要在函数名前加上调用约定注解XM_CALLCONV
传参规则:
-
前三个XMVECTOR参数应当使用类型FXMVECTOR
-
第四个使用GXMVECTOR
-
第五、六个使用HXMVECTOR
-
其他的使用CMXVECTOR
对于构造函数来说,前三个使用FXMVECTOR而其余的用CXMVECTOR且不能使用XM_CALLCONV注解
这些规则仅适用于传入参数,传出参数不适用(因为传出不占用SSE/SSE2寄存器)
向量
特点:
具有大小和方向,是图像中的一条有向线段
净方向:即合方向
表示方法:
-
在数学中,一个传统的表示方式就是建立一个空间直角坐标系,使用三个浮点数表示一个三维向量
-
对于同一个向量在不同的坐标系中有不同的坐标表示,向量不随参考系的变化而变化,而“位置向量”即“点”会随参考系的变化而变化
-
在DirectX3D中,使用左手坐标系(即Z轴向屏幕里侧,X轴向右手边,Y轴向上边),左手系与右手系的Z轴方向相反
在D3D中,提供了几种不同的向量:
- 4D向量,这个一般用于三维图形变换,最后一个值是用来区分向量和点的,向量的w=0,点的w=1
运算
求模:
即取一个向量的长度,可以根据毕达哥拉斯定理(即勾股定理)算得,也可以使用D3D给出的函数:
XMVECTOR XM_CALLCONV XMVector3Length(FXMVECTOR V);//求模
XMVECTOR XM_CALLCONV XMVector3LengthSq(FXMVECTOR V);//求模的平方
将3换成2或者4就可以进行二维或四维向量的运算,以下所有的数学函数同理
单位化(规范化、标准化等同义):
即一个向量除以它的模长,得出一个长度为单位长度的向量,可以使用D3D给出的函数:
XMVECTOR XM_CALLCONV XMVector3Normalize(FXMVECTOR V);
正交化:
正交投影:一个向量向某个面作垂线1,并将垂足与原点形成的新的投影向量向两个轴作垂线2、3,将垂线1平移至轴,一端与原点重合,从原点向线段末端的这个向量即在这个轴上的正交投影,原点向垂线2的垂足的向量是在这个轴上的正交投影,垂线3同理
对于向量v向向量n作正交投影也类似,先使n单位化,使n成为一个轴,向这个轴上作投影,v与n原点相同,投影出来的这个向量就是v在n上的正交投影,记作projn(v),(其实就是在n上投影得到的向量),可以使用D3D给出的函数得到某个向量的正交向量和正交投影
XMVECTOR XM_CALLCONV XMVector3Orthogonal(FXMVECTOR v);//得到v的正交向量
void XM_CALLCONV XMVector3ComponentsFromNormal(XMVECTOR* pParallel,//返回与n平行的轴上的投影projn(v)
XMVECTOR* pPerpendicular,//返回与n垂直的轴上的投影perpn(v)
FXMVECTOR v,//向量v
FXMVECTOR Normal);//规范化向量n
规范正交:一个向量集中每个向量都彼此相互正交且都是单位向量,那么这个集合就是规范正交的集合。
有一个近似规范正交的集合,可以通过正交化与规范化使其成为规范正交集合,正交化的一个重要用途在于使由于在多次运算过程中因无法避免的精度问题而变得不再规范正交的集合重新变为规范正交集合
正交化:正交化的操作其实就是使一个向量减去它在另一个向量上的投影(相当于是让其中一个作为轴了),三维向量的操作更加复杂一些,但也就只是遵循类似的规律:想要求得的轴上的正交向量=原向量-在其他一个轴(已求出的相互正交的向量之一)上的投影向量-在其他另一个轴(已求出的相互正交的向量另外之一)上的投影向量
对于有n个向量的一般集合而言,可以使用格拉姆—施密特正交化方法处理
加(减)法:
同维向量的对应分量相加(减),XMVECTOR类型对于向量的基础运算作了重载,因此向量可以使用以下运算符实现加(减)法:
+
+=
-
-=
乘法:
-
标量乘法:是一个标量(即没有方向的数值)与一个向量的乘法,使用这个标量与向量的各个分量进行乘法运算,得到的结果仍然是一个向量,XMVECTOR类型支持重载乘法,可以使用以下运算符实现标量乘法
* *= / 支持除法 /= 支持除法
-
点积:也称作内积或者数量积,是向量与向量的一种乘法,是两个向量的对应分量分别相乘再相加,也是两个向量的模的乘积再乘它们夹角(一般都指小于180°的角)的余弦值,结果是一个标量,正交向量之间的点积为0,夹角为锐角的向量之间点积大于零,夹角为钝角的向量之间的点积小于零,不仅XMVECTOR支持的重载运算符可以进行点积运算,D3D提供的数学函数也可以
* *= / /= XMVECTOR XM_CALLCONV XMVector3Dot(FXMVECTOR V1,FXMVECTOR V2); XMVECTOR XM_CALLCONV XMVector3AngleBetweenVectors(FXMVECTOR V1,FXMVECTOR V2);//这个是求两向量夹角的
-
叉积:也称外积、向量积,叉积的计算结果仍为向量,2D向量无叉积,对于向量u×v,新x坐标=u的y坐标乘v的z坐标-u的z坐标乘v的y坐标,新y坐标=u的z坐标乘v的x坐标-u的x坐标乘v的z坐标,新z坐标=u的x坐标乘v的y坐标-u的y坐标乘v的x坐标,两个三维向量的叉积的结果是正交于这两个向量的一个向量,对于左手坐标系,遵循左手(拇指)定则,向量的叉积满足反交换律,即u×v=-v×u,可以利用叉积进行正交化,可以使用D3D提供的数学函数
XMVECTOR XM_CALLCONV XMVector3Cross(FXMVECTOR V1,FXMVECTOR V2);
其他函数
bool XM_CALLCONV XMVector3Equal(FXMVECTOR V1,FXMVECTOR V2);//判断两向量是否相等
bool XM_CALLCONV XMVector3NotEqual(FXMVECTOR V1,FXMVECTOR V2);//判断两向量是否不相等
杂项
在DirectXMath库中定义了一些关于pi的常用数据近似值
const float XM_PI=3.141592654f;
const float XM_2PI=6.283185307f;
const float XM_1DIVPI=0.318309886f;
const float XM_2DIVPI=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 XMConvertToRadians(float fRadians){
return fRadians*(180.0f/XM_PI);
}
比较两个值的大小
template<class T> inline T XMMin(T a,T b){return (a<b)?a:b;}
template<class T> inline T XMMin(T a,T b){return (a>b)?a:b;}
Setter函数
XMVECTOR XM_CALLCONV XMVectorZero();//返回零向量
XMVECTOR XM_CALLCONV XMVectorSplatOne();//返回(1,1,1,1)
XMVECTOR XM_CALLCONV XMVectorSet(float x,float y,float z,float w);//返回(x,y,z,w)
XMVECTOR XM_CALLCONV XMVectorReplicate(float Value);//返回四个分量都是Value的
XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V);//返回四个分量都是v中的x坐标值的,下面两个分别是都是y的和都是z坐标值的
XMVECTOR XM_CALLCONV XMVectorSplatY(FXMVECTOR V);
XMVECTOR XM_CALLCONV XMVectorSplatZ(FXMVECTOR V);
点
一个没有方向的向量,也被称为位置向量,只有一部分关于向量的运算对于点有意义
常向量
应当使用XMVECTOR32类型表示,这种类型是以16位对齐的结构体
转换为XMVECTOR类型:
XMVECTOR V=static_cast<XMVECTOR32> M;
这个static_cast能够处理C++中的隐式类型转换
矩阵
矩阵在计算机中常用二维数组来表示,矩阵的行数和列数相乘就是它的维度,在某些情况下,将矩阵的每一行看作一个向量
在32位Windows操作系统上,由于SSE/SSE2寄存器智能存入三个XMVECTOR,因此矩阵只能通过堆栈引用,其他平台见DirectXMath文档中的“Library Internals”下的“Conventions”部分
矩阵的传参
矩阵的传参与向量类似,但矩阵由4个XMVECTOR组成,因此根据_fastcall调用约定,前三个传入寄存器,第四个进入堆栈,根据_vector调用约定,前6个进入寄存器
矩阵的初始化
D3D中矩阵类型XMMATRIX是4×4的矩阵,初始化XMMATRIX类要使用它的构造函数
-
可以指定4个行向量初始化矩阵
XMMATRIX(FXMVECTOR R0,FXMVECTOR R1,FXMVECTOR R2,CXMVECTOR R3);
-
指定16个元素初始化矩阵
XMMATRIX(float m00,float m01,float m02,float m03, float m10,float m11,float m12,float m13, float m20,float m21,float m22,float m23, float m30,float m31,float m32,float m33, )
-
通过含有16个浮点数的数组初始化
explicit XMMATRIX(_In_reads_(16) const float *pArray);
-
使用XMMatrixSet创建实例
XMMATRIX XM_CALLCONV XMMtrixSet( float m00,float m01,float m02,float m03, float m10,float m11,float m12,float m13, float m20,float m21,float m22,float m23, float m30,float m31,float m32,float m33, )
XMMATRIX和XMFLOAT4X4的转换
XMFLOAT4X4是用来存储矩阵的,从XMMATRIX转换到XMFLOAT4X4:
inline void XM_CALLCONV XMStoreFloat4x4(XMFLOAT4X4* pSource,FXMMATRIX M);
XMMATRIX是用来运算的,从XMFLOAT4X4转换到XMMATRIX:
inline XMMATRIX XM_CALLCONV XMLoadFloat4x4(const XMFLOAT4X4* pSource)
运算
矩阵的运算满足加法交换律,加法结合律,标量乘法对矩阵加法的分配律,矩阵乘法对标量加法的分配律
矩阵的加(减)法
两矩阵相加,对应元素相加,这连个矩阵必然有相同的行数和列数
矩阵的标量乘法
使标量与每个元素相乘,也可以理解为标量与每行的向量相乘
矩阵乘法
m×n矩阵与n×p矩阵相乘得到m×p矩阵,结果矩阵C的第i行第j列元素是矩阵A的第i个行向量与矩阵B的第j个列向量相点积所得,矩阵A的列数必与矩阵B的行数相等,矩阵乘法不满足交换律但满足结合律与矩阵乘法与矩阵加法的分配律
XMMATRIX XM_CALLCONV XMMatrixMultiply(FXMMATRIX A,CXMMATRIX B);
转置矩阵
将原矩阵行与列互换后得到的新矩阵M的转置矩阵记作MT,返回矩阵M的转置矩阵
XMMATRIX XM_CALLCONV XMMatrixTranspose(FXMMATRIX M);
单位矩阵
单位矩阵是主对角线(从左上到右下)元素均为1其余为0的方阵
检测是否为单位矩阵
bool XM_CALLCONV XMMatrixIsIdentity(FXMMATRIX M);
返回一个单位矩阵
XMMATRIX XM_CALLCONV XMMatrixIdentity();
矩阵的行列式
对于三维及以上物体,行列式反映了在线性变换下n维多面体的体积变化的相关信息,对于二维图形来说,反映了平行四边形有向面积的变化相关信息,此外,当且仅当一个方阵的行列式不为0,这个方阵可逆
余子式
余子式是方阵去除某行某列的方阵
行列式的递归定义是:detA=Σj=1A1j(-1)1+jdetA1j(最后这个是余子式,上面的横杠打不出来),在3D图形学中主要使用4×4矩阵
返回一个向量,四个分量都是行列式值
VMVECTOR XM_CALLCONV XMMatrixDetermination(FXMMATRIX M);
(-1)i+jdetAij(这个是余子式),这是元素Aij的代数余子式,代数余子式矩阵就是将每个元素的代数余子式算出来得出的矩阵,这个矩阵的转置矩阵就是矩阵A的伴随矩阵A*
逆矩阵
存在逆矩阵的矩阵称为可逆矩阵,不存在逆矩阵的称为奇异矩阵,矩阵与其逆矩阵相乘得单位方阵
M的逆矩阵记作M-1,逆矩阵、伴随矩阵、行列式的关系:A-1=A*/detA这个公式很少用
输入四个分量都是行列式的向量,矩阵,返回逆矩阵
XMMATRIX XM_CALLCONV XMMatrixInverse(XMVECTOR* pSource,FXMMATRIX M);
变换
线性变换
即一个标量乘一个向量类似形式的
矩阵表示法
将一个线性变换u=tv改写成u=tai+tbj+tck,再改写成矩阵形式[a,b,c]A(这是一个3×3矩阵,可以看作三个行向量分别为t(i),t(j),t(k))其中i、j、k分别为三个标准基向量,称A是线性变换t的矩阵表示法
缩放
指的是改变物体的大小,将缩放变换定义为S(x,y,z)=(sxx,syy,szz)
变换矩阵的每一个行向量:
S(i)=(sx,0,0)
S(j)=(0,sy,0)
S(k)=(0,0,sz)
其中sxsysz都是在相应的轴上进行缩放的倍数,扩展到四维向量,第四个值应该全为1
关于缩放的函数
XMMATRIX XM_CALLCONV XMMatrixScalling(float ScaleX,
float ScaleY,
float ScaleZ);
XMMATRIX XM_CALLCONV XMMatrixScalingFromVector(FXMVECTOR Scale);
旋转
原理上是让向量垂直于旋转轴的部分进行旋转(因为向量平行部分旋转时不变)
这里不作推导,可以见《DirectX12 3D游戏开发实战》p.56
绕x轴旋转:
Rx:
第一行:1 0 0
第二行:0 cosθ sinθ
第三行:0 -sinθ cosθ
第四行:0 0 0
XMMATRIX XM_CALLCONV XMMatrixRotationX(float Angle);//顺时针弧度θ
绕y轴旋转:
Ry:
第一行:cosθ 0 -sinθ
第二行:0 1 0
第三行:sinθ 0 cosθ
第四行:0 0 0
XMMATRIX XM_CALLCONV XMMatrixRotationY(float Angle);//顺时针弧度θ
绕z轴旋转:
Rz:
第一行:cosθ sinθ 0
第二行:-sinθ cosθ 0
第三行:0 0 1
第四行:0 0 0
XMMATRIX XM_CALLCONV XMMatrixRotationZ(float Angle);//顺时针弧度θ
绕任意轴旋转:
XMMATRIX XM_CALLCONV XMMatrixRotationAxis(FXMVECTOR Axis,float Angle);//轴Axis,沿轴看顺时针旋转θ
仿射变换
齐次坐标
由于对向量的平移操作无意义,因而引入齐次坐标这一概念,齐次坐标中每个三维向量或点用四个分量表示,前三个描述位置,后一个描述是点还是向量,(x,y,z,0)表示向量,(x,y,z,1)表示点
仿射变换的定义及坐标表示
仿射变换就是线性变换再加上一个平移量b
矩阵表示就是:
[x,y,z]A+[bx,by,bz]
扩充为齐次坐标,可写作:
[x,y,z,1]A=[x’,y’,z’,1]
A为:
A11 A12 A13 0
A21 A22 A23 0
A31 A32 A33 0
bx by bz 1
平移的矩阵表示:
1 0 0 0
0 1 0 0
0 0 1 0
bx by bz 1
仅对一些点进行仿射变换即可实现物体的变形
平移相关函数:
XMMATRIX XM_CALLCONV XMMatrixTranslation(float OffsetX,
float OffsetY,
float OffsetZ);
XMMATRIX XM_CALLCONV XMMatrixTranslationFromVector(FXMVECTOR Offset);
变换的复合
为了提高运算效率,通常先对矩阵之间进行计算再对向量或点进行运算,当同时对一个点vi进行缩放(S)旋转®平移(T)操作时,按照SRT的顺序先对这几个矩阵进行运算得到变换矩阵C,再将C与点vi运算
坐标变换
坐标变换也称标架变换,是将坐标系1中的点变换为坐标系2的表现形式的操作,操作前后几何体的形状并未发生变化,仅仅是变更了观察的参考系
向量的坐标变换
u,v,w分别为坐标系1的在x,y,z轴上的单位向量相对于坐标系2的向量,向量p在坐标系1中的坐标为(x,y,z),那么变换坐标系后,pB=xu+yv+zw
点的坐标变换
由于位置是点的重要属性,对点进行变换时,使用了相对原点的距离
u,v,w分别为坐标系1的在x,y,z轴上的单位向量相对于坐标系2的向量,点p在坐标系1中的坐标为(x,y,z),变换后相对于原点2的距离为Q,那么变换坐标系后,pB=xu+yv+zw+Q
坐标变换的矩阵表示
使用齐次坐标,变换后的向量pB**=[x,y,z,w]A
A是有四个行向量的矩阵,这四个行向量从上到下依次为u,v,w,Q
u=(ux,uy,uz,0)
v=(vx,vy,vz,0)
w=(wx,wy,wz,0)
Q=(Qx,Qy,Qz,1)
在对向量进行多次坐标变换时,应当先将所有变换矩阵进行运算,最后再对向量进行运算
//计算向量与矩阵的乘积,此函数针对点的变换,默认齐次坐标最后一位为1
XMVECTOR XM_CALLCONV XMVector3TransformCoord(FXMVECTOR V,
CXMMATRIX M);
//计算向量与矩阵的乘积,此函数针对向量的变换,默认齐次坐标最后一位为0
XMVECTOR XM_CALLCONV XMVector3TransformNormal(FXMVECTOR V,
CXMMATRIX M);
引用声明
引用《DirectX12 3D游戏开发实战》[美]弗兰克·D.卢娜(Frank D.Luna) 著,王陈 译,人民邮电出版社,2019年1月第一版