跳过线性代数的数学概念部分。
绝大部分内容来自于《Introduction to 3D Game Programming with DirectX12 Frank D. Luna》
1.编译环境
在Windows 8以及更高版本的操作系统中,DirectX Math 是一个用于 Direct3D 的 3D 数学库,它已经是Windows SDK的一部分了,并且使用了 SSE2 指令集。
SSE2是什么?
http://share.onlinesjtu.com/mod/tab/view.php?id=303
单指令流多数据流(SIMD)是一种实现数据级并行的技术,其典型代表是向量处理器(Vector Processor)和阵列处理器(Array Processor)。
SIMD技术最初主要应用在大规模的超级计算机中,但是近些年来,小规模SIMD技术也开始在个人计算机上得到广泛应用。
SIMD技术的关键是在1条单独的指令中同时执行多个运算操作,以增加处理器的吞吐量。为此,SIMD结构的CPU有多个执行部件,但都在同一个指令部件的控制之下,中央控制器向各个处理单元发送指令,整个系统只要求有一个中央控制器,只要求存储一份程序,所有的计算都是同步的。
为了了解SIMD在性能上的优势,我们以加法指令为例进行说明:
单指令流单数据流(SISD)型CPU对加法指令译码后,执行部件先访问主存,取得第一个操作数,之后再一次访问主存,取得第二个操作数,随后才能进行求和运算;而在SIMD型CPU中,指令译码后,几个执行部件同时访问主存,一次性获得所有操作数进行运算。
这一特点使得SIMD技术特别适合于多媒体应用等数据密集型运算。
1.MMX技术
MMX(Multi-Media
Extension,多媒体扩展)是Intel设计的一种SIMD多媒体指令集。作为一种多媒体扩展技术,MMX大大提高了计算机在多媒体和通信应用方面的能力,带有MMX技术的CPU适合于数据量很大的图形、图像数据处理,从而使三维图形、动画、视频、音乐合成、语音识别、虚拟现实等数据处理的速度有了很大提高。MMX技术的优点是增加了多媒体处理能力,可以一次处理多个数据,缺点则是仅仅只能处理整型数,并且由于占用浮点数寄存器进行运算,以至于MMX指令集与x87浮点运算指令不能够同时执行,必须做密集的切换才可以正常执行,这种情况势必造成整个系统运行质量的下降。
2.SSE技术
1999年,Intel在其Pentium III微处理器中集成了SSE(Streaming SIMD
Extensions)技术,有效增强了CPU浮点运算的能力。SSE兼容MMX指令,可以通过SIMD和单时钟周期并行处理多个浮点数据来有效提高浮点运算速度,对图像处理、浮点运算、3D运算、视频处理、音频处理等诸多多媒体应用起到全面强化作用。
具有SSE指令集支持的处理器有8个128位的寄存器,每一个寄存器可以存放4个单精度(32位)浮点数。SSE同时提供了一个指令集,其中的指令允许把浮点数加载到这些128位寄存器中,这些数就可以在这些寄存器中进行算术逻辑运算,然后把结果送回主存。也就是说,SSE中的所有计算都可以针对4个浮点数一次性完成,这种批处理带来了效率的提升。
例如,考虑下面这个任务:计算一个很长的浮点型数组中每一个元素的平方根。
实现这个任务的算法一般可以写为:
for each f in array
{
把f从主存加载到浮点寄存器
计算平方根
再把计算结果从寄存器中取出写入主存
}
而在采用SSE技术后,算法可以改写为:
for each 4 members in array //对数组中的每4个元素
{
把数组中的这4个数加载到一个128位的SSE寄存器中
在一个CPU指令执行周期中完成计算这4个数的平方根的操作
把所得的4个结果取出写入主存
}
3.SSE2技术
2001年,Intel配合其Pentium 4微处理器,推出了SSE2(Streaming SIMD Extensions
2)指令集,扩展了SSE指令集,并可完全取代MMX。SSE2指令集是Intel公司在SSE指令集的基础上发展起来的。相比于SSE,SSE2使用了144个新增指令,扩展了MMX技术和SSE技术,提高了诸如MPEG-2、MP3、3D图形等应用程序的运行性能。
在整数处理方面,随MMX技术引进的SIMD整数指令从64位扩展到了128
位,使SIMD整数类型操作的执行效率成倍提高;在浮点数处理方面,双精度浮点SIMD指令允许以
SIMD格式同时执行两个浮点操作,提供双精度操作支持有助于加速内容创建、财务、工程和科学应用。除SSE2指令之外,最初的SSE指令也得到增强,通过支持多种数据类型(例如双字、四字)的算术运算,支持灵活、动态范围更广的计算功能。4.SSE3技术
2004年,Intel在其基于Prescott核心的新款Pentium 4处理器中,开始使用SSE3(Streaming SIMD
Extensions 3)技术。SSE3指令集是Intel公司在SSE2指令集的基础上发展起来的。相比于SSE2,SSE3在SSE2的基础上又增加了13条SIMD指令,以提升Intel超线程(Hyper-Threading)技术的效能,最终达到提升多媒体和游戏性能的目的。
为了能够使用 DirectX Math 类库,需要包含头文件:
#include <DirectXMath.h>
对于一些其它的数据类型,还需要类似于下面的头文件:
#include <DirectXPackedVector.h>
前者位于DirectX命名空间中;后者位于DirectX::PackedVector命名空间中。
另外说一句,x64的CPU是默认支持SSE2的,而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 > /fp:fast
2.向量类型
2.1 XMVECTOR & XMFLOATn
在DirectX Math中,向量的核心类型是 XMVECTOR,此类型与SIMD硬件寄存器直接映射。它是一个128位的数据类型,能够在一个SIMD指令下处理4个32位的浮点数。
如果SSE2处于使能状态,XMVECTOR 类型一般被定义成下面这个样子:
typedef __m128 XMVECTOR;
我们来看看这个__m128到底是什么?
typedef union __declspec(intrin_type) _CRT_ALIGN(16) __m128 {
float m128_f32[4];
unsigned __int64 m128_u64[2];
__int8 m128_i8[16];
__int16 m128_i16[8];
__int32 m128_i32[4];
__int64 m128_i64[2];
unsigned __int8 m128_u8[16];
unsigned __int16 m128_u16[8];
unsigned __int32 m128_u32[4];
} __m128;
一个联合类型,里面装了一堆数组,并且按照 16 Byte(=16x4x2 =128bit) 对齐。换句话说,XMVECTOR是一个能够装4个32位数的数组,可以使用它的2个,3个或者4个32位,不使用的尽管忽略掉。
但是,XMVECTOR与寄存器向映射,利于运算并不善于存储,所以,应该使用其它类型来用于存储数据。
来看看XMFLOATn的定义:
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;
}
};
大同小异,都是包含了基本数据、无参构造函数、带有4个float参数的构造函数和等号的重载。
不知道是不是只有我一个人好奇In_reads()到底是干什么的……
接下来,就是传遍百度的所谓XMVECTOR使用规则:
- 在局部、全局变量中使用XMVECTOR类型;
- 在类中定义向量成员时,使用类型XMFLOAT2,XMFLOAT3,XMFLOAT4;
- 对类中的向量进行运算时,用Load型函数把相应向量读取到XMVECTOR中,再进行运算;
- 运算完后把相应的结果XMVECTOR通过Store型函数存储到相应的XMFLOATn向量中。
下面引用原文
- Use XMVECTOR for local or global variables.
- Use XMFLOAT2, XMFLOAT3, and XMFLOAT4 for class data members.
- Use loading functions to convert from XMFLOATn to XMVECTOR before doing calculations.
- Do calculations with XMVECTOR instances.
- Use storage functions to convert from XMVECTOR to XMFLOATn.
嗯,少了一条:使用 XMVECTOR 的实例进行运算。
2.2 装载(loadling)与存储(storage)函数
XMVECTOR 类型因为与SIMD硬件寄存器关联,所以善于计算;而 XMFLOATn 利于数据的存储,所以需要一些函数将两者进行相互转换。
2.2.1 将数据从 XMFLOATn 装载至 XMVECTOR :
// 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);
2.2.2 将 XMVECTOR 的数据存储至 XMFLOATn:
// 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);
2.3 获取(get)与设置( set ) XMVECTOR 的单一元素
//Get
float XM_CALLCONV XMVectorGetX(FXMVECTOR V);
float XM_CALLCONV XMVectorGetY(FXMVECTOR V);
float XM_CALLCONV XMVectorGetZ(FXMVECTOR V);
float XM_CALLCONV XMVectorGetW(FXMVECTOR V);
//Set
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);
一会再说 FXMVECTOR 到底是什么东西。
2.4 参数传递
XMVECTOR 类型的变量使用的是SSE/SSE2寄存器传值而不是堆栈的方式,这是出于性能考虑,但是这种为了性能的考虑也是有一些代价的,其中包括编程的复杂性。
利用SSE/SSE2寄存器传值并非没有限制,参数传递的数量是根据编译器以及操作平台所决定的。因此,为了能够让平台和编译器独立,使用另外四种类型来传递 XMVECTOR 类型的参数:FXMVECTOR,GXMVECTOR,HXMVECTOR 和 CXMVECTOR,并且,必须在函数名称前加上 XM_CALLCONV 调用约定。
这是 XM_CALLCONV 的定义:
#if _XM_VECTORCALL_
#define XM_CALLCONV __vectorcall
#else
#define XM_CALLCONV __fastcall
#endif
如果想探究更多的细节,请跳转:
__fastcall:https://msdn.microsoft.com/zh-cn/library/6xa169sk.aspx
__vectorcall:https://msdn.microsoft.com/zh-cn/library/dn375768.aspx
传递 XMVECTOR 参数的规则有以下几点:
- 前3个XMVECTOR的参数使用 FXMVECTOR 类型传递;
- 第4个XMVECTOR的参数使用 GXMVECTOR 类型传递;
- 第5、6个使用HXMVECTOR类型;
- 其余的XMVECTOR的参数使用 CXMVECTOR 类型来传递;
- 以上都是从左到右的顺序。
为什么这样做?
因为x86、arm、xbox360上的vc对vector call convention要求,前三个参数会放入SIMD寄存器(FXMVECTOR),后面的只传入地址(CXMVECTOR)。这样如果<=3个参数,就会得到更好的优化。如果都用CXMVECTOR,速度不如通过寄存器的。如果都用FXMVECTOR,后面的参数则传不进去。
具体请跳转:https://msdn.microsoft.com/zh-cn/library/windows/apps/ee418728.aspx
说到这,再看看上面提到的各类型是如何定义的。
1. 在32位windows平台下,使用__fastcall调用约定时各类型的定义。在这里,前3个参数使用寄存器通道来传递,其它的使用堆栈的方式:
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR& GXMVECTOR;
typedef const XMVECTOR& HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
2. 在32位windows平台下,使用__vectorcall调用约定时各类型的定义。在这里,前6个参数使用寄存器通道来传递,其它的使用堆栈的方式:
typedef const XMVECTOR FXMVECTOR;
typedef const XMVECTOR GXMVECTOR;
typedef const XMVECTOR HXMVECTOR;
typedef const XMVECTOR& CXMVECTOR;
以上,不同情况下各类型的定义代码,结合参数传递的规则,再加上XM_CALLCONV的定义,突然明白了一些东西……XMVECTOR类型会使用SSE2寄存器传值而XMVECTOR&则不会,同理是不是也可以认为XMVECTOR*也不会被使用SSE2?也就是说,当你的参数中存在XMVECTOR& 或 XMVECTOR*类型时(这种引用或指针类型一般被用作是输出的参数),这些参数被编译器认为是非XMVECTOR(non-XMVECTOR)参数而不会被优化。
在DirectXMath.h中,有这样的一个函数声明(Line: 1264),这也是书中所提到的一个例子:
XMMATRIX XM_CALLCONV XMMatrixTransformation(
FXMVECTOR ScalingOrigin,
FXMVECTOR ScalingOrientationQuaternion,
FXMVECTOR Scaling,
GXMVECTOR RotationOrigin,
HXMVECTOR RotationQuaternion,
HXMVECTOR Translation
);
上面的例子拥有6个参数,所有参数全部遵循了“传递 XMVECTOR 参数的规则”。但如果参数中间存在non-XMVECTOR的参数该如何呢?
例如(DirectXMath.h Line: 1262):
XMMATRIX XM_CALLCONV XMMatrixTransformation2D(
FXMVECTOR ScalingOrigin,
float ScalingOrientation,
FXMVECTOR Scaling,
FXMVECTOR RotationOrigin,
float Rotation,
GXMVECTOR Translation
);
规则是一样的,只看 XMVECTOR 类型的。
1. 上面的函数拥有6个参数,第一个XMVECTOR类型的参数是ScalingOrigin,使用的是FXMVECTOR类型来传递;
2. 第二个XMVECTOR类型的参数是Scaling,也是用的FXMVECTOR;
3. 第三个是RotationOrigin,使用FXMVECTOR类型传递;
4. 第四个是Translation,使用GXMVECTOR类型传递。
2.5 静态向量
尽管书中说“静态XMVECTOR实例应该使用XMVECTORF32类型”,但是我不明白XMVECTORF32存在的意义究竟是什么,难道仅仅用于初始化吗?那么,我为什么不用XMFLOATn来初始化呢?
XMVECTORF32类型的定义。
// 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
};
既然问题产生,答案也随之而来了:XMVECTORF32是属于DirectX Math 的类型,而XMFLOATn不是;
一个最简单的例子:
XMVECTOR data;
XMVECTORF32 floatingVector = { 0.f, 0.f, 0.1f, 1.f };
XMFLOAT4 aa = { 0.f, 0.f, 0.1f, 1.f };
data = floatingVector; //OK!
data = aa; //Error!
data = XMLoadFloat4(&aa); //OK!
类似于这种类型的东西有很多,具体看这里:
https://msdn.microsoft.com/en-us/library/windows/desktop/ee415679(v=vs.85).aspx
2.6 运算符重载
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);
(我很讨厌贴代码的环节,但……这有什么好说的?)
2.7 杂项
2.7.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.7.2 角度与弧度的相互转换
//degrees to radians.
inline float XMConvertToRadians(float fDegrees){
return fDegrees * (XM_PI / 180.0f);
}
//radians to degrees.
inline float XMConvertToDegrees(float fRadians){
return fRadians * (180.0f / XM_PI);
}
2.7.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;
}
2.7.4 获取向量
// 返回一个 0 向量;
XMVECTOR XM_CALLCONV XMVectorZero();
// 返回一个单位向量;
XMVECTOR XM_CALLCONV XMVectorSplatOne();
// 返回一个自定义的向量;
XMVECTOR XM_CALLCONV XMVectorSet(float x, float y, float z, float w);
// 返回一个四维都是同一个数的向量;
XMVECTOR XM_CALLCONV XMVectorReplicate(float Value);
// 返回向量的第一个维度x组成的向量(Vx,Vx,Vx,Vx);
XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V);
// 返回向量的第二个维度y组成的向量(Vy,Vy,Vy,Vy);
XMVECTOR XM_CALLCONV XMVectorSplatY(FXMVECTOR V);
// 返回向量的第三个维度z组成的向量(Vz,Vz,Vz,Vz);
XMVECTOR XM_CALLCONV XMVectorSplatZ(FXMVECTOR V);
贴上一个书中自带的例子:
#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 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;
}
2.7.5 向量操作函数
书中只列举了3维向量的操作函数,没有列举2维和4维的,但是它们的函数使用之间仅仅是函数名的区别:
//返回向量的模 |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 × v2
XMVECTOR XM_CALLCONV XMVector3Cross(FXMVECTOR V1, FXMVECTOR V2);
//返回与给定向量方向相同的单位向量 v/|v|
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, //out
XMVECTOR* pPerpendicular, //out
FXMVECTOR V, //in
FXMVECTOR Normal //in
);
//判断两个向量是否相等,返回 v1 = v2 的值
bool XM_CALLCONV XMVector3Equal(FXMVECTOR V1, FXMVECTOR V2);
//判断两个向量是否不等,返回 v1 ≠ v2 的值
bool XM_CALLCONV XMVector3NotEqual(FXMVECTOR V1, FXMVECTOR V2);
在这里,有一些东西需要思考,也是我比较困惑的地方。
有些函数返回的是一个标量,比如向量的“点积”。但是这些函数中,所有函数的返回值全部都是 XMVECTOR 类型,也就是说,所有的操作全都被计算成了普通向量的形式,即使是计算结果并非向量的“点积”。
关于这个,书中的解释是,之所以就连标量也返回成XMVECTOR 类型,是因为它想让你把所有的计算都利用SIMD来完成,直到你结束你的流程。
那么,类似于XMVector3Dot函数,它的返回值是什么样的——是三维都相同的一个向量(v1 · v2, v1 · v2, v1 · 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还提供了一些估算的函数,它们虽然不是很精确,但是速度很快。如有需求可以使用它们。
// Returns estimated ||v||
XMVECTOR XM_CALLCONV XMVector3LengthEst(FXMVECTOR V);
// Returns estimated v/||v||
XMVECTOR XM_CALLCONV XMVector3NormalizeEst(FXMVECTOR V);
DirectX Math提供了一个近似比较两向量是否相等的函数——毕竟计算机中浮点数运算并不是100%准确。
// 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
);
2.8 向量运算法则
在最后,贴上一些关于向量的运算法则吧。
啊!终于结束了……呃,怎么会嫌弃呢?