原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十二章:四元数(QUATERNIONS)



学习目标

  1. 回顾复数,以及复数相乘如何在平面上表达旋转;
  2. 理解四元数以及它的运算;
  3. 理解单位四元数如何表达3D旋转;
  4. 学习如何转换旋转变量的表达;
  5. 学习如何对单位四元数线性差值,并且理解它等价于几何上的3D角度差值;
  6. 熟悉DirectX Math库中的四元数类和操作。


1 回顾复数

四元数可以看做是一个复数,所以我们先要回顾一下复数。本章的主要目标是展示一个复数P乘以一个单位复数,得到的是旋转后的P。


1.1 定义

有很多种方法介绍复数,我们采用下面的方法,将它想象成一个2D点或者向量。

一对排序的实数z = (a, b)是一个复数。第一个部分叫实数部分,第二个部分是虚数部分。并且加减乘除定义如下:



并且很容易证明实数的算术性质对复数也依然有效(交换律,结合律,分配率)(练习1);

如果一个复数形式是(x, 0),那么它就是通过实数x定义,然后写成(x, 0);那么任何实数都可以是一个复数,其虚数部分是0;观察一个实数乘以一个复数x(a, b) = (x, 0)(a, b) = (xa, xb) = (a, b)(x, 0) = (a, b)x,这个形式可以让我们回忆起变量-向量的乘法。

我们定义单位虚数i = (0, 1),然后使用定义的复数乘法,i^2 = (0, 1)(0, 1) = (−1, 0) = −1,所以i是方程x^2 = −1的解。

复数z = (a, b)的复数共轭表示为z‾\overline{z}z,并且z‾\overline{z}z= (a, −b);一个简单的记住复数除法公式的方法是:分子和分母乘以分母的共轭,这样分母就成为了一个实数:

下面展示一个复数(a, b)可以写为a + ib,我们有a = (a, 0), b = (b, 0)和i = (0, 1),所以:

使用a + ib形式,我们可以重做加减乘除:



并且在这种形式下,z = a + ib的共轭复数为a - ib。


1.2 几何解释

我们将复数a + ib = (a, b)理解为几何上的2D向量或者点(在复平面);复数相加匹配到向量的相加:



复数的绝对值或者长度由向量的长度来定义:



我们将长度为1的复数称之为单位复数:


1.3 极坐标表示和旋转

因为复数可以看做是2D复平面的点或者向量,所以我们也可以将它表示到极坐标:



后面的等式就是复数a + ib的极坐标表示。



令z1 = r1(cosθ1 + isinθ1), z2 = (cosθ2 + isinθ2),那么:



这里使用了三角定理:



所以几何上,z1z2的乘积表示长度为r1r2的向量旋转了θ1 + θ2角度;如果其中r2为1,那么乘积就表示将z1旋转了θ2角度。如上图,所以复数乘以单位复数,表示把前面的复数旋转。



2 四元数代数


2.1 基本运算的定义

四个有序实数q = (x, y, z, w) = (q1, q2, q3, q4)是一个四元数,通常简写为q = (u, w) = (x, y, z, w),并且我们称u = (x, y, z)为虚数部分,w为实数部分。那么加减乘除定义如下:



乘法的定义看起来会比较奇怪,但是这些运算是定义,所以我们可以定义为我们想要的形式,并且这些形式很有用。矩阵的乘法定义一开始也看起来很奇怪,但是结果是它们很有用。

令p = (u, p4) = (p1, p2, p3, p4)并且q = (v, q4) = (q1, q2, q3, q4),那么u × v =(p2q3 – p3q2, p3q1 – p1q3, p1q2 – p2q1)并且u·v = p1q1 + p2q2 + p3q3。那么组件形式下,四元数的乘积r = pq是:



可以写成矩阵乘法形式:



如果你偏爱行向量形式,取其转置矩阵:


2.2 特殊乘积

令i = (1, 0, 0, 0), j = (0, 1, 0, 0), k = (0, 0, 1, 0)为四元数,那么我们有一些特殊乘积,会让我们回忆起叉积:



这些等式是直接从四元数乘积公式得来的,比如:


