DirectX12 3D游戏开发实践(龙书)第一章 向量代数

第一章 向量代数

向量与坐标系

定义

一种兼具大小和方向的量

坐标系

下图展现了向量v以及控件中俩组不同的标架(frame)
每当我们根据坐标来确定一个向量时,其对应的坐标总是相当于某一个参考系而言的。在3D计算图形学中,我们通常会用到较多的参考系。因此我们需要记录向量的每一种坐标系中对应的坐标。另外我们也需要知道如何将向量坐标在不同标架之间进行转换。
在这里插入图片描述
Direct3D采用的是左手坐标系,左手坐标系Z轴指向屏幕里,也就是眼睛看向方向,右手坐标系Z轴指向屏幕外,见下俩图。
在这里插入图片描述
在这里插入图片描述

向量的基本运算

点积是一种计算结果为标量值的向量乘法运算,因此有些时候也称标量积。设向量 u = ( u x , u y , u z ) u=(u_x,u_y,u_z) u=uxuyuz v = ( v x , v y , v z ) v=(v_x,v_y,v_z) v=vxvyvz,则点积的定义为:

u ⋅ v = u x v x + u y v y + u z v z u·v=u_xv_x+u_yv_y+u_zv_z uv=uxvx+uyvy+uzvz

可见,点积就是向量间对应分类的乘积之和。

点积的定义并没有明显地体现出其几何意义。但是我们却能根据余弦定理找到向量点积的几何关系:
u ⋅ v = ∣ ∣ u ∣ ∣   ∣ ∣ v ∣ ∣ c o s θ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad u·v=\mid \mid u \mid \mid \, \mid \mid v \mid \mid cos\theta uv=∣∣u∣∣∣∣v∣∣cosθ
如果 u u u v v v是单位向量的话,那么有:
u ⋅ v = c o s θ \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad u·v=cos\theta uv=cosθ
此时我们就能通过点积计算得到向量的夹角的余弦值,也就能得到夹角度数。

正交投影投影线垂直于投影面的投影。
正交化一个集合里的向量都与一向量正交,且这个集合的向量都为单位向量,那我们将此集合称为规范正交,而将普通向量集正交化为规范正交集的过程叫做正交化。而这个过程就要使用格拉姆——施密特正交化(Gram-Schmidt Orthogonalization)方法进行处理。规范化步骤: w i = w i ∣ ∣ w i ∣ ∣ w_i=\frac{w_i}{||w_i||} wi=∣∣wi∣∣wi

叉积(亦称向量积、外积)是向量的第二种乘法形式。与计算结果为标量的点积不同,叉积的计算结果亦为向量。叉积的计算方法为:
w = u × v = ( u y v z − u z v y , u z v x − u x v z , u x v y − u y v x ) \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad w=u×v=(u_yv_z-u_zv_y,u_zv_x-u_xv_z,u_xv_y-u_yv_x) w=u×v=(uyvzuzvy,uzvxuxvz,uxvyuyvx)
若实际上采用的是左手坐标系,那么伸出左手,四指头伸直指向向量 u u u,四指做握拳动作,四指弯曲的方向则是向量 v v v的方向,那么此时,大拇指指向的方向则是向量 w w w的方向。(注:如若此处用的是右手坐标系,那么用右手做同样操作即可),这个方法叫左手拇指法则,有的文献也称之为左手定则

利用DirectXMath库对向量运算
DirectXMath使用环境

对于Windows8及其以上版本来讲,DirectXMath(其前身为XNA Math数据库,DirectXMath正是基于此而成)是一款Direct3D应用程序量身打造的3D数学库,而它也自此成为了WindowsSDK的一部分。该数据库采用了SIMD流指令扩展2(Streaming SIMD
Extensions2,SSE2)指令集。借助128位宽的单指令多数据(single instruction multiple data,SIMD)寄存器,利用一条指令SIMD指令即可同时对4个32位浮点数或整数进行运算。这对于向量运算带来的收益是不言而喻的。

为了使用DirectXMath库,我们需要添加头文件#include <DirectXMath.h>,为了一些相关数据类型还需要加入头文件#include <DirectXPackedVector.h>,除此之外并不需要其他库文件,因为所有的代码都以内联的方式是现在头文件里。DirectXMath.h文件中的代码都存于DirectX命名空间中,DirectXPackedVector.h的代码则都位于DirectX::PackedVector中。
另外对于x86平台,我们需要启用SSE2指令集(Project Properties(工程属性) > Configuration Properties(配置属性) >
C/C++ > Code Generation(代码生成) > Enable Enhanced Instruction Set(启用增强子令) )。
在这里插入图片描述
对于所有平台,我们还应当启用快速浮点模型/fp:fast (Project Properties(工程属性) >
Configuration Properties(配置属性) > C/C++ > Code Generation(代码生成) > Floating Point Model(浮点模型))
在这里插入图片描述
对于x64平台来说,我们却不必开启SSE2指令集,这是因为所有的x64 CPU对此均有支持。

