DirectX 12 持续整理 ——1. 向量

版权声明:转载请标注源地址。 https://blog.csdn.net/rkexy/article/details/72829270

跳过线性代数的数学概念部分。
绝大部分内容来自于《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使用规则:

  1. 在局部、全局变量中使用XMVECTOR类型;
  2. 在类中定义向量成员时,使用类型XMFLOAT2,XMFLOAT3,XMFLOAT4;
  3. 对类中的向量进行运算时,用Load型函数把相应向量读取到XMVECTOR中,再进行运算;
  4. 运算完后把相应的结果XMVECTOR通过Store型函数存储到相应的XMFLOATn向量中。

  下面引用原文

  1. Use XMVECTOR for local or global variables.
  2. Use XMFLOAT2, XMFLOAT3, and XMFLOAT4 for class data members.
  3. Use loading functions to convert from XMFLOATn to XMVECTOR before doing calculations.
  4. Do calculations with XMVECTOR instances.
  5. 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 类型的参数:FXMVECTORGXMVECTORHXMVECTORCXMVECTOR,并且,必须在函数名称前加上 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 参数的规则有以下几点:

  1. 前3个XMVECTOR的参数使用 FXMVECTOR 类型传递;
  2. 第4个XMVECTOR的参数使用 GXMVECTOR 类型传递;
  3. 第5、6个使用HXMVECTOR类型;
  4. 其余的XMVECTOR的参数使用 CXMVECTOR 类型来传递;
  5. 以上都是从左到右的顺序。

  为什么这样做?

  因为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 向量运算法则

  在最后,贴上一些关于向量的运算法则吧。

向量运算法则


啊!终于结束了……呃,怎么会嫌弃呢?

猜你喜欢

转载自blog.csdn.net/rkexy/article/details/72829270