2.3 特性

四元数的乘法不具备交换律,下图证明ij = −ji。四元数乘积具有结合律;所以四元数可以联想为矩阵的乘积,具有结合律,不具有交换律。四元数e = (0, 0, 0, 1)用以乘法单位:



四元数具有乘法分配律p(q + r) = pq + pr 和 (q + r)p = qp + rp。


2.4 转化

我们将实数、向量与四元数通过下面的方式来关联。令s是实数x,u = (x, y, z)是向量,那么:



可以说任意实数是一个有0向量的四元数,任意向量是一个具有0实数的四元数;另外单位四元数为1 = (0, 0, 0, 1);一个四元数具有0实数称之为纯四元数(pure quaternion)。

使用四元数乘法的定义,一个实数乘以四元数是标量相乘,具有交换律:




2.5 共轭和标范数

四元数q = (q1, q2, q3, q4) = (u, q4)的共轭由q定义:



也就是直接将虚数部位取反;相比于共轭复数,它具有下面特性:



其中q + q
和qq* = q*q等于实数。

四元数的范数(长度),定义为:



范数为1的四元数为单位四元数,范数具有下面的特性:



特性2表示2个单位四元数相乘,依然是单位四元数;如果||p|| = 1,那么||pq|| = ||q||。

共轭和范数的特性可以直接通过定义推导出来,比如:


2.6 逆(Inverses)

因为矩阵和四元数乘法不具有交换律,所以不能直接定义除法运算。但是每个非0四元数具有逆,令q = (q1, q2, q3, q4) = (u, q4)是一个非0的四元数,那么它的逆通过q−1q^{-1}q−1来定义:



很容易证明它是复数的逆:



可以看出如果q是单位四元数,那么∣∣q∣∣2=1||q||^2 = 1∣∣q∣∣2=1,并且q−1=q∗q^{-1} = q^*q−1=q∗。

并且符合下面的特性:


2.7 极坐标表达

如果q = (q1, q2, q3, q4) = (u, q4)是单位四元数,那么:





上图表示,对于θ∈[0, π],q4 = cosθ,根据三角定义sin2θ + cos2θ = 1:



所以



现在求单位向量:



所以u = sinθn,现在我们可以写出单位四元数q = (u, q4)的极坐标表达,其中n是单位向量:

如果我们将−θ带入等式中的θ,只是取反了向量部分:



下节将会介绍,n表示旋转的轴向。



3 单位四元数和旋转


3.1 旋转运算

令q = (u, w)是一个单位四元数并且令v是一个3D点或者向量,然后我们认为v是一个纯四元数p = (v, 0)。当q是一个单位四元数时,q−1=q∗q^{−1} = q^*q−1=q∗。回顾四元数乘法公式:



现在考虑下面的乘法:



对其长度稍作简化,我们把实数部分和向量部分分开计算。我们做下面的符号替换:



实数部分:



其中u · (v × u) = 0,因为根据叉积的定义,(v × u)是和u正交的。

虚数部分:



其中对u × (u × v)应用了乘法定义:a × (b × c) = (a · c)b − (a · b)c。所以:



计算的结果是一个向量或者点,其实数部分为0。所以随后的等式中,我们放弃实数部分。

因为q是一个单位四元数,所以可以写为:



带入上面公式后:



为了进一步简化,我们带入三角定义:



对比第三章的旋转公式,我们发现它和旋转公式基本一致,它将向量v沿着n轴旋转2θ度。



所以我们定义四元数旋转运算:



所以如果你要沿着n轴旋转θ度,那么你可以构建对于的旋转四元数:



然后应用到旋转公式中Rq(v)R_q(v)Rq​(v)。


3.2 四元数旋转运算到矩阵

令q = (u, w) = (q1, q2, q3, q4)是一个单位四元数,根据之前的公式,可以得到:



上面公式的三个部分可以分别写出矩阵形式:



将它们相加:



根据单位四元数的特性(各分组件平方的和为1),做下面的简化:



最后矩阵可以写为:


3.3 矩阵到四元数旋转运算

给出一个旋转矩阵:



我们希望找到四元数q = (q1, q2, q3, q4),我们的策略是,设置矩阵如下:



然后求解q1, q2, q3, q4;

首先将对角线上的元素相加(最终一个矩阵):



然后组合对角相反的元素来求解q1, q2, q3:



如果q4 = 0,那上面这些公式就无意义,所以我们要找到R的最大对角元素来除,并且选择矩阵元素的其他组合。加入R11是最大的对角:



如果假设R22 或者 R33为最大对角线,计算模式类似。


3.4 组成

假设p和q是单位四元数,并且对于旋转运算为Rp 和 Rq,令v′=Rp(v)v^{'} = R_p(v)v′=Rp​(v),那么组合:



因为p和q都是单位四元数,pq的乘积也是单位四元数||pq|| = ||p||||q|| = 1;所以pq也表示旋转;也就是说说得到的旋转为:Rq(Rp(v))R_q(R_p(v))Rq​(Rp​(v))。



4 四元数的插值

因为四元数是由4个实数组成的,所以几何上,可以把它看成是一个4D向量,单位四元数是4D在单位4D球体表面,除了叉积(只定义了3D向量)。特别的,点积也支持四元数,令p = (u, s)并且q = (v, t),那么:



其中θ是两个四元数之间的夹角,如果p和q是单位四元数,那么p·q = cosθ,所以点积可以帮助我们考虑2个四元数之间的夹角。

出于动画考虑,我们需要在两个方向之间进行插值,为了插值四元数,我们需要在单位球体上进行弧度差值,所以也需要在单位四元数上差值。为何推导出公式,如下图所示:我们需要在a和b中间差值tθ。我们需要找到权重c1和c2支持p = c1a + c2b,其中||p|| = ||a|| = ||b||。我们对两个未知项创建两个等式:





然后可以导出下面的矩阵:



考虑到上面的矩阵等式Ax = b,其中A是可逆的,所以根据克莱姆法则xi=detAidetAx_i = \frac{detA_i}{detA}xi​=detAdetAi​​,其中AiA_iAi​是通过交换A中第i列的向量到b,所以:



根据三角毕达哥斯拉定义和加法公式,我们可以得出:



所以:



并且:



所以我们定义出球体差值公式:

如果将单位四元数看成4D向量的话,我们就可以求解四元数之间的夹角:θ = arccos(a · b)。

如果a和b之间的夹角趋近于0,sinθ趋近于0,那么上面公式中的除法就会引发问题,会导致无限大的结果。这种情况下,对两个四元数进行线性差值,并标准化结果,就是对小θ的一个很好的近似:



观察下图,线性差值是通过将四元数差值投影回单位球体,其结果是一个非线性速率的旋转。所以如果你对大角度使用线性差值的话,旋转的速度会时快时慢。



我们现在支持一个四元数有趣的特性,(sq)= sq并且标量-四元数的乘积是具有交换律的,所以我们可以得出:



我们得出q和-q表示的相同的旋转,也可以通过其他方式来证明,如果





Rq表示围绕n旋转θ,R-q表示围绕-n旋转2π − θ。在几何上,一个在4D单位球体上的单位四元数和它的极坐标相反值−q代表的是相同的方向。下图可以看出,这两个旋转到了相同的位置,只是一个旋转了小角度,另一个旋转了大的角度:



所以b和-b表达了相同的方向,我们有2个选择来差值:slerp(a, b, t) 或者 slerp(a, −b, t)。其中一个是从更小的角度直接旋转;另一个是从更大的角度来旋转。如下图所示,选择哪个旋转基于哪个旋转在单位球体上的弧度:选择小弧度代表选择了更直接的路径,选择更长的弧度代表对物体有额外更多的旋转[Eberly01]。

[Watt92]如果要在单位球面上找到四元数最短旋转弧度,我们可以比较||a – b||2和||a – (−b)||2 = ||a + b||2。如果 ||a + b||2 < ||a – b||2我们就选择-b,因为-b更接近a:

  1. // Linear interpolation (for small theta).
  2. public static Quaternion LerpAndNormalize(Quaternion p, Quaternion q, float s)
  3. {
  4. // Normalize to make sure it is a unit quaternion.
  5. return Normalize((1.0f - s)*p + s*q);
  6. }
  7. public static Quaternion Slerp(Quaternion p, Quaternion q, float s)
  8. {
  9. // Recall that q and -q represent the same orientation, but
  10. // interpolating between the two is different: One will take the
  11. // shortest arc and one will take the long arc. To find
  12. // the shortest arc, compare the magnitude of p-q with the
  13. // magnitude p-(-q) = p+q.
  14. if(LengthSq(p-q) > LengthSq(p+q))
  15. q = -q;
  16. float cosPhi = DotP(p, q);
  17. // For very small angles, use linear interpolation.
  18. if(cosPhi > (1.0f - 0.001))
  19. return LerpAndNormalize(p, q, s);
  20. // Find the angle between the two quaternions.
  21. float phi = (float)Math.Acos(cosPhi);
  22. float sinPhi = (float)Math.Sin(phi);
  23. // Interpolate along the arc formed by the intersection of the 4D
  24. // unit sphere and the plane passing through p, q, and the origin of
  25. // the unit sphere.
  26. return ((float)Math.Sin(phi*(1.0- s))/sinPhi)*p + ((float)Math.Sin(phi*s)/sinPhi)*q;
  27. }


5 DIRECTX MATH四元数函数

DirectX数学库支持四元数。因为四元数的数据是4个实数,所以使用XMVECTOR类型类保存四元数。下面是通用的函数:

  1. // Returns the quaternion dot product Q1·Q2.
  2. XMVECTOR XMQuaternionDot(XMVECTOR Q1, XMVECTOR Q2);
  3. // Returns the identity quaternion (0, 0, 0, 1).
  4. XMVECTOR XMQuaternionIdentity();
  5. // Returns the conjugate of the quaternion Q.
  6. XMVECTOR XMQuaternionConjugate(XMVECTOR Q);
  7. // Returns the norm of the quaternion Q.
  8. XMVECTOR XMQuaternionLength(XMVECTOR Q);
  9. // Normalizes a quaternion by treating it as a 4D vector.
  10. XMVECTOR XMQuaternionNormalize(XMVECTOR Q);
  11. // Computes the quaternion product Q1Q2.
  12. XMVECTOR XMQuaternionMultiply(XMVECTOR Q1, XMVECTOR Q2);
  13. // Returns a quaternions from axis-angle rotation representation.
  14. XMVECTOR XMQuaternionRotationAxis(XMVECTOR Axis, FLOAT Angle);
  15. // Returns a quaternions from axis-angle rotation representation, where the axis
  16. // vector is normalized—this is faster than XMQuaternionRotationAxis.
  17. XMVECTOR XMQuaternionRotationNormal(XMVECTOR NormalAxis,FLOAT Angle);
  18. // Returns a quaternion from a rotation matrix.
  19. XMVECTOR XMQuaternionRotationMatrix(XMMATRIX M);
  20. // Returns a rotation matrix from a unit quaternion.
  21. XMMATRIX XMMatrixRotationQuaternion(XMVECTOR Quaternion);
  22. // Extracts the axis and angle rotation representation from the quaternion Q.
  23. VOID XMQuaternionToAxisAngle(XMVECTOR *pAxis, FLOAT *pAngle, XMVECTOR Q);
  24. // Returns slerp(Q1, Q2, t)
  25. XMVECTOR XMQuaternionSlerp(XMVECTOR Q0, XMVECTOR Q1, FLOAT t);


6 旋转Demo

本章中的Demo,我们在简单的场景中运动一个骷髅头。位置、方形和缩放都做动画。我们用四元数来表达骷髅的方向,然后使用球面差值来对方向差值。使用线性差值对位置和缩放差值。它是对下一章中的角色动画做预热。

我们使用关键帧系统对骷髅做动画:

  1. struct Keyframe
  2. {
  3. Keyframe();
  4. ˜Keyframe();
  5. float TimePos;
  6. XMFLOAT3 Translation;
  7. XMFLOAT3 Scale;
  8. XMFLOAT4 RotationQuat;
  9. };

动画是一些列通过实践来排序的关键帧:

  1. struct BoneAnimation
  2. {
  3. float GetStartTime()const;
  4. float GetEndTime()const;
  5. void Interpolate(float t, XMFLOAT4X4& M)const;
  6. std::vector<Keyframe> Keyframes;
  7. };

GetStartTime函数用来返回第一个帧的时间;GetEndTime函数返回最后一个关键帧的时间。它对于动画什么时候结束很有用,我们可以停止动画。

现在有了一个关键帧列表,对于每两个帧之间使用插值计算:

  1. void BoneAnimation::Interpolate(float t, XMFLOAT4X4& M)const
  2. {
  3. // t is before the animation started, so just return the first key frame.
  4. if( t <= Keyframes.front().TimePos )
  5. {
  6. XMVECTOR S = XMLoadFloat3(&Keyframes.front().Scale);
  7. XMVECTOR P = XMLoadFloat3(&Keyframes.front().Translation);
  8. XMVECTOR Q = XMLoadFloat4(&Keyframes.front().RotationQuat);
  9. XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
  10. XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
  11. }
  12. // t is after the animation ended, so just return the last key frame.
  13. else if( t >= Keyframes.back().TimePos )
  14. {
  15. XMVECTOR S = XMLoadFloat3(&Keyframes.back().Scale);
  16. XMVECTOR P = XMLoadFloat3(&Keyframes.back().Translation);
  17. XMVECTOR Q = XMLoadFloat4(&Keyframes.back().RotationQuat);
  18. XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
  19. XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
  20. }
  21. // t is between two key frames, so interpolate.
  22. else
  23. {
  24. for(UINT i = 0; i < Keyframes.size()-1; ++i)
  25. {
  26. if( t >= Keyframes[i].TimePos && t <= Keyframes[i+1].TimePos )
  27. {
  28. float lerpPercent = (t - Keyframes[i].TimePos) /
  29. (Keyframes[i+1].TimePos - Keyframes[i].TimePos);
  30. XMVECTOR s0 = XMLoadFloat3(&Keyframes[i].Scale);
  31. XMVECTOR s1 = XMLoadFloat3(&Keyframes[i+1].Scale);
  32. XMVECTOR p0 = XMLoadFloat3(&Keyframes[i].Translation);
  33. XMVECTOR p1 = XMLoadFloat3(&Keyframes[i+1].Translation);
  34. XMVECTOR q0 = XMLoadFloat4(&Keyframes[i].RotationQuat);
  35. XMVECTOR q1 = XMLoadFloat4(&Keyframes[i+1].RotationQuat);
  36. XMVECTOR S = XMVectorLerp(s0, s1, lerpPercent);
  37. XMVECTOR P = XMVectorLerp(p0, p1, lerpPercent);
  38. XMVECTOR Q = XMQuaternionSlerp(q0, q1, lerpPercent);
  39. XMVECTOR zero = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);
  40. XMStoreFloat4x4(&M, XMMatrixAffineTransformation(S, zero, Q, P));
  41. break;
  42. }
  43. }
  44. }

下图展示了两个关键帧之间插值的结果:



插值过后,我们构造了变换的矩阵,因为在着色器中我们最终使用矩阵做变换。XMMatrixAffineTransformation函数定义如下:

  1. XMMATRIX XMMatrixAffineTransformation(
  2. XMVECTOR Scaling,
  3. XMVECTOR RotationOrigin,
  4. XMVECTOR RotationQuaternion,
  5. XMVECTOR Translation);

现在我们简单的动画系统已经完成,下一步是定义一些关键帧:

  1. // Member data
  2. float mAnimTimePos = 0.0f;
  3. BoneAnimation mSkullAnimation;
  4. //
  5. // In constructor, define the animation keyframes
  6. //
  7. void QuatApp::DefineSkullAnimation()
  8. {
  9. //
  10. // Define the animation keyframes
  11. //
  12. XMVECTOR q0 = XMQuaternionRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f),
  13. XMConvertToRadians(30.0f));
  14. XMVECTOR q1 = XMQuaternionRotationAxis(XMVectorSet(1.0f, 1.0f, 2.0f, 0.0f),
  15. XMConvertToRadians(45.0f));
  16. XMVECTOR q2 = XMQuaternionRotationAxis(XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f),
  17. XMConvertToRadians(-30.0f));
  18. XMVECTOR q3 = XMQuaternionRotationAxis(XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f),
  19. XMConvertToRadians(70.0f));
  20. mSkullAnimation.Keyframes.resize(5);
  21. mSkullAnimation.Keyframes[0].TimePos = 0.0f;
  22. mSkullAnimation.Keyframes[0].Translation = XMFLOAT3(-0.0f, 0.0f);
  23. mSkullAnimation.Keyframes[0].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
  24. XMStoreFloat4(&mSkullAnimation.Keyframes[0].RotationQuat, q0);
  25. mSkullAnimation.Keyframes[1].TimePos = 2.0f;
  26. mSkullAnimation.Keyframes[1].Translation = XMFLOAT3(0.0f, 2.0f, 10.0f);
  27. mSkullAnimation.Keyframes[1].Scale = XMFLOAT3(0.5f, 0.5f, 0.5f);
  28. XMStoreFloat4(&mSkullAnimation.Keyframes[1].RotationQuat, q1);
  29. mSkullAnimation.Keyframes[2].TimePos = 4.0f;
  30. mSkullAnimation.Keyframes[2].Translation = XMFLOAT3(7.0f, 0.0f, 0.0f);
  31. mSkullAnimation.Keyframes[2].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
  32. XMStoreFloat4(&mSkullAnimation.Keyframes[2].RotationQuat, q2);
  33. mSkullAnimation.Keyframes[3].TimePos = 6.0f;
  34. mSkullAnimation.Keyframes[3].Translation = XMFLOAT3(0.0f, 1.0f, -10.0f);
  35. mSkullAnimation.Keyframes[3].Scale = XMFLOAT3(0.5f, 0.5f, 0.5f);
  36. XMStoreFloat4(&mSkullAnimation.Keyframes[3].RotationQuat, q3);
  37. mSkullAnimation.Keyframes[4].TimePos = 8.0f;
  38. mSkullAnimation.Keyframes[4].Translation = XMFLOAT3(-0.0f, 0.0f);
  39. mSkullAnimation.Keyframes[4].Scale = XMFLOAT3(0.25f, 0.25f, 0.25f);
  40. XMStoreFloat4(&mSkullAnimation.Keyframes[4].RotationQuat, q0);
  41. }