向量类型

DirectXMath中,核心向量时XMVECTOR,它将被映射到SIMD硬件寄存器中。通过SIMD指令的配合,利用这种具有128位的类型能一次性处理4个32位的浮点数。在开启SSE2后,此类型在x86和x64的定义是:

typedef __m128 XMVECTOR;

此处m128是特殊的SIMD类型(定义见xmmintrin.h)(采用联合的形式,这样用法的好处是可以同一段地址空间,做不同变量长度的字节数组使用)

typedef union __declspec(intrin_type) __declspec(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;

对于类中的数据成员建议分别使用XMFLOAT2(2D),XMFLOAT2(3D),XMFLOAT2(4D)

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; }
};
加载方法和存储方法

如果把上面的类型用于计算,却依然不能发挥SIMD技术的高效性,为此我们还需要将这些类型实例化转换为XMVECTOR类型。转换过程可以通过DirectXMath库的加载函数(loading function)实现。相反地,DirectXMath库也提供了用来将XMVECTOR类型转换为XMVECTn类型的存储函数。(想了很久不清楚它这里为什么要分别用俩种类型互相转换,而不是直接用XMVECTOR直接定义,我自己趋向的结果是类型明确,加上此程序开发者不关心其他类型的冗余数据,如果有什么官方解释,麻烦请留言,谢谢)

用下面的方法将数据类型从XMFLOATn类型加载到XMVECTOR类型 :

XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2 *pSource); 
XMVECTOR XM_CALLCONV XMLoadFloat3(const XMFLOAT3 *pSource); 
XMVECTOR XM_CALLCONV XMLoadFloat4(const XMFLOAT4 *pSource); 

用下面的方法将数据类型从XMVECTOR类型存储到XMFLOATn类型:

void XM_CALLCONV XMStoreFloat2(XMFLOAT2 *pDestination, FXVECTOR V);
void XM_CALLCONV XMStoreFloat3(XMFLOAT3 *pDestination, FXVECTOR V);
void XM_CALLCONV XMStoreFloat4(XMFLOAT4 *pDestination, FXVECTOR V);

当我们只期望从XMVECTOR 实例中得到某一向量分量或将某一向量转换为XMVECTOR类型时,相关存取方法如下:

float XM_CALLCONV XMVectorGetX(FXMVECTOR V);
float XM_CALLCONV XMVectorGetY(FXMVECTOR V);
float XM_CALLCONV XMVectorGetZ(FXMVECTOR V);
float XM_CALLCONV XMVectorGetW(FXMVECTOR V);

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);
参数传递

为了提高效率,可以将XMVECTOR类型的值作为函数的参数,直接送至SSE/SSE2寄存器里,而不存于栈内。以此方式传递的参数数量取决于用户使用的平台和编译器。因此为了使代码更具通用性,不受具体平台、编译器的影响,我们将利用FXMVECTORGXMVECTORHXMECTORCXMVECTOR类型来传递XMVECTOR类型的参数。基于特定的平台和编译器,它们会被自动地定义为适当的类型。

传递XMVECTOR参数的规则如下:

