我们知道,常用的transform主要有三种,分别是translate,rotate,scale。接下来我们逐一看下在UE5中,如何分别使用TRS构造相应的矩阵,以及如何从矩阵中提取TRS。
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
struct alignas(16) TMatrix
{
static_assert(std::is_floating_point_v, "T must be floating point");
public:
using FReal = T;
alignas(16) T M[4][4];
};
UE提供了TTranslationMatrix
struct来辅助构造平移矩阵。
template
FORCEINLINE TTranslationMatrix::TTranslationMatrix(const TVector& Delta)
: TMatrix(
TPlane(1.0f, 0.0f, 0.0f, 0.0f),
TPlane(0.0f, 1.0f, 0.0f, 0.0f),
TPlane(0.0f, 0.0f, 1.0f, 0.0f),
TPlane(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)
那么相应地,从矩阵中提取平移向量也很简单,返回矩阵的最后一行即可。
除四元数外,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
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 (v