最后一步使根据时间进行插值操作:

  1. void QuatApp::UpdateScene(float dt)
  2. {

  3. // Increase the time position.
  4. mAnimTimePos += dt;
  5. if(mAnimTimePos >= mSkullAnimation.GetEndTime())
  6. {
  7. // Loop animation back to beginning.
  8. mAnimTimePos = 0.0f;
  9. }
  10. // Get the skull’s world matrix at this time instant.
  11. mSkullAnimation.Interpolate(mAnimTimePos, mSkullWorld);

  12. }

现在骷髅的世界矩阵每一帧都根据动画来更新。



7 总结

  1. 一个有序的4个实时q = (x, y, z, w) = (q1, q2, q3, q4)是一个四元数,一般都简写成q = (u, w) = (x, y, z, w),并且我们将u = (x, y, z)称为虚向量部分,w为实数部分,进一步它的加减乘除定义为:

  2. 四元数乘法不满足交换律,但是满足结合律,四元数e = (0, 0, 0, 1)用以恒等式。四元数支持乘法分配律p(q + r) = pq + pr和(q + r)p = qp + rp;

  3. 我们可以将任意实数写成四元数s = (0, 0, 0, s),也可以将任意向量转换成四元数u = (u, 0)。实数部分为0的四元数为纯四元数。四元数可以和标量相乘:s(p1, p2, p3, p4) = (sp1, sp2, sp3, sp4) = (p1, p2, p3, p4)s,特殊的地方在于标量和四元数的乘法支持交换律;

  4. 共轭四元数和四元数范式的定义;

  5. 逆四元数的定义和计算;

  6. 单位四元数可以写成极向量表达q = (u, q4),其中n是单位向量;

  7. 如果q是一个单位四元数,那么q = (sinθn,cosθ) for ||n|| = 1 and θ ∈ [0, π],旋转运算为Rq(v)=qvq−1=qvq∗R_q(v) = qvq^{-1} = qvq^*Rq​(v)=qvq−1=qvq∗表示将点/向量围绕n旋转2θ。Rq有矩阵表达,任何旋转矩阵都可以转换成四元数用来表达旋转;

  8. 我们可以使用球面插值来对两个用单位四元数表示的方向进行插值。



