UE5中的四元数
- 绕任意轴旋转
- 四元数与矩阵
- 四元数与欧拉角
- 将一个向量旋转到另一个向量
- 插值
- Reference
我们知道,四元数是除了欧拉角,旋转矩阵之外,主要用来描述旋转的量。四元数直观的定义就是
q = [ c o s ( θ 2 ) , s i n ( θ 2 ) N ] q = [cos(\frac{\theta}{2}), sin(\frac{\theta}{2})N] q=[cos(2θ),sin(2θ)N]
其中,N表示旋转轴单位向量, θ \theta θ表示旋转的角度。那么,给定一个任意向量V,绕N旋转 θ \theta θ角度之后的V’可以用四元数乘法表示。令四元数v = [0, V],v’ = [0, V’],那么
v ′ = q v q ∗ v' = qvq^* v′=qvq∗
我们先来推导一下这个公式。
绕任意轴旋转
首先,四元数的乘法公式为
q 1 = [ s , v ] q_1 = [s, \textbf{v}] q1=[s,v]
q 2 = [ t , u ] q_2 = [t, \textbf{u}] q2=[t,u]
q 1 q 2 = [ s t − v ⋅ u , s u + t v + v × u ] q_1q_2 = [st - \textbf{v} \cdot \textbf{u}, s\textbf{u} + t\textbf{v} + \textbf{v} \times \textbf{u}] q1q2=[st−v⋅u,su+tv+v×u]
那么
v q ∗ = [ s i n ( θ 2 ) N ⋅ V , c o s ( θ 2 ) V + s i n ( θ 2 ) N × V ] vq^* = [sin(\frac{\theta}{2})N \cdot V, cos(\frac{\theta}{2}) V + sin(\frac{\theta}{2}) N \times V] vq∗=[sin(2θ)N⋅V,cos(2θ)V+sin(2θ)N×V]
q v q ∗ = [ s i n ( θ 2 ) c o s ( θ 2 ) N ⋅ V − s i n ( θ 2 ) N ⋅ ( c o s ( θ 2 ) V + s i n ( θ 2 ) N × V ) , c o s ( θ 2 ) ( c o s ( θ 2 ) V + s i n ( θ 2 ) N × V ) + s i n 2 ( θ 2 ) ( N ⋅ V ) N + s i n ( θ 2 ) N × ( c o s ( θ 2 ) V + s i n ( θ 2 ) N × V ) ] qvq^* = [ sin(\frac{\theta}{2})cos(\frac{\theta}{2})N \cdot V - sin(\frac{\theta}{2})N \cdot (cos(\frac{\theta}{2}) V + sin(\frac{\theta}{2}) N \times V), \\ cos(\frac{\theta}{2})(cos(\frac{\theta}{2}) V + sin(\frac{\theta}{2}) N \times V) + sin^2(\frac{\theta}{2})(N \cdot V)N + sin(\frac{\theta}{2}) N \times (cos(\frac{\theta}{2}) V + sin(\frac{\theta}{2}) N \times V) ] qvq∗=[sin(2θ)cos(2θ)N⋅V−sin(2θ)N⋅(cos(2θ)V+sin(2θ)N×V),cos(2θ)(cos(2θ)V+sin(2θ)N×V)+sin2(2θ)(N⋅V)N+sin(2θ)N×(cos(2θ)V+sin(2θ)N×V)]
我们先对结果的实部进行化简,因为点乘满足分配律,所以有
s i n ( θ 2 ) c o s ( θ 2 ) N ⋅ V − s i n ( θ 2 ) N ⋅ ( c o s ( θ 2 ) V + s i n ( θ 2 ) N × V ) = s i n ( θ 2 ) c o s ( θ 2 ) N ⋅ V − s i n ( θ 2 ) c o s ( θ 2 ) N ⋅ V − s i n 2 ( θ 2 ) N ⋅ ( N × V ) = − s i n 2 ( θ 2 ) N ⋅ ( N × V ) sin(\frac{\theta}{2})cos(\frac{\theta}{2})N \cdot V - sin(\frac{\theta}{2})N \cdot (cos(\frac{\theta}{2}) V + sin(\frac{\theta}{2}) N \times V) \\ = sin(\frac{\theta}{2})cos(\frac{\theta}{2})N \cdot V - sin(\frac{\theta}{2})cos(\frac{\theta}{2})N \cdot V - sin^2(\frac{\theta}{2})N \cdot (N \times V) \\ = - sin^2(\frac{\theta}{2})N \cdot (N \times V) sin(2θ)cos(2θ)N⋅V−sin(2θ)N⋅(cos(2θ)V+sin(2θ)N×V)=sin(2θ)cos(2θ)N⋅V−sin(2θ)cos(2θ)N⋅V−sin2(2θ)N⋅(N×V)=−sin2(2θ)N⋅(N×V)
N和V的叉乘显然与N垂直,所以点积为0,因此
− s i n 2 ( θ 2 ) N ⋅ ( N × V ) = 0 -sin^2(\frac{\theta}{2})N \cdot (N \times V) = 0 −sin2(2θ)N⋅(N×V)=0
再看虚部,同样叉乘也满足分配律,所以有
c o s ( θ 2 ) ( c o s ( θ 2 ) V + s i n ( θ 2 ) N × V ) + s i n 2 ( θ 2 ) ( N ⋅ V ) N + s i n ( θ 2 ) N × ( c o s ( θ 2 ) V + s i n ( θ 2 ) N × V ) = c o s 2 ( θ 2 ) V + s i n ( θ 2 ) c o s ( θ 2 ) N × V + s i n 2 ( θ 2 ) ( N ⋅ V ) N + s i n ( θ 2 ) c o s ( θ 2 ) N × V + s i n 2 ( θ 2 ) N × ( N × V ) cos(\frac{\theta}{2})(cos(\frac{\theta}{2}) V + sin(\frac{\theta}{2}) N \times V) + sin^2(\frac{\theta}{2})(N \cdot V)N + sin(\frac{\theta}{2}) N \times (cos(\frac{\theta}{2}) V + sin(\frac{\theta}{2}) N \times V) \\ = cos^2(\frac{\theta}{2})V + sin(\frac{\theta}{2})cos(\frac{\theta}{2})N \times V + sin^2(\frac{\theta}{2})(N \cdot V)N + sin(\frac{\theta}{2})cos(\frac{\theta}{2}) N \times V + sin^2(\frac{\theta}{2})N \times (N \times V) cos(2θ)(cos(2θ)V+sin(2θ)N×V)+sin2(2θ)(N⋅V)N+sin(2θ)N×(cos(2θ)V+sin(2θ)N×V)=cos2(2θ)V+sin(2θ)cos(2θ)N×V+sin2(2θ)(N⋅V)N+sin(2θ)cos(2θ)N×V+sin2(2θ)N×(N×V)
注意,叉乘并不满足结合律,且有
a × ( b × c ) = ( a ⋅ c ) b − ( a ⋅ b ) ⋅ c \textbf{a} \times (\textbf{b} \times \textbf{c}) = (\textbf{a} \cdot \textbf{c})\textbf{b} - (\textbf{a} \cdot \textbf{b}) \cdot \textbf{c} a×(b×c)=(a⋅c)b−(a⋅b)⋅c
所以上式进一步化简得到
c o s 2 ( θ 2 ) V + 2 s i n ( θ 2 ) c o s ( θ 2 ) N × V + s i n 2 ( θ 2 ) ( N ⋅ V ) N + s i n 2 ( θ 2 ) ( ( N ⋅ V ) N − V ) = ( c o s 2 ( θ 2 ) − s i n 2 ( θ 2 ) ) V + 2 s i n 2 ( θ 2 ) ( ( N ⋅ V ) N + 2 s i n ( θ 2 ) c o s ( θ 2 ) N × V = c o s θ V + ( 1 − c o s θ ) ( N ⋅ V ) N + s i n θ ( N × V ) cos^2(\frac{\theta}{2})V + 2sin(\frac{\theta}{2})cos(\frac{\theta}{2})N \times V + sin^2(\frac{\theta}{2})(N \cdot V)N + sin^2(\frac{\theta}{2})((N \cdot V)N - V) \\ = (cos^2(\frac{\theta}{2}) - sin^2(\frac{\theta}{2}))V + 2sin^2(\frac{\theta}{2})((N \cdot V)N + 2sin(\frac{\theta}{2})cos(\frac{\theta}{2})N \times V \\ = cos\theta V + (1- cos\theta)(N \cdot V)N + sin\theta(N \times V) cos2(2θ)V+2sin(2θ)cos(2θ)N×V+sin2(2θ)(N⋅V)N+sin2(2θ)((N⋅V)N−V)=(cos2(2θ)−sin2(2θ))V+2sin2(2θ)((N⋅V)N+2sin(2θ)cos(2θ)N×V=cosθV+(1−cosθ)(N⋅V)N+sinθ(N×V)
不难发现,这个就是绕任意轴旋转的公式。
当然,在真正实现中,没必要按部就班地用这种方式计算。UE实现用四元数旋转任意向量的代码如下:
template<typename T>
FORCEINLINE TVector<T> TQuat<T>::RotateVector(TVector<T> V) const
{ // http://people.csail.mit.edu/bkph/articles/Quaternions.pdf// V' = V + 2w(Q x V) + (2Q x (Q x V))// refactor:// V' = V + w(2(Q x V)) + (Q x (2(Q x V)))// T = 2(Q x V);// V' = V + w*(T) + (Q x T)const TVector<T> Q(X, Y, Z);const TVector<T> TT = 2.f * TVector<T>::CrossProduct(Q, V);const TVector<T> Result = V + (W * TT) + TVector<T>::CrossProduct(Q, TT);return Result;
}
UE所用的其实就是上述公式的反推。推导如下:
c o s θ V + ( 1 − c o s θ ) ( N ⋅ V ) N + s i n θ ( N × V ) = V − 2 s i n 2 ( θ 2 ) V + 2 s i n 2 ( θ 2 ) ( N ⋅ V ) N + 2 s i n ( θ 2 ) c o s ( θ 2 ) ( N × V ) = V − 2 s i n 2 ( θ 2 ) V + 2 ( Q ⋅ V ) Q + 2 w ( Q × V ) = V − 2 s i n 2 ( θ 2 ) V + 2 ( Q × ( Q × V ) + ( Q ⋅ Q ) V ) + 2 w ( Q × V ) = V − 2 s i n 2 ( θ 2 ) V + 2 Q × ( Q × V ) + 2 s i n 2 ( θ 2 ) V + 2 w ( Q × V ) = V + 2 w ( Q × V ) + 2 Q × ( Q × V ) cos\theta V + (1- cos\theta)(N \cdot V)N + sin\theta(N \times V) \\ = V -2sin^2(\frac{\theta}{2})V + 2sin^2(\frac{\theta}{2})(N \cdot V)N + 2sin(\frac{\theta}{2})cos(\frac{\theta}{2})(N \times V) \\ = V -2sin^2(\frac{\theta}{2})V + 2(Q \cdot V)Q + 2w(Q \times V) \\ = V -2sin^2(\frac{\theta}{2})V + 2(Q \times (Q \times V) + (Q \cdot Q)V) + 2w(Q \times V) \\ = V -2sin^2(\frac{\theta}{2})V + 2Q \times (Q \times V) + 2sin^2(\frac{\theta}{2})V + 2w(Q \times V) \\ = V + 2w(Q \times V) + 2Q \times (Q \times V) cosθV+(1−cosθ)(N⋅V)N+sinθ(N×V)=V−2sin2(2θ)V+2sin2(2θ)(N⋅V)N+2sin(2θ)cos(2θ)(N×V)=V−2sin2(2θ)V+2(Q⋅V)Q+2w(Q×V)=V−2sin2(2θ)V+2(Q×(Q×V)+(Q⋅Q)V)+2w(Q×V)=V−2sin2(2θ)V+2Q×(Q×V)+2sin2(2θ)V+2w(Q×V)=V+2w(Q×V)+2Q×(Q×V)
四元数与矩阵
我们知道,矩阵也可以用来表示3D的旋转。绕任意轴旋转的矩阵为
( c o s α + r x 2 ( 1 − c o s α ) r x r y ( 1 − c o s α ) + r z s i n α r x r z ( 1 − c o s α ) − r y s i n α 0 r x r y ( 1 − c o s α ) − r z s i n α c o s α + r y 2 ( 1 − c o s α ) r y r z ( 1 − c o s α ) + r x s i n α 0 r x r z ( 1 − c o s α ) + r y s i n α r y r z ( 1 − c o s α ) − r x s i n α c o s α + r z 2 ( 1 − c o s α ) 0 0 0 0 1 ) \begin{pmatrix} cos\alpha + r_x^2(1 - cos\alpha) & r_xr_y(1 - cos\alpha) + r_zsin\alpha & r_xr_z(1 - cos\alpha) - r_ysin\alpha & 0 \\ r_xr_y(1 - cos\alpha) - r_zsin\alpha & cos\alpha + r_y^2(1 - cos\alpha) & r_yr_z(1 - cos\alpha) + r_xsin\alpha & 0 \\ r_xr_z(1 - cos\alpha) + r_ysin\alpha & r_yr_z(1 - cos\alpha) - r_xsin\alpha & cos\alpha + r_z^2(1 - cos\alpha) & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} cosα+rx2(1−cosα)rxry(1−cosα)−rzsinαrxrz(1−cosα)+rysinα0rxry(1−cosα)+rzsinαcosα+ry2(1−cosα)ryrz(1−cosα)−rxsinα0rxrz(1−cosα)−rysinαryrz(1−cosα)+rxsinαcosα+rz2(1−cosα)00001
假设四元数为
q = [ w , x , y , z ] q = [w, x, y, z] q=[w,x,y,z]
对于矩阵的第一行第一列,有
c o s α + r x 2 ( 1 − c o s α ) = 2 c o s 2 α 2 − 1 + r x 2 ( 2 s i n 2 α 2 ) = 2 w 2 − 1 + 2 ( r x s i n α 2 ) 2 = 2 w 2 − 1 + 2 x 2 = 2 ( 1 − y 2 − z 2 ) − 1 = 1 − 2 ( y 2 + z 2 ) cos\alpha + r_x^2(1 - cos\alpha) \\ = 2cos^2\dfrac{\alpha}{2} - 1 + r_x^2(2sin^2\dfrac{\alpha}{2}) \\ = 2w^2 - 1 + 2(r_x sin\dfrac{\alpha}{2})^2 \\ = 2w^2 - 1 + 2x^2 \\ = 2(1 - y^2 - z^2) - 1 \\ = 1 - 2(y^2 + z^2) cosα+rx2(1−cosα)=2cos22α−1+rx2(2sin22α)=2w2−1+2(rxsin2α)2=2w2−1+2x2=2(1−y2−z2)−1=1−2(y2+z2)
对于矩阵的第一行第二列,有
r x r y ( 1 − c o s α ) + r z s i n α = r x r y ( 2 s i n 2 α 2 ) + r z ( 2 s i n α 2 c o s α 2 ) = 2 ( r x s i n α 2 ) ( r y s i n α 2 ) + 2 c o s α 2 ( r z s i n α 2 ) = 2 x y + 2 w z r_xr_y(1 - cos\alpha) + r_zsin\alpha \\ = r_xr_y(2sin^2\dfrac{\alpha}{2}) + r_z(2sin\dfrac{\alpha}{2}cos\dfrac{\alpha}{2}) \\ = 2(r_xsin\dfrac{\alpha}{2})(r_ysin\dfrac{\alpha}{2}) + 2cos\dfrac{\alpha}{2}(r_zsin\dfrac{\alpha}{2}) \\ = 2xy + 2wz rxry(1−cosα)+rzsinα=rxry(2sin22α)+rz(2sin2αcos2α)=2(rxsin2α)(rysin2α)+2cos2α(rzsin2α)=2xy+2wz
其他部分类似,最终可得到用四元数表示的旋转矩阵为
( 1 − 2 ( y 2 + z 2 ) 2 x y + 2 w z 2 x z − 2 w y 0 2 x y − 2 w z 1 − 2 ( x 2 + z 2 ) 2 y z + 2 w x 0 2 x z + 2 w y 2 y z − 2 w z 1 − 2 ( x 2 + y 2 ) 0 0 0 0 1 ) \begin{pmatrix} 1 - 2(y^2 + z^2) & 2xy + 2wz & 2xz - 2wy & 0 \\ 2xy - 2wz & 1 - 2(x^2 + z^2) & 2yz + 2wx & 0 \\ 2xz + 2wy & 2yz - 2wz & 1 - 2(x^2 + y^2) & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} 1−2(y2+z2)2xy−2wz2xz+2wy02xy+2wz1−2(x2+z2)2yz−2wz02xz−2wy2yz+2wx1−2(x2+y2)00001
与UE中四元数转矩阵的函数完全一致。
template<typename T>
void TQuat<T>::ToMatrix(TMatrix<T>& R) const
{// Note: copied and modified from TQuatRotationTranslationMatrix<> mainly to avoid circular dependency.
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) && WITH_EDITORONLY_DATA// Make sure Quaternion is normalizedensure(IsNormalized());
#endifconst T x2 = X + X; const T y2 = Y + Y; const T z2 = Z + Z;const T xx = X * x2; const T xy = X * y2; const T xz = X * z2;const T yy = Y * y2; const T yz = Y * z2; const T zz = Z * z2;const T wx = W * x2; const T wy = W * y2; const T wz = W * z2;R.M[0][0] = 1.0f - (yy + zz); R.M[1][0] = xy - wz; R.M[2][0] = xz + wy; R.M[3][0] = 0.0f;R.M[0][1] = xy + wz; R.M[1][1] = 1.0f - (xx + zz); R.M[2][1] = yz - wx; R.M[3][1] = 0.0f;R.M[0][2] = xz - wy; R.M[1][2] = yz + wx; R.M[2][2] = 1.0f - (xx + yy); R.M[3][2] = 0.0f;R.M[0][3] = 0.0f; R.M[1][3] = 0.0f; R.M[2][3] = 0.0f; R.M[3][3] = 1.0f;
}
那么,假如已知的是旋转矩阵,又如何解出四元数呢?我们取出矩阵中的3x3部分,计算3x3矩阵的trace
t r = ( 1 − 2 ( y 2 + z 2 ) ) + ( 1 − 2 ( x 2 + z 2 ) ) + ( 1 − 2 ( x 2 + y 2 ) ) = 3 − 4 ( x 2 + y 2 + z 2 ) = 4 w 2 − 1 tr = (1 - 2(y^2 + z^2)) + (1 - 2(x^2 + z^2)) + (1 - 2(x^2 + y^2)) \\ = 3 - 4(x^2 + y^2 + z^2) \\ = 4w^2 - 1 tr=(1−2(y2+z2))+(1−2(x2+z2))+(1−2(x2+y2))=3−4(x2+y2+z2)=4w2−1
如果trace大于0,就能解出w的值,进而快速解出x,y和z了,而如果trace小于等于0,则需要换一个思路。对于矩阵对角线上的三个元素,我们取出最大的元素,这里不妨假设
M [ 2 ] [ 2 ] > M [ 1 ] [ 1 ] > M [ 0 ] [ 0 ] M[2][2] > M[1][1] > M[0][0] M[2][2]>M[1][1]>M[0][0]
那么有
1 − 2 ( x 2 + y 2 ) > 1 − 2 ( x 2 + z 2 ) > 1 − 2 ( y 2 + z 2 ) x 2 + y 2 < x 2 + z 2 < y 2 + z 2 0 ≤ x 2 < y 2 < z 2 1 - 2(x^2 + y^2) > 1 - 2(x^2 + z^2) > 1 - 2(y^2 + z^2) \\ x^2 + y^2 < x^2 + z^2 < y^2 + z^2 \\ 0 \leq x^2 < y^2 < z^2 1−2(x2+y2)>1−2(x2+z2)>1−2(y2+z2)x2+y2<x2+z2<y2+z20≤x2<y2<z2
可以发现, z 2 z^2 z2必定大于0,它是一个可以作为分母的值,那么只需构造一个包含 z 2 z^2 z2的式子,把z解出即可。
M [ 2 ] [ 2 ] − M [ 1 ] [ 1 ] − M [ 0 ] [ 0 ] + 1 = 4 z 2 M[2][2] - M[1][1] - M[0][0] + 1 = 4z^2 M[2][2]−M[1][1]−M[0][0]+1=4z2
UE中的相关代码如下:
template<typename T>
inline TQuat<T>::TQuat(const UE::Math::TMatrix<T>& M)
{//const MeReal *const t = (MeReal *) tm;T s;// Check diagonal (trace)const T tr = M.M[0][0] + M.M[1][1] + M.M[2][2];if (tr > 0.0f) {T InvS = FMath::InvSqrt(tr + T(1.f));this->W = T(T(0.5f) * (T(1.f) / InvS));s = T(0.5f) * InvS;this->X = ((M.M[1][2] - M.M[2][1]) * s);this->Y = ((M.M[2][0] - M.M[0][2]) * s);this->Z = ((M.M[0][1] - M.M[1][0]) * s);} else {// diagonal is negativeint32 i = 0;if (M.M[1][1] > M.M[0][0])i = 1;if (M.M[2][2] > M.M[i][i])i = 2;static constexpr int32 nxt[3] = { 1, 2, 0 };const int32 j = nxt[i];const int32 k = nxt[j];s = M.M[i][i] - M.M[j][j] - M.M[k][k] + T(1.0f);T InvS = FMath::InvSqrt(s);T qt[4];qt[i] = T(0.5f) * (T(1.f) / InvS);s = T(0.5f) * InvS;qt[3] = (M.M[j][k] - M.M[k][j]) * s;qt[j] = (M.M[i][j] + M.M[j][i]) * s;qt[k] = (M.M[i][k] + M.M[k][i]) * s;this->X = qt[0];this->Y = qt[1];this->Z = qt[2];this->W = qt[3];DiagnosticCheckNaN();}
}
四元数与欧拉角
UE中的旋转顺序为先roll,再pitch,最后yaw的外旋旋转顺序。绕固定坐标系的X-Y-Z轴依次旋转α,β,γ角度可以得到最终的四元数为
q = q γ q β q α = [ c o s ( γ 2 ) 0 0 s i n ( γ 2 ) ] [ c o s ( β 2 ) 0 − s i n ( β 2 ) 0 ] [ c o s ( α 2 ) − s i n ( α 2 ) 0 0 ] = [ c o s ( α 2 ) c o s ( β 2 ) c o s ( γ 2 ) + s i n ( α 2 ) s i n ( β 2 ) s i n ( γ 2 ) c o s ( α 2 ) s i n ( β 2 ) s i n ( γ 2 ) − s i n ( α 2 ) c o s ( β 2 ) c o s ( γ 2 ) − c o s ( α 2 ) s i n ( β 2 ) c o s ( γ 2 ) − s i n ( α 2 ) c o s ( β 2 ) s i n ( γ 2 ) c o s ( α 2 ) c o s ( β 2 ) s i n ( γ 2 ) − s i n ( α 2 ) s i n ( β 2 ) c o s ( γ 2 ) ] q = q_{\gamma}q_{\beta}q_{\alpha} \\ = \begin{bmatrix} cos(\dfrac{\gamma}{2}) \\ 0 \\ 0 \\ sin(\dfrac{\gamma}{2}) \end{bmatrix} \begin{bmatrix} cos(\dfrac{\beta}{2}) \\ 0 \\ -sin(\dfrac{\beta}{2}) \\ 0 \end{bmatrix} \begin{bmatrix} cos(\dfrac{\alpha}{2}) \\ -sin(\dfrac{\alpha}{2}) \\ 0 \\ 0 \end{bmatrix} \\ = \begin{bmatrix} cos(\dfrac{\alpha}{2})cos(\dfrac{\beta}{2})cos(\dfrac{\gamma}{2}) + sin(\dfrac{\alpha}{2})sin(\dfrac{\beta}{2})sin(\dfrac{\gamma}{2}) \\ cos(\dfrac{\alpha}{2})sin(\dfrac{\beta}{2})sin(\dfrac{\gamma}{2}) - sin(\dfrac{\alpha}{2})cos(\dfrac{\beta}{2})cos(\dfrac{\gamma}{2}) \\ -cos(\dfrac{\alpha}{2})sin(\dfrac{\beta}{2})cos(\dfrac{\gamma}{2}) - sin(\dfrac{\alpha}{2})cos(\dfrac{\beta}{2})sin(\dfrac{\gamma}{2}) \\ cos(\dfrac{\alpha}{2})cos(\dfrac{\beta}{2})sin(\dfrac{\gamma}{2}) - sin(\dfrac{\alpha}{2})sin(\dfrac{\beta}{2})cos(\dfrac{\gamma}{2}) \end{bmatrix} q=qγqβqα= cos(2γ)00sin(2γ) cos(2β)0−sin(2β)0 cos(2α)−sin(2α)00 = cos(2α)cos(2β)cos(2γ)+sin(2α)sin(2β)sin(2γ)cos(2α)sin(2β)sin(2γ)−sin(2α)cos(2β)cos(2γ)−cos(2α)sin(2β)cos(2γ)−sin(2α)cos(2β)sin(2γ)cos(2α)cos(2β)sin(2γ)−sin(2α)sin(2β)cos(2γ)
根据上述公式,解出roll,pitch,和yaw
α = a r c t a n ( − 2 ( w x + y z ) 1 − 2 ( x 2 + y 2 ) ) \alpha = arctan(\dfrac{-2(wx + yz)}{1 - 2(x^2 + y^2)}) α=arctan(1−2(x2+y2)−2(wx+yz))
β = a r c s i n ( 2 ( z x − w y ) ) \beta = arcsin(2(zx - wy)) β=arcsin(2(zx−wy))
γ = a r c t a n ( 2 ( w z + x y ) 1 − 2 ( y 2 + z 2 ) ) \gamma = arctan(\dfrac{2(wz + xy)}{1 - 2(y^2 + z^2)}) γ=arctan(1−2(y2+z2)2(wz+xy))
不过这里有个特殊情况,我们知道欧拉角是可能导致万向锁的,例如当pitch = 90°时,roll和yaw会变成一个自由度。此时四元数的值为
q = [ 2 2 c o s ( α − γ 2 ) − 2 2 s i n ( α − γ 2 ) − 2 2 c o s ( α − γ 2 ) − 2 2 s i n ( α − γ 2 ) ] q = \begin{bmatrix} \dfrac{\sqrt 2}{2} cos(\dfrac{\alpha - \gamma}{2}) \\ -\dfrac{\sqrt 2}{2} sin(\dfrac{\alpha - \gamma}{2}) \\ -\dfrac{\sqrt 2}{2} cos(\dfrac{\alpha - \gamma}{2}) \\ -\dfrac{\sqrt 2}{2} sin(\dfrac{\alpha - \gamma}{2}) \end{bmatrix} q= 22cos(2α−γ)−22sin(2α−γ)−22cos(2α−γ)−22sin(2α−γ)
可以发现此时
w x + y z = 0 wx + yz = 0 wx+yz=0
w z + x y = 0 wz + xy = 0 wz+xy=0
y 2 + z 2 = 1 2 y^2 + z^2 = \dfrac{1}{2} y2+z2=21
x 2 + y 2 = 1 2 x^2 + y^2 = \dfrac{1}{2} x2+y2=21
这会导致arctan里出现0除0,因此并不能采用上述推导的公式进行计算。我们知道,pitch = 90°时当且仅当
z x − w y = 1 2 zx - wy = \dfrac{1}{2} zx−wy=21
并且
t a n α − γ 2 = − x w tan \dfrac{\alpha - \gamma}{2} = -\dfrac{x}{w} tan2α−γ=−wx
即
α = γ − 2 a r c t a n ( x w ) \alpha = \gamma - 2arctan(\dfrac{x}{w}) α=γ−2arctan(wx)
通常令yaw=0°,得到
α = − 2 a r c t a n ( x w ) \alpha = -2arctan(\dfrac{x}{w}) α=−2arctan(wx)
UE相关的代码如下,这是四元数转欧拉角的部分:
template<>
FRotator3f FQuat4f::Rotator() const
{DiagnosticCheckNaN();const float SingularityTest = Z * X - W * Y;const float YawY = 2.f * (W * Z + X * Y);const float YawX = (1.f - 2.f * (FMath::Square(Y) + FMath::Square(Z)));// reference // http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles// http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/// this value was found from experience, the above websites recommend different values// but that isn't the case for us, so I went through different testing, and finally found the case // where both of world lives happily. const float SINGULARITY_THRESHOLD = 0.4999995f;const float RAD_TO_DEG = (180.f / UE_PI);float Pitch, Yaw, Roll;if (SingularityTest < -SINGULARITY_THRESHOLD){Pitch = -90.f;Yaw = (FMath::Atan2(YawY, YawX) * RAD_TO_DEG);Roll = FRotator3f::NormalizeAxis(-Yaw - (2.f * FMath::Atan2(X, W) * RAD_TO_DEG));}else if (SingularityTest > SINGULARITY_THRESHOLD){Pitch = 90.f;Yaw = (FMath::Atan2(YawY, YawX) * RAD_TO_DEG);Roll = FRotator3f::NormalizeAxis(Yaw - (2.f * FMath::Atan2(X, W) * RAD_TO_DEG));}else{Pitch = (FMath::FastAsin(2.f * SingularityTest) * RAD_TO_DEG);Yaw = (FMath::Atan2(YawY, YawX) * RAD_TO_DEG);Roll = (FMath::Atan2(-2.f * (W*X + Y*Z), (1.f - 2.f * (FMath::Square(X) + FMath::Square(Y)))) * RAD_TO_DEG);}FRotator3f RotatorFromQuat = FRotator3f(Pitch, Yaw, Roll);return RotatorFromQuat;
}
这是欧拉角转四元数的部分:
template<>
FQuat4f FRotator3f::Quaternion() const
{const float DEG_TO_RAD = UE_PI/(180.f);const float RADS_DIVIDED_BY_2 = DEG_TO_RAD/2.f;float SP, SY, SR;float CP, CY, CR;const float PitchNoWinding = FMath::Fmod(Pitch, 360.0f);const float YawNoWinding = FMath::Fmod(Yaw, 360.0f);const float RollNoWinding = FMath::Fmod(Roll, 360.0f);FMath::SinCos(&SP, &CP, PitchNoWinding * RADS_DIVIDED_BY_2);FMath::SinCos(&SY, &CY, YawNoWinding * RADS_DIVIDED_BY_2);FMath::SinCos(&SR, &CR, RollNoWinding * RADS_DIVIDED_BY_2);FQuat4f RotationQuat;RotationQuat.X = CR*SP*SY - SR*CP*CY;RotationQuat.Y = -CR*SP*CY - SR*CP*SY;RotationQuat.Z = CR*CP*SY - SR*SP*CY;RotationQuat.W = CR*CP*CY + SR*SP*SY;return RotationQuat;
}
将一个向量旋转到另一个向量
实际应用中经常会遇到已知一个向量A,如何将其旋转到另外一个向量B上。想要构造表示旋转的四元数,只需计算出旋转轴N和旋转的角度θ即可。
q = [ c o s ( θ 2 ) , s i n ( θ 2 ) N ∣ ∣ N ∣ ∣ ] q = [cos(\frac{\theta}{2}), sin(\frac{\theta}{2})\dfrac{N}{||N||}] q=[cos(2θ),sin(2θ)∣∣N∣∣N]
旋转轴可由两个向量的叉乘得到,假设两个向量均为单位向量,那么
q = [ c o s ( θ 2 ) , s i n ( θ 2 ) A × B s i n θ ] = [ c o s ( θ 2 ) , A × B 2 c o s ( θ 2 ) ] q = [cos(\frac{\theta}{2}), sin(\frac{\theta}{2})\dfrac{A \times B}{sin\theta}] \\ = [cos(\frac{\theta}{2}), \dfrac{A \times B}{2cos(\frac{\theta}{2})}] q=[cos(2θ),sin(2θ)sinθA×B]=[cos(2θ),2cos(2θ)A×B]
再对q进行数乘
2 c o s ( θ 2 ) q = [ 2 c o s 2 ( θ 2 ) , A × B ] = [ 1 + c o s θ , A × B ] = [ 1 + A ⋅ B , A × B ] 2cos(\frac{\theta}{2})q = [2cos^2(\frac{\theta}{2}), A \times B] \\ = [1 + cos\theta, A \times B] \\ = [1 + A \cdot B, A \times B] 2cos(2θ)q=[2cos2(2θ),A×B]=[1+cosθ,A×B]=[1+A⋅B,A×B]
这样,我们可以仅用两个向量的点乘和叉乘,就能得到表示旋转的四元数,最后将其归一化即可。不过,这里还需要考虑两个向量共线的特殊情况。当两个向量共线时,叉乘为0向量,点乘为±1。可以看到,如果两个向量方向相同,得到的四元数为[1, 0, 0, 0],即为表示旋转角度为0的四元数;而如果两个向量方向相反,得到四元数为[0, 0, 0, 0],显然是不正确的。此时只需要再指定一个向量,一般取x,y,z值当中较小的单位轴,然后在此基础上再构建出一个旋转轴即可。
UE中相关代码如下:
template<typename T>
FORCEINLINE_DEBUGGABLE TQuat<T> FindBetween_Helper(const TVector<T>& A, const TVector<T>& B, T NormAB)
{T W = NormAB + TVector<T>::DotProduct(A, B);TQuat<T> Result;if (W >= 1e-6f * NormAB){//Result = FVector::CrossProduct(A, B);Result = TQuat<T>(A.Y * B.Z - A.Z * B.Y,A.Z * B.X - A.X * B.Z,A.X * B.Y - A.Y * B.X,W);}else{// A and B point in opposite directionsW = 0.f;const T X = FMath::Abs(A.X);const T Y = FMath::Abs(A.Y);const T Z = FMath::Abs(A.Z);// Find orthogonal basis. const TVector<T> Basis = (X > Y && X > Z) ? TVector<T>::YAxisVector : -TVector<T>::XAxisVector;//Result = FVector::CrossProduct(A, Basis);Result = TQuat<T>(A.Y * Basis.Z - A.Z * Basis.Y,A.Z * Basis.X - A.X * Basis.Z,A.X * Basis.Y - A.Y * Basis.X,W);}Result.Normalize();return Result;
}
插值
假设有四元数 q 0 q_0 q0和 q 1 q_1 q1,如何从 q 0 q_0 q0变换到 q 1 q_1 q1呢?我们知道四元数本身可以表示旋转,所以如果有向量v,那么
经过 q 0 q_0 q0变换有
v 0 = q 0 v q 0 ∗ v_0 = q_0 v q_0^* v0=q0vq0∗
经过 q 1 q_1 q1变换有
v 1 = q 1 v q 1 ∗ v_1 = q_1 v q_1^* v1=q1vq1∗
换言之,从 q 0 q_0 q0变换到 q 1 q_1 q1,可以认为等同于将 v 0 v_0 v0变换到 v 1 v_1 v1,假设变换的四元数为 Δ q \Delta q Δq,有
v 1 = Δ q v 0 Δ q ∗ v_1 = \Delta q v_0 \Delta q^* v1=Δqv0Δq∗
q 1 v q 1 ∗ = ( Δ q q 0 ) v ( Δ q q 0 ) ∗ q_1 v q_1^* = (\Delta q q_0) v (\Delta q q_0)^* q1vq1∗=(Δqq0)v(Δqq0)∗
所以
q 1 = Δ q q 0 q_1 = \Delta q q_0 q1=Δqq0
Δ q = q 1 q 0 ∗ Δ q = [ c o s ( θ 1 2 ) , s i n ( θ 1 2 ) N 1 ] [ c o s ( θ 0 2 ) , − s i n ( θ 0 2 ) N 0 ] \Delta q = q_1 q_0^* \\ \Delta q = [cos(\frac{\theta_1}{2}), sin(\frac{\theta_1}{2})N_1] [cos(\frac{\theta_0}{2}), -sin(\frac{\theta_0}{2})N_0] \\ Δq=q1q0∗Δq=[cos(2θ1),sin(2θ1)N1][cos(2θ0),−sin(2θ0)N0]
Δ q \Delta q Δq的实部部分,刚好是把 q 0 q_0 q0, q 1 q_1 q1视为四维向量的点乘。而 Δ q \Delta q Δq本身也可以表示旋转,它的实部也可写成
c o s ϕ 2 = q 0 ⋅ q 1 = ∣ ∣ q 0 ∣ ∣ ∣ ∣ q 1 ∣ ∣ c o s θ = c o s θ cos \dfrac{\phi}{2} = q_0 \cdot q_1 = ||q_0|| ||q_1|| cos \theta = cos \theta cos2ϕ=q0⋅q1=∣∣q0∣∣∣∣q1∣∣cosθ=cosθ
也就是说, q 0 q_0 q0和 q 1 q_1 q1作为四维向量的夹角,刚好是它们之间的旋转变化量 Δ q \Delta q Δq所表示的角度的一半。
所以,对四元数进行插值,可以理解为对四维向量进行插值。如果做Slerp,假设四维向量间的夹角为θ,要插值的角度为tθ,得到的四维向量为
q t = a q 0 + b q 1 q_t = aq_0 + bq_1 qt=aq0+bq1
式子两边都点乘 q 0 q_0 q0,得到
q 0 q t = a q 0 2 + b q 0 q 1 q_0 q_t = a q_0^2 + b q_0 q_1 q0qt=aq02+bq0q1
c o s ( t θ ) = a + b c o s θ cos(t\theta) = a + b cos\theta cos(tθ)=a+bcosθ
式子两边都点乘 q 1 q_1 q1,得到
q 1 q t = a q 0 q 1 + b q 1 2 q_1 q_t = a q_0 q_1 + b q_1^2 q1qt=aq0q1+bq12
c o s ( ( 1 − t ) θ ) = a c o s θ + b cos((1 - t)\theta) = a cos \theta + b cos((1−t)θ)=acosθ+b
解方程得到
a = s i n ( ( 1 − t ) θ ) s i n θ a = \dfrac{sin((1 - t)\theta)}{sin \theta} a=sinθsin((1−t)θ)
b = s i n ( t θ ) s i n θ b = \dfrac{sin(t\theta)}{sin\theta} b=sinθsin(tθ)
所以
q t = s i n ( ( 1 − t ) θ ) s i n θ q 0 + s i n ( t θ ) s i n θ q 1 q_t = \dfrac{sin((1 - t)\theta)}{sin \theta} q_0 + \dfrac{sin(t\theta)}{sin\theta} q_1 qt=sinθsin((1−t)θ)q0+sinθsin(tθ)q1
值得注意的是,四元数和3D旋转并不是一一对应的,一个旋转既可以表示为绕旋转轴正方向旋转θ角度,也可以表示为绕旋转轴负方向旋转 2 π − θ 2\pi - \theta 2π−θ角度。
q 1 = [ c o s ( θ 2 ) , s i n ( θ 2 ) N ] q_1 = [cos(\frac{\theta}{2}), sin(\frac{\theta}{2})N] q1=[cos(2θ),sin(2θ)N]
q 2 = [ c o s ( 2 π − θ 2 ) , s i n ( 2 π − θ 2 ) ( − N ) ] = [ − c o s ( θ 2 ) , − s i n ( θ 2 ) N ] = − q 1 q_2 = [cos(\frac{2\pi - \theta}{2}), sin(\frac{2\pi - \theta}{2})(-N)] = [-cos(\frac{\theta}{2}), -sin(\frac{\theta}{2})N] = -q_1 q2=[cos(22π−θ),sin(22π−θ)(−N)]=[−cos(2θ),−sin(2θ)N]=−q1
可以看到表示同一旋转的两个四元数相互为负。所以在四元数插值时,要先选择一个最短的旋转路径。检测方式就是计算两个四元数的夹角,如果为负数将某一个取反即可。
另外,如果四元数的夹角很小,那么夹角的正弦值可能趋近于0,这会导致上述式子a,b出现除0的后果。为了避免这种情况发生,此时需要将Slerp换成普通的线性插值计算。
UE中相关代码如下:
template<typename T>
TQuat<T> TQuat<T>::Slerp_NotNormalized(const TQuat<T>& Quat1, const TQuat<T>& Quat2, T Slerp)
{// Get cosine of angle between quats.T RawCosom =Quat1.X * Quat2.X +Quat1.Y * Quat2.Y +Quat1.Z * Quat2.Z +Quat1.W * Quat2.W;// Unaligned quats - compensate, results in taking shorter route.const T Sign = FMath::FloatSelect(RawCosom, static_cast<T>(1.0), static_cast<T>(-1.0));RawCosom *= Sign;T Scale0 = static_cast<T>(1.0) - Slerp;T Scale1 = Slerp * Sign;if (RawCosom < static_cast<T>(0.9999)){const T Omega = FMath::Acos(RawCosom);const T InvSin = static_cast<T>(1.0) / FMath::Sin(Omega);Scale0 = FMath::Sin(Scale0 * Omega) * InvSin;Scale1 = FMath::Sin(Scale1 * Omega) * InvSin;}return TQuat<T>(Scale0 * Quat1.X + Scale1 * Quat2.X,Scale0 * Quat1.Y + Scale1 * Quat2.Y,Scale0 * Quat1.Z + Scale1 * Quat2.Z,Scale0 * Quat1.W + Scale1 * Quat2.W);
}
Reference
[1] 四元数与三维旋转
[2] 四元数与欧拉角(RPY角)的相互转换
[3] Efficiently Building a Matrix to Rotate One Vector to Another
[4] Householder变换
[5] CloudCompare/CloudCompare