UE5的TRS矩阵
- matrix
- translate
- rotate
- scale
- TRS顺序
- 绕任意轴旋转
- Reference
我们知道,常用的transform主要有三种,分别是translate,rotate,scale。接下来我们逐一看下在UE5中,如何分别使用TRS构造相应的矩阵,以及如何从矩阵中提取TRS。
matrix
UE中的矩阵是行主序,x,y,z基向量会以行向量的形式存储在矩阵中。矩阵的数据结构定义很简单,就是一个4x4的template二维数组:
/*** 4x4 matrix of floating point values.* Matrix-matrix multiplication happens with a pre-multiple of the transpose --* in other words, Res = Mat1.operator*(Mat2) means Res = Mat2^FArg * Mat1, as* opposed to Res = Mat1 * Mat2.* Matrix elements are accessed with M[RowIndex][ColumnIndex].*/template<typename T>
struct alignas(16) TMatrix
{static_assert(std::is_floating_point_v<T>, "T must be floating point");public:using FReal = T;alignas(16) T M[4][4];
};
translate
UE提供了TTranslationMatrix
struct来辅助构造平移矩阵。
template<typename T>
FORCEINLINE TTranslationMatrix<T>::TTranslationMatrix(const TVector<T>& Delta): TMatrix<T>(TPlane<T>(1.0f, 0.0f, 0.0f, 0.0f),TPlane<T>(0.0f, 1.0f, 0.0f, 0.0f),TPlane<T>(0.0f, 0.0f, 1.0f, 0.0f),TPlane<T>(Delta.X, Delta.Y,Delta.Z,1.0f))
{ }
这里,TPlane可以理解为一个简单的4维向量,可以看到平移向量Delta位于第4行,那么需要行向量右乘该矩阵,才能达到平移的结果。
( p x , p y , p z , 1 ) ( 1 0 0 0 0 1 0 0 0 0 1 0 t x t y t z 1 ) = ( p x + t x , p y + t y , p z + t z , 1 ) (p_x, p_y, p_z, 1) \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ t_x & t_y & t_z & 1 \end{pmatrix} = (p_x + t_x, p_y + t_y, p_z + t_z, 1) (px,py,pz,1) 100tx010ty001tz0001 =(px+tx,py+ty,pz+tz,1)
那么相应地,从矩阵中提取平移向量也很简单,返回矩阵的最后一行即可。
rotate
除四元数外,UE中还有一个用来专门表示旋转的数据结构,TRotator
。它和Unity的欧拉角概念类似,有3个分量,pitch,yaw,roll,分别表示绕这3个轴各自的旋转角度。UE在代码中详细解释了TRotator
的定义:
/*** Implements a container for rotation information.** All rotation values are stored in degrees.** The angles are interpreted as intrinsic rotations applied in the order Yaw, then Pitch, then Roll. I.e., an object would be rotated* first by the specified yaw around its up axis (with positive angles interpreted as clockwise when viewed from above, along -Z), * then pitched around its (new) right axis (with positive angles interpreted as 'nose up', i.e. clockwise when viewed along +Y), * and then finally rolled around its (final) forward axis (with positive angles interpreted as clockwise rotations when viewed along +X).** Note that these conventions differ from quaternion axis/angle. UE Quat always considers a positive angle to be a left-handed rotation, * whereas Rotator treats yaw as left-handed but pitch and roll as right-handed.* */
template<typename T>
struct TRotator
{public:using FReal = T;/** Rotation around the right axis (around Y axis), Looking up and down (0=Straight Ahead, +Up, -Down) */T Pitch;/** Rotation around the up axis (around Z axis), Turning around (0=Forward, +Right, -Left)*/T Yaw;/** Rotation around the forward axis (around X axis), Tilting your head, (0=Straight, +Clockwise, -CCW) */T Roll;
};
首先是pitch,yaw,roll这三个值的含义以及对应旋转的方向。由注释可知,pitch对应绕UE的y轴旋转,yaw对应绕UE的z轴旋转,roll对应绕UE的x轴旋转。
接下来看方向,由注释可知,pitch的正方向表示向上看:
yaw的正方向表示向右转:
roll的正方向为顺时针,在UE中x轴通常被认为是forward向量。
再看旋转顺序,由注释可知,UE定义旋转顺序为先yaw,再pitch,最后roll的内旋旋转顺序。所谓内旋,就是指坐标轴会随着一起旋转,相对的就是外旋了,即绕固定轴旋转。由于内旋可以看作是坐标系一起旋转了,那么被旋转的向量,其实可以认为,在旋转前的坐标系下,和旋转后的坐标系下,坐标其实是相同的。
内旋与外旋的对比图如下:
所以内旋本质上可以拆解为两步,第一步是把初始坐标系变换到经过yaw,pitch,roll之后的坐标系,此时被旋转的向量也跟着变换了;第二步再计算新坐标系下的被旋转的向量,在初始坐标系下的坐标。我们先看一下第一步中的Yaw矩阵,容易知道yaw变换后的坐标系,基向量的坐标分别为:
X = ( c o s θ , s i n θ , 0 ) X = (cos \theta, sin \theta, 0) X=(cosθ,sinθ,0)
Y = ( − s i n θ , c o s θ , 0 ) Y = (-sin\theta, cos\theta, 0) Y=(−sinθ,cosθ,0)
Z = ( 0 , 0 , 1 ) Z = (0, 0, 1) Z=(0,0,1)
那么yaw变换其实就是把三个基向量变换为:
X ′ = ( 1 , 0 , 0 ) X' = (1, 0, 0) X′=(1,0,0)
Y ′ = ( 0 , 1 , 0 ) Y' = (0, 1, 0) Y′=(0,1,0)
Z ′ = ( 0 , 0 , 1 ) Z' = (0, 0, 1) Z′=(0,0,1)
注意UE是行向量右乘矩阵,所以Yaw矩阵为
Y a w = ( c o s α − s i n α 0 0 s i n α c o s α 0 0 0 0 1 0 0 0 0 1 ) Yaw = \begin{pmatrix} cos\alpha & -sin\alpha & 0 & 0 \\ sin\alpha & cos\alpha & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} Yaw= cosαsinα00−sinαcosα0000100001
类似地,可以得到Pitch和Roll矩阵为
P i t c h = ( c o s β 0 − s i n β 0 0 1 0 0 s i n β 0 c o s β 0 0 0 0 1 ) Pitch = \begin{pmatrix} cos\beta & 0 & -sin\beta & 0 \\ 0 & 1 & 0 & 0 \\ sin\beta & 0 & cos\beta & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} Pitch= cosβ0sinβ00100−sinβ0cosβ00001
R o l l = ( 1 0 0 0 0 c o s γ s i n γ 0 0 − s i n γ c o s γ 0 0 0 0 1 ) Roll = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & cos\gamma & sin\gamma & 0 \\ 0 & -sin\gamma & cos\gamma & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} Roll= 10000cosγ−sinγ00sinγcosγ00001
那么第一步最后得到的坐标系变换矩阵为
M = Y a w ⋅ P i t c h ⋅ R o l l M = Yaw \cdot Pitch \cdot Roll M=Yaw⋅Pitch⋅Roll
第二步,需要计算新坐标系下的向量,在初始坐标系下的坐标。依然从基向量出发,新坐标系下的的基向量,显然为
X = ( 1 , 0 , 0 ) X = (1, 0, 0) X=(1,0,0)
Y = ( 0 , 1 , 0 ) Y = (0, 1, 0) Y=(0,1,0)
Z = ( 0 , 0 , 1 ) Z = (0, 0, 1) Z=(0,0,1)
初始坐标系的基向量,在新坐标系下的坐标,其实就是M矩阵的三个行向量:
X ’ = M [ 0 ] X’ = M[0] X’=M[0]
Y ′ = M [ 1 ] Y' = M[1] Y′=M[1]
Z ′ = M [ 2 ] Z' = M[2] Z′=M[2]
那么向量V,用新坐标系的基向量可表示为
V = ( v x , v y , v z , 0 ) ( X 0 Y 0 Z 0 0 1 ) = ( v x , v y , v z , 0 ) I V = (v_x,v_y,v_z, 0) \begin{pmatrix} X & 0\\ Y & 0\\ Z & 0 \\ \textbf{0} & 1 \end{pmatrix} = (v_x,v_y,v_z, 0)I V=(vx,vy,vz,0) XYZ00001 =(vx,vy,vz,0)I
而用初始坐标系的基向量可表示为
V = ( v x ′ , v y ′ , v z ′ , 0 ) ( X ′ 0 Y ′ 0 Z ′ 0 0 1 ) = ( v x ′ , v y ′ , v z ′ , 0 ) M V = (v'_x,v'_y,v'_z, 0) \begin{pmatrix} X' & 0\\ Y' & 0\\ Z' & 0 \\ \textbf{0} & 1 \end{pmatrix} = (v'_x,v'_y,v'_z, 0) M V=(vx′,vy′,vz′,0) X′Y′Z′00001 =(vx′,vy′,vz′,0)M
那么,向量V在初始坐标系下的坐标
( v x ′ , v y ′ , v z ′ , 0 ) = ( v x , v y , v z , 0 ) M − 1 = ( v x , v y , v z , 0 ) M T (v'_x,v'_y,v'_z, 0) = (v_x,v_y,v_z, 0)M^{-1} = (v_x,v_y,v_z, 0)M^T (vx′,vy′,vz′,0)=(vx,vy,vz,0)M−1=(vx,vy,vz,0)MT
所以最终的变换矩阵其实就是M矩阵的转置,根据矩阵的性质,进一步可以得到
M T = ( Y a w ⋅ P i t c h ⋅ R o l l ) T = R o l l T ⋅ P i t c h T ⋅ Y a w T M^T = (Yaw \cdot Pitch \cdot Roll)^T = Roll^T \cdot Pitch^T \cdot Yaw^T MT=(Yaw⋅Pitch⋅Roll)T=RollT⋅PitchT⋅YawT
Roll,Pitch,Yaw的转置矩阵就是对应外旋下的旋转矩阵。这也说明了,内旋和外旋影响三个轴的旋转矩阵相乘的顺序,但不影响最终的旋转矩阵结果,先yaw,再pitch,最后roll的内旋等价于先roll,再pitch,最后yaw的外旋。最终的变换矩阵如下:
( c o s α c o s β s i n α c o s β s i n β 0 c o s α s i n β s i n γ − s i n α c o s γ s i n α s i n β s i n γ + c o s α c o s γ − c o s β s i n γ 0 − ( c o s α s i n β c o s γ + s i n α s i n γ ) c o s α s i n γ − s i n α s i n β c o s γ c o s β c o s γ 0 0 0 0 1 ) \begin{pmatrix} cos\alpha cos\beta & sin\alpha cos\beta & sin\beta & 0 \\ cos\alpha sin\beta sin\gamma - sin\alpha cos\gamma & sin\alpha sin\beta sin\gamma + cos\alpha cos\gamma & -cos\beta sin\gamma & 0 \\ -(cos\alpha sin\beta cos\gamma + sin\alpha sin\gamma) & cos\alpha sin\gamma - sin\alpha sin\beta cos\gamma & cos\beta cos\gamma & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} cosαcosβcosαsinβsinγ−sinαcosγ−(cosαsinβcosγ+sinαsinγ)0sinαcosβsinαsinβsinγ+cosαcosγcosαsinγ−sinαsinβcosγ0sinβ−cosβsinγcosβcosγ00001
与UE代码的旋转部分完全一致:
template<typename T>
FORCEINLINE TRotationTranslationMatrix<T>::TRotationTranslationMatrix(const TRotator<T>& Rot, const TVector<T>& Origin)
{M[0][0] = CP * CY;M[0][1] = CP * SY;M[0][2] = SP;M[0][3] = 0.f;M[1][0] = SR * SP * CY - CR * SY;M[1][1] = SR * SP * SY + CR * CY;M[1][2] = - SR * CP;M[1][3] = 0.f;M[2][0] = -( CR * SP * CY + SR * SY );M[2][1] = CY * SR - CR * SP * SY;M[2][2] = CR * CP;M[2][3] = 0.f;M[3][0] = Origin.X;M[3][1] = Origin.Y;M[3][2] = Origin.Z;M[3][3] = 1.f;
}
有了这个矩阵,我们还可以从数学的角度分析一下万向锁。我们假设
β = 9 0 ∘ \beta = 90^\circ β=90∘
也就是绕y轴旋转90度。那么
s i n β = 1 sin\beta = 1 sinβ=1
c o s β = 0 cos\beta = 0 cosβ=0
此时变换矩阵简化为
( 0 0 1 0 c o s α s i n γ − s i n α c o s γ s i n α s i n γ + c o s α c o s γ 0 0 − ( c o s α c o s γ + s i n α s i n γ ) c o s α s i n γ − s i n α c o s γ 0 0 0 0 0 1 ) \begin{pmatrix} 0 & 0 & 1 & 0 \\ cos\alpha sin\gamma - sin\alpha cos\gamma & sin\alpha sin\gamma + cos\alpha cos\gamma & 0 & 0 \\ -(cos\alpha cos\gamma + sin\alpha sin\gamma) & cos\alpha sin\gamma - sin\alpha cos\gamma & 0 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} 0cosαsinγ−sinαcosγ−(cosαcosγ+sinαsinγ)00sinαsinγ+cosαcosγcosαsinγ−sinαcosγ010000001
根据三角函数公式,有
( 0 0 1 0 s i n ( γ − α ) c o s ( γ − α ) 0 0 − c o s ( γ − α ) s i n ( γ − α ) 0 0 0 0 0 1 ) \begin{pmatrix} 0 & 0 & 1 & 0 \\ sin(\gamma - \alpha) & cos(\gamma - \alpha) & 0 & 0 \\ -cos(\gamma - \alpha) & sin(\gamma - \alpha) & 0 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} 0sin(γ−α)−cos(γ−α)00cos(γ−α)sin(γ−α)010000001
可以看到,这里的yaw和roll其实已经可以被一个变量所取代,也就是失去了一个自由的轴,这就是触发万向锁了。
反过来,从矩阵提取yaw,pitch,roll也很简单,就是些纯三角函数的计算了:
template<typename T>
UE::Math::TRotator<T> UE::Math::TMatrix<T>::Rotator() const
{using TRotator = UE::Math::TRotator<T>;using TVector = UE::Math::TVector<T>;const TVector XAxis = GetScaledAxis( EAxis::X );const TVector YAxis = GetScaledAxis( EAxis::Y );const TVector ZAxis = GetScaledAxis( EAxis::Z );const T RadToDeg = T(180.0 / UE_DOUBLE_PI);TRotator Rotator = TRotator(FMath::Atan2( XAxis.Z, FMath::Sqrt(FMath::Square(XAxis.X)+FMath::Square(XAxis.Y)) ) * RadToDeg,FMath::Atan2( XAxis.Y, XAxis.X ) * RadToDeg,0 );const TVector SYAxis = (TVector)UE::Math::TRotationMatrix<T>( Rotator ).GetScaledAxis( EAxis::Y );Rotator.Roll = FMath::Atan2( ZAxis | SYAxis, YAxis | SYAxis ) * RadToDeg;Rotator.DiagnosticCheckNaN();return Rotator;
}
scale
UE提供了辅助数据结构TScaleMatrix
来构造缩放矩阵:
template<typename T>
FORCEINLINE TScaleMatrix<T>::TScaleMatrix( const TVector<T>& Scale ): TMatrix<T>(TPlane<T>(Scale.X, 0.0f, 0.0f, 0.0f),TPlane<T>(0.0f, Scale.Y, 0.0f, 0.0f),TPlane<T>(0.0f, 0.0f, Scale.Z, 0.0f),TPlane<T>(0.0f, 0.0f, 0.0f, 1.0f))
{ }
从矩阵中提取scale也比较简单:
template<typename T>
inline TVector<T> TMatrix<T>::GetScaleVector(T Tolerance/*=UE_SMALL_NUMBER*/) const
{TVector<T> Scale3D(1, 1, 1);// For each row, find magnitude, and if its non-zero re-scale so its unit length.for (int32 i = 0; i < 3; i++){const T SquareSum = (M[i][0] * M[i][0]) + (M[i][1] * M[i][1]) + (M[i][2] * M[i][2]);if (SquareSum > Tolerance){Scale3D[i] = FMath::Sqrt(SquareSum);}else{Scale3D[i] = 0.f;}}return Scale3D;
}
TRS顺序
这一点UE和通用的规则保持一致,先缩放,再旋转,最后才是平移。UE提供TScaleRotationTranslationMatrix
这个数据结构来构造TRS矩阵。
template<typename T>
FORCEINLINE TScaleRotationTranslationMatrix<T>::TScaleRotationTranslationMatrix(const TVector<T>& Scale, const TRotator<T>& Rot, const TVector<T>& Origin)
{T SP, SY, SR;T CP, CY, CR;GetSinCos(SP, CP, (T)Rot.Pitch);GetSinCos(SY, CY, (T)Rot.Yaw);GetSinCos(SR, CR, (T)Rot.Roll);M[0][0] = (CP * CY) * Scale.X;M[0][1] = (CP * SY) * Scale.X;M[0][2] = (SP) * Scale.X;M[0][3] = 0.f;M[1][0] = (SR * SP * CY - CR * SY) * Scale.Y;M[1][1] = (SR * SP * SY + CR * CY) * Scale.Y;M[1][2] = (- SR * CP) * Scale.Y;M[1][3] = 0.f;M[2][0] = ( -( CR * SP * CY + SR * SY ) ) * Scale.Z;M[2][1] = (CY * SR - CR * SP * SY) * Scale.Z;M[2][2] = (CR * CP) * Scale.Z;M[2][3] = 0.f;M[3][0] = Origin.X;M[3][1] = Origin.Y;M[3][2] = Origin.Z;M[3][3] = 1.f;
}
绕任意轴旋转
UE也提供了绕任意轴旋转任意角度的变换矩阵:
template<typename T>
inline TVector<T> TVector<T>::RotateAngleAxisRad(const T AngleRad, const TVector<T>& Axis) const
{T S, C;FMath::SinCos(&S, &C, AngleRad);const T XX = Axis.X * Axis.X;const T YY = Axis.Y * Axis.Y;const T ZZ = Axis.Z * Axis.Z;const T XY = Axis.X * Axis.Y;const T YZ = Axis.Y * Axis.Z;const T ZX = Axis.Z * Axis.X;const T XS = Axis.X * S;const T YS = Axis.Y * S;const T ZS = Axis.Z * S;const T OMC = 1.f - C;return TVector<T>((OMC * XX + C) * X + (OMC * XY - ZS) * Y + (OMC * ZX + YS) * Z,(OMC * XY + ZS) * X + (OMC * YY + C) * Y + (OMC * YZ - XS) * Z,(OMC * ZX - YS) * X + (OMC * YZ + XS) * Y + (OMC * ZZ + C) * Z);
}
这个矩阵又是怎么来的呢?它有两种主流的推导方式。第一种,我们可以先把任意的旋转轴变换到某个基向量上(例如x轴),然后问题就转换成了绕x轴旋转任意角度,旋转之后再把旋转轴变换到原来的位置。
假设旋转轴为r,我们先基于r建立一个新的坐标系,r,s,t为三个基向量。那么矩阵M为
M = ( r x s x t x 0 r y s y t y 0 r z s z t z 0 0 0 0 1 ) M = \begin{pmatrix} r_x & s_x & t_x & 0 \\ r_y & s_y & t_y & 0 \\ r_z & s_z & t_z & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} M= rxryrz0sxsysz0txtytz00001
左手系下绕x轴旋转的矩阵为
R = ( 1 0 0 0 0 c o s α s i n α 0 0 − s i n α c o s α 0 0 0 0 1 ) R = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & cos\alpha & sin\alpha & 0 \\ 0 & -sin\alpha & cos\alpha & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} R= 10000cosα−sinα00sinαcosα00001
三个矩阵相乘可以得到
M R M T = ( r x 2 + ( s x 2 + t x 2 ) c o s α r x r y + ( s x s y + t x t y ) c o s α + ( s x t y − s y t x ) s i n α r x r z + ( s x s z + t x t z ) c o s α + ( s x t z − s z t x ) s i n α 0 r x r y + ( s x s y + t x t y ) c o s α + ( s y t x − s x t y ) s i n α r y 2 + ( s y 2 + t y 2 ) c o s α r y r z + ( s y s z + t y t z ) c o s α + ( s y t z − s z t y ) s i n α 0 r x r z + ( s x s z + t x t z ) c o s α + ( s z t x − s x t z ) s i n α r y r z + ( s y s z + t y t z ) c o s α + ( s z t y − s y t z ) s i n α r z 2 + ( s z 2 + t z 2 ) c o s α 0 0 0 0 1 ) MRM^T = \begin{pmatrix} r_x^2 + (s_x^2 + t_x^2)cos\alpha & r_xr_y + (s_xs_y + t_xt_y)cos\alpha + (s_xt_y - s_yt_x)sin\alpha & r_xr_z + (s_xs_z + t_xt_z)cos\alpha + (s_xt_z - s_zt_x)sin\alpha & 0 \\ r_xr_y + (s_xs_y + t_xt_y)cos\alpha + (s_yt_x - s_xt_y)sin\alpha & r_y^2 + (s_y^2 + t_y^2)cos\alpha & r_yr_z + (s_ys_z + t_yt_z)cos\alpha + (s_yt_z - s_zt_y)sin\alpha & 0 \\ r_xr_z + (s_xs_z + t_xt_z)cos\alpha + (s_zt_x - s_xt_z)sin\alpha & r_yr_z + (s_ys_z + t_yt_z)cos\alpha + (s_zt_y - s_yt_z)sin\alpha & r_z^2 + (s_z^2 + t_z^2)cos\alpha & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} MRMT= rx2+(sx2+tx2)cosαrxry+(sxsy+txty)cosα+(sytx−sxty)sinαrxrz+(sxsz+txtz)cosα+(sztx−sxtz)sinα0rxry+(sxsy+txty)cosα+(sxty−sytx)sinαry2+(sy2+ty2)cosαryrz+(sysz+tytz)cosα+(szty−sytz)sinα0rxrz+(sxsz+txtz)cosα+(sxtz−sztx)sinαryrz+(sysz+tytz)cosα+(sytz−szty)sinαrz2+(sz2+tz2)cosα00001
接下来想办法要把多余的s和t变量给消除掉。首先我们知道基向量的模为1,那么有
r x 2 + s x 2 + t x 2 = 1 r_x^2 + s_x^2 + t_x^2 = 1 rx2+sx2+tx2=1
r y 2 + s y 2 + t y 2 = 1 r_y^2 + s_y^2 + t_y^2 = 1 ry2+sy2+ty2=1
r z 2 + s z 2 + t z 2 = 1 r_z^2 + s_z^2 + t_z^2 = 1 rz2+sz2+tz2=1
那么上述矩阵可简化为
( c o s α + r x 2 ( 1 − c o s α ) r x r y + ( s x s y + t x t y ) c o s α + ( s x t y − s y t x ) s i n α r x r z + ( s x s z + t x t z ) c o s α + ( s x t z − s z t x ) s i n α 0 r x r y + ( s x s y + t x t y ) c o s α + ( s y t x − s x t y ) s i n α c o s α + r y 2 ( 1 − c o s α ) r y r z + ( s y s z + t y t z ) c o s α + ( s y t z − s z t y ) s i n α 0 r x r z + ( s x s z + t x t z ) c o s α + ( s z t x − s x t z ) s i n α r y r z + ( s y s z + t y t z ) c o s α + ( s z t y − s y t z ) 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 + (s_xs_y + t_xt_y)cos\alpha + (s_xt_y - s_yt_x)sin\alpha & r_xr_z + (s_xs_z + t_xt_z)cos\alpha + (s_xt_z - s_zt_x)sin\alpha & 0 \\ r_xr_y + (s_xs_y + t_xt_y)cos\alpha + (s_yt_x - s_xt_y)sin\alpha & cos\alpha + r_y^2(1 - cos\alpha) & r_yr_z + (s_ys_z + t_yt_z)cos\alpha + (s_yt_z - s_zt_y)sin\alpha & 0 \\ r_xr_z + (s_xs_z + t_xt_z)cos\alpha + (s_zt_x - s_xt_z)sin\alpha & r_yr_z + (s_ys_z + t_yt_z)cos\alpha + (s_zt_y - s_yt_z)sin\alpha & cos\alpha + r_z^2(1 - cos\alpha) & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} cosα+rx2(1−cosα)rxry+(sxsy+txty)cosα+(sytx−sxty)sinαrxrz+(sxsz+txtz)cosα+(sztx−sxtz)sinα0rxry+(sxsy+txty)cosα+(sxty−sytx)sinαcosα+ry2(1−cosα)ryrz+(sysz+tytz)cosα+(szty−sytz)sinα0rxrz+(sxsz+txtz)cosα+(sxtz−sztx)sinαryrz+(sysz+tytz)cosα+(sytz−szty)sinαcosα+rz2(1−cosα)00001
其次,基向量是相互正交的,也就是点积为0,那么有
r x r y + s x s y + t x t y = 0 r_xr_y + s_xs_y + t_xt_y = 0 rxry+sxsy+txty=0
r x r z + s x s z + t x t z = 0 r_xr_z + s_xs_z + t_xt_z = 0 rxrz+sxsz+txtz=0
r y r z + s y s z + t y t z = 0 r_yr_z + s_ys_z + t_yt_z = 0 ryrz+sysz+tytz=0
矩阵进一步简化为
( c o s α + r x 2 ( 1 − c o s α ) r x r y ( 1 − c o s α ) + ( s x t y − s y t x ) s i n α r x r z ( 1 − c o s α ) + ( s x t z − s z t x ) s i n α 0 r x r y ( 1 − c o s α ) + ( s y t x − s x t y ) s i n α c o s α + r y 2 ( 1 − c o s α ) r y r z ( 1 − c o s α ) + ( s y t z − s z t y ) s i n α 0 r x r z ( 1 − c o s α ) + ( s z t x − s x t z ) s i n α r y r z ( 1 − c o s α ) + ( s z t y − s y t z ) 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) + (s_xt_y - s_yt_x)sin\alpha & r_xr_z(1 - cos\alpha) + (s_xt_z - s_zt_x)sin\alpha & 0 \\ r_xr_y(1 - cos\alpha) + (s_yt_x - s_xt_y)sin\alpha & cos\alpha + r_y^2(1 - cos\alpha) & r_yr_z(1 - cos\alpha) + (s_yt_z - s_zt_y)sin\alpha & 0 \\ r_xr_z(1 - cos\alpha) + (s_zt_x - s_xt_z)sin\alpha & r_yr_z(1 - cos\alpha) + (s_zt_y - s_yt_z)sin\alpha & cos\alpha + r_z^2(1 - cos\alpha) & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} cosα+rx2(1−cosα)rxry(1−cosα)+(sytx−sxty)sinαrxrz(1−cosα)+(sztx−sxtz)sinα0rxry(1−cosα)+(sxty−sytx)sinαcosα+ry2(1−cosα)ryrz(1−cosα)+(szty−sytz)sinα0rxrz(1−cosα)+(sxtz−sztx)sinαryrz(1−cosα)+(sytz−szty)sinαcosα+rz2(1−cosα)00001
最后,基向量r可由s叉乘t得到,那么
r x = s y t z − s z t y r_x = s_yt_z - s_zt_y rx=sytz−szty
r y = s z t x − s x t z r_y = s_zt_x - s_xt_z ry=sztx−sxtz
r z = s x t y − s y t x r_z = s_xt_y - s_yt_x rz=sxty−sytx
最终得到的矩阵
( 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
再看第二种推导方式。本质上,绕旋转轴旋转的向量,可分解为垂直于旋转轴的向量与平行于旋转轴的向量,平行于旋转轴的向量不参与旋转,只需要将垂直旋转轴的向量旋转相应的角度,就可以得到最终旋转后的向量。
假设旋转轴为单位向量N,旋转向量为V,那么v可以分解为
V / / = ( V ⋅ N ) N V_{//} = (V \cdot N)N V//=(V⋅N)N
V ⊥ = V − V / / V_{\perp} = V - V_{//} V⊥=V−V//
然后,对垂直于旋转轴的向量进行旋转,首先需要一组正交基:
X = V ⊥ ∣ ∣ V ⊥ ∣ ∣ X = \dfrac{V_{\perp}}{||V_{\perp}||} X=∣∣V⊥∣∣V⊥
Y = N × V ∣ ∣ N × V ∣ ∣ Y = \dfrac{N \times V}{||N \times V||} Y=∣∣N×V∣∣N×V
可得到旋转后的垂直向量
V ⊥ ′ = ∣ ∣ V ⊥ ∣ ∣ c o s α X + ∣ ∣ V ⊥ ∣ ∣ s i n α Y V'_{\perp} = ||V_{\perp}||cos\alpha X + ||V_{\perp}||sin\alpha Y V⊥′=∣∣V⊥∣∣cosαX+∣∣V⊥∣∣sinαY
最终向量即为
V ′ = V / / + V ⊥ ′ V' = V_{//} + V'_{\perp} V′=V//+V⊥′
接下来还是一样,利用数学的魔法,把这些中间临时变量都消除掉。首先把上述几个式子代入,得到
V ′ = ( 1 − c o s α ) ( V ⋅ N ) N + c o s α V + s i n α ( N × V ) V' = (1 - cos\alpha)(V \cdot N)N + cos\alpha V + sin\alpha (N \times V) V′=(1−cosα)(V⋅N)N+cosαV+sinα(N×V)
我们知道,叉乘是可以转换为矩阵形式的,即
N × V = V N × = V ( 0 n z − n y − n z 0 n x n y − n x 0 ) N \times V = V N_{\times} = V \begin{pmatrix} 0 & n_z & -n_y \\ -n_z & 0 & n_x \\ n_y & -n_x & 0 \end{pmatrix} N×V=VN×=V 0−nznynz0−nx−nynx0
然后,根据向量三重积公式
N × ( N × V ) = N ⋅ ( N ⋅ V ) − V ⋅ ( N ⋅ N ) N \times (N \times V) = N \cdot (N \cdot V) - V \cdot (N \cdot N) N×(N×V)=N⋅(N⋅V)−V⋅(N⋅N)
即
( V ⋅ N ) ⋅ N = V ⋅ ( I + N × 2 ) (V \cdot N) \cdot N = V \cdot (I + N_{\times}^2) (V⋅N)⋅N=V⋅(I+N×2)
所以最终V’
V ′ = V ( ( 1 − c o s α ) ( I + N × 2 ) + c o s α + s i n α N × ) V' = V((1 - cos\alpha)(I + N_{\times}^2) + cos\alpha + sin\alpha N_{\times}) V′=V((1−cosα)(I+N×2)+cosα+sinαN×)
后面那一坨就是最终的矩阵了,计算之后,可以发现和第一种推导方式得出的结论一致。
Reference
[1] 内旋外旋与矩阵左乘右乘
[2] Unreal Engine C++: FMatrix doc sheet
[3] How to Rotate in Unity (complete beginner’s guide)
[4] Extrinsic & intrinsic rotation: Do I multiply from right or left?
[5] 【线性代数的几何意义】行列式的几何意义
[6] 绕任意轴旋转的两种方法简单推导
[7] 叉乘速查手册