8 练习

Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十二章:四元数(QUATERNIONS)的更多相关文章

  1. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十二章:几何着色器(The Geometry Shader)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十二章:几何着色器(The Geometry Shader) 代码工 ...

  2. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图 学习目标 理解为什么需要法线贴图: 学习法线贴图如 ...

  3. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十六章:实例化和截头锥体裁切

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十六章:实例化和截头锥体裁切 代码工程地址: https://git ...

  4. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引 代码工程地址: https://g ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段 代码工程地址: https://github. ...

  6. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画 学习目标 熟悉蒙皮动画的术语: 学习网格层级变换 ...

  7. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION) 学习目标 ...

  8. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十章:阴影贴图

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十章:阴影贴图 本章介绍一种在游戏和应用中,模拟动态阴影的基本阴影 ...

  9. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十八章:立方体贴图

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十八章:立方体贴图 代码工程地址: https://github.c ...

随机推荐

  1. http和tcp/ip,socket的区别

    http协议和tcp/ip协议乍看起来,感觉是同一类的东西,其实不然,下面简单的说说他们的区别. http协议是应用层的一种数据封装协议,类似的还有ftp,telnet等等,而tcp/ip是数据传输层 ...

  2. 【DM642学习笔记十】DSP优化记录

    1. 处理的数据先EDMA到片内,具有更高的效率! 以YUV2RGB为例: #pragma DATA_SECTION(onchipBuf0_y,".INTPROCBUFF"); # ...

  3. [转]WinForm实现win7 Aero磨砂效果介绍

    WinForm实现win7 Aero磨砂效果如下: WinForm实现win7 Aero磨砂效果代码如下: using System; using System.Collections.Generic ...

  4. Eclipse配置jstl标准标签库详解

    安装JSTL1.2 日期:2017-06-27 下载jstl1.2版本,下载地址:http://repo2.maven.org/maven2/javax/servlet/jstl/ 用压缩包打开jst ...

  5. Simple implementation and results of genetic algorithm.

    This experiment was done for the final assignment of my Professional English class. This part has be ...

  6. NOSQL数据库之 REDIS

    NOSQL数据库之 REDIS 一.NOSQL 1.简介 NoSQL ,(Not Only SQL),泛指非关系型数据库. 特点: NoSQL 通常是以key-value形式存储, 不支持SQL语句, ...

  7. 【python之路33】开发模式单例模式及简单的服务器请求框架原理

    1.单例模式指的是创建单个实例,例如:数据库连接池中包含10个数据库连接,用户访问数据时随机从连接池中拿出一个进行连接,其他用户再次访问时不再创建对象进行连接 #!usr/bin/env python ...

  8. bzoj 1800 [Ahoi2009]fly 飞行棋——模拟

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1800 原来只想到一个弧是一条边. 然后发现不是.差点不会做.经Zinn提醒,不用枚举那条边由 ...

  9. Composer项目安装依赖包

    说明:一般用Composer的项目中,目录下都有一个composer.json文件,安装这些依赖,只需要输入命令即可 步骤: cmd进入项目目录,运行命令即可 composer install 文章参 ...

  10. 使用SpringBoot发送mail邮件

    1.前言 发送邮件应该是网站的必备拓展功能之一,注册验证,忘记密码或者是给用户发送营销信息.正常我们会用JavaMail相关api来写发送邮件的相关代码,但现在springboot提供了一套更简易使用 ...