在32位的Windows系统上,编译器将根据__vectorcall调用约定将前3个,XMVECTOR参数传递到寄存器中,而把其余参数都存于栈上。
在64位的Windows系统上,编译器将根据__fastcall调用约定将前6个,`XMVECTOR参数传递到寄存器中,而把其余参数都存于栈上。
在这里插入图片描述

前3个XMVECTOR参数的类型都是应当用类型:FXMVECTOR
第4个XMVECTOR参数应当用类型:GXMVECTOR
第5、6个XMVECTOR参数应当用类型:HXMVECTOR
其余的XMVECTOR参数应当用类型:CXMVECTOR

构造函数对于这些规则来说是个例外,在编写时,前三个XMVECTOR参数用FXMVECTOR类型,其余用CXMVECTOR类型,另外对于构造函数,不要用XM_CALLCONV注解。
下面是截取自DirectXMath源码库:

inline XMMATRIX XM_CALLCONV XMMatrixTransformation(
FXMVECTOR ScalingOrigin,
FXMVECTOR ScalingOrientationQuaternion, .
FXMVECTOR Scaling,//第1~3个参数用FXMVECTOR
GXMVECTOR RotationOrigin,//第4个参数用GXMVECTOR
HXMVECTOR RotationQuaternion,
HXMVECTOR Translation);//5~6个参数用CXMVECTOR

当我们在这些参数之间掺杂其他不是XMVECTOR参数类型,此规则依旧可用:

inline XMMATRIX XM_CALLCONV XMMatrixTransformation2D(
FXMVECTOR ScalingOrigin,
float ScalingOrientation,
FXMVECTOR Scaling,
FXMVECTOR RotationOrigin,
float Rotation,
GXMVECTOR Translation);//第4个XMVECTOR参数

传递XMVECTOR参数的规则仅适用“输入”参数。“输出”的XMVECTOR参数则不会占用SSE/SSE2寄存器,所以他们的处理方式与非XMVECTOR类型的参数一致。

常向量

XMVECTOR类型的惨了实例应当用XMVECTORF32类型来表示。在DirectX SDK中就可以看到这种实例的运用:

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 转换成XMVECTOR类型的运算符:

__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
};

另外,也可以通过XMVECTORU32类型来创建由整数数据结构构成的XMVECTOR常量:

static const XMVECTORU32 vGrabY = {
0x00000000,0xFFFFFFFF,0x00000000,0x00000000
};
运算符重载

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);
杂项

DirectXMath库定义了一组与 π \pi π有关的常用常量近似值:

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;

另外,它用下列内联函数实现了弧度和角度间的互相转化:

inline float XMConvertToRadians(float fDegrees)
{ return fDegrees * (XM_PI / 180.0f); }
inline float XMConvertToDegrees(float fRadians)
{ return fRadians * (180.0f / XM_PI); }

DirectXMath库还定义了求出俩个数间较大值及较小值的函数:

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; }
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);

实例程序:
#include <windows.h> //
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

//重载运算符<<,这样就可以通过Cout函数输出XMVECTOR对象

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);

	//检查是否支持SSE2指令集(Pentium4,AMD K8及后续版本的处理器)
	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;
}

运行结果:
在这里插入图片描述

向量函数

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);

下面Demo演示一些函数与重载的使用:

#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

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);
	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);
	XMVECTOR a = u + v;
	XMVECTOR b = u - v;
	XMVECTOR c = 10.0f * u;
	XMVECTOR L = XMVector3Length(u);
	XMVECTOR d = XMVector3Normalize(u);
	XMVECTOR s = XMVector3Dot(u, v);
	XMVECTOR e = XMVector3Cross(u, v);
	XMVECTOR projW;
	XMVECTOR perpW;
	XMVector3ComponentsFromNormal(&projW, &perpW, w, n);
	bool equal = XMVector3Equal(projW + perpW, w) != 0;
	bool notEqual = XMVector3NotEqual(projW + perpW, w)
		!= 0;
		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;
}

运行结果如下:
在这里插入图片描述

浮点数误差

在用计算机处理向量有关工作时,我们应当了解以下工作内容,在比较浮点数时,一定要注意浮点数存在误差。我们认为相等的俩个浮点数可能会因此有细微的差别。例如,已知在数学上规范化向量长度为1,但是在计算机程序中的表达上,向量的长度只能接近于1。此外,在数学中,对于任意实数 p p p 1 p = 1 1^p=1 1p=1。但是,当只能在数值上逼近1时,随着幂 p p p的增加,所求近似值的误差也在逐渐增大,由此可见,数值误差时可以积累的。下面这个小程序可以印证这些观点:

#include <windows.h> 
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;
int main()
{
	cout.precision(8);
	//检查是否支持SSE2指令集(Pentium4,AMD K8以及其后续版本的处理器)
	if (!XMVerifyCPUSupport())
	{
		cout << "directx math not supported" << endl;
		return 0;
	}
	XMVECTOR u = XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f);
	XMVECTOR n = XMVector3Normalize(u);
	float LU = XMVectorGetX(XMVector3Length(n));
	cout << LU << endl;
	if (LU == 1.0f)
		//在数学上,此向量的长度应当为1.
		cout << "Length 1" << endl;
	else
		cout << "Length not 1" << endl;
	float powLU = powf(LU, 1.0e6f);
	cout << "LU ^ (10 ^ 6) = " << powLU << endl;
}

运算结果:
在这里插入图片描述
因此为了弥补浮点数精确性上的不足,我们通过比较俩个浮点数是否近似相等来加以解决。在比较的时候,我们需要定义一个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 作为容差,测试比较的向量是否相等:

// 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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值