Tag DirectX下的博客主要用于记录DirectX的学习过程,主要参考《DirectX 12 3D 游戏实战开发》。函数
Shader的编写离不开数学运算,尤为是向量矩阵类型的运算,DirectX为咱们准备了一个完备的数学库DirectXMath.h
,里面封装了经常使用的向量矩阵类型,而且重载了许多方法,如点积、叉积、矩阵乘法等。DirectXMath是Windows SDK的一部分,是为D3D打造的3D数学库,采用SIMD指令集加速向量运算,如:单条SIMD加法指令能够直接计算4D向量的加法结果。性能
添加#include <DirectXMath.h>
便可使用DirectXMath库,其中的代码都位于namespace DirectX
,使用相关数据类型则需添加#include <DirectXPackedVector.h>
,其中的代码位于namespace DirectX::PackedVector
。在DirectXMath库中的核心类型为XMVECTOR
,它须要按16字节对齐,其中向量类型为XMFLOAT2
、XMFLOAT3
、XMFLOAT4
。而在开启SSE2以后,在x86和x64平台中,XMVECTOR
被定义为一个共用体_m128
,经过这个类型,SIMD技术才得以实现。学习
// _m128在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;
DirectXMath库提供了设置XMVECTOR
类型中数据的函数。spa
// return zero vector XMVECTOR XM_CALLCONV XMVectorZero(); // return unit vector XMVECTOR XM_CALLCONV XMVectorSplatOne(); // return vector(x, y, z, w) XMVECTOR XM_CALLCONV XMVectorSet(float x, float y, float z, float w); // return vector(Value, Value, Value, Value) XMVECTOR XM_CALLCONV XMVectorReplicate(float Value); // return vector(V.x, V.x, V.x, V.x) XMVECTOR XM_CALLCONV XMVectorSplatX(FXMVECTOR V); // return vector(V.y, V.y, V.y, V.y) XMVECTOR XM_CALLCONV XMVectorSplatY(FXMVECTOR V); // return vector(V.z, V.z, V.z, V.z) XMVECTOR XM_CALLCONV XMVectorSplatZ(FXMVECTOR V);
XMVECTOR
类型的常量应使用XMVECTORF32
或者XMVECTORU32
类型表示,即便用XMVECTORF32
初始化浮点型向量,使用XMVECTORU32
初始化整型向量。code
为了发挥SIMD特性,在构建向量实例时还应使用库提供的loading function把实例转换为XMVECTOR
类型,同时,库还提供了逆向转换的storage function,用于把XMVECTOR
转换为XMFLOATn
类型。总结就是:变量统一为XMVECTOR
类型,类内成员用XMFLOATn
类型,运算前转换为XMVECTOR
类型,运算后转换回XMFLOATn
类型。orm
// loading function(n为向量维度) XMVECTOR XM_CALLCONV XMLoadFloatn(const XMFLOATn *pSource); // storage function(n为向量维度) void XM_CALLCONV XMStoreFloatn(XMFLOATn *pDestination, FXMVECTOR V); // 获取or存储XMVECTOR中的某个份量(以x份量为例) float XM_CALLCONV XMVectorGetX(FXMVECTOR V); XMVECTOR XM_CALLCONV XMVectorSetX(FXMVECTOR V, float x);
为了进一步提升效率,DirectX还容许把XMVECTOR
类型的值做为函数参数直接送入SSE寄存器中,可是经过这种方法传递的参数有数量限制,数量的限制也因平台而异。为了让代码更具通用性,DirectX为咱们提供了一种约定注解XM_CALLCONV
。这个注解添加在有XMVECTOR
类型参与的函数定义以前,编译器会根据平台转化为特定的调用约定。调用约定则会把特定数量的XMVECTOR
类型直接送往寄存器。游戏
// 在32位的Windows系统上,XM_CALLCONV转化为__fastcall调用约定 // 把前三个XMVECTOR类型直接传入寄存器时,将会出现以下类型定义 typedef const XMVECTOR FXMVECTOR; typedef const XMVECTOR& GXMVECTOR; typedef const XMVECTOR& HXMVECTOR; typedef const XMVECTOR& CXMVECTOR;
DirectX给咱们的建议是,在同一个函数调用中,前三个XMVECTOR
参数应使用类型FXMVECTOR
,第四个XMVECTOR
参数应使用GXMVECTOR
,第五、6个XMVECTOR
应使用HXMVECTOR
,其他的使用CXMVECTOR
。而在定义构造函数时,规则又有所不一样。定义构造函数时,前三个XMVECTOR
类型应使用FXMVECTOR
类型,其他的使用CXMVECTOR
,并且不要对构造函数使用XM_CALLCONV注解。而在参数输出时并不会输出到SSE寄存器内,故处理方式和普通类型一致。在DirectXMath库某些函数的定义中就能够体会到XMVECTOR
的参数传递法则。开发
// DirectXMath库中的XMMatrixTransformation函数 inline XMMATRIX XM_CALLCONV XMMatrixTransformation( FXMVECTOR ScalingOrigin, FXMVECTOR ScalingOrientationQuaternion, FXMVECTOR Scaling, GXMVECTOR RotationOrigin, HXMVECTOR RotationQuaternion, HXMVECTOR Translation);
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库提供了封装好的函数执行向量运算,下面给出其中部分的3D向量版本,对应的有2D和4D版本,函数名形式相同。每一个函数的命名都已经很好地体现了函数的功能。能够注意到,即便运算在数学上的计算结果是标量,例如点积,对应函数的返回类型仍然是XMVECTOR
,而这个结果标量会被复制到各个份量中。这么作的缘由之一是尽可能减小SIMD向量和标量的混合运算,提升效率。DirectXMath库还提供了一些性能开销交稿的函数的估算版本,这些函数精度低,速度快。博客
XMVECTOR XM_CALLCONV XMVector3Length(FXMVECTOR V); // 计算V的模长 XMVECTOR XM_CALLCONV XMVector3LengthSq(FXMVECTOR V); // 计算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); // 返回一个与V正交的向量 XMVECTOR XM_CALLCONV XMVector3AngleBetweenVectors(FXMVECTOR V1, FXMVECTOR V2); // 计算向量间的夹角 // 计算V关于Normal的平行份量和正交份量,输入的Normal必须是已经normalize的 // pParallel存储平行份量,pPerpendicular存储正交份量 void XM_CALLCONV XMVector3ComponentsFromNormal( XMVECTOR* pParallel, XMVECTOR* pPerpendicular, FXMVECTOR V, FXMVECTOR Normal ); bool XM_CALLCONV XMVector3Equal(FXMVECTOR V1, FXMVECTOR V2); // 判断V1和V2是否相同 bool XM_CALLCONV XMVector3NotEqual(FXMVECTOR V1, FXMVECTOR V2); // 判断V1和V2是否不一样 // 判断V1和V2是否近似相等,容差值为Epsilon,这是为了弥补浮点偏差 XMFINLINE bool XM_CALLCONV XMVector3NearEquals(FXMVECTOR V1, FXMVECTOR V2, FXMVECTOR Epsilon); XMVECTOR XM_CALLCONV XMVector3LengthEst(FXMVECTOR V); // 估算V的模长 XMVECTOR XM_CALLCONV XMVector3NormalizeEst(FXMVECTOR V); // 估算V的规范化向量
DirectXMath库也提供了一些数学常量的近似值,以及弧度角度转化、比较值的函数。
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); } 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; }