UE5的TRS矩阵

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
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];
};

translate

UE提供了TTranslationMatrixstruct来辅助构造平移矩阵。

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)
那么相应地,从矩阵中提取平移向量也很简单,返回矩阵的最后一行即可。

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
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的内旋旋转顺序。所谓内旋,就是指坐标轴会随着一起旋转,相对的就是外旋了,即绕固定轴旋转。由于内旋可以看作是坐标系一起旋转了,那么被旋转的向量,其实可以认为,在旋转前的坐标系下,和旋转后的坐标系下,坐标其实是相同的。

UE5的TRS矩阵_第1张图片

内旋与外旋的对比图如下:

所以内旋本质上可以拆解为两步,第一步是把初始坐标系变换到经过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α00sinα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β00100sinβ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=YawPitchRoll
第二步,需要计算新坐标系下的向量,在初始坐标系下的坐标。依然从基向量出发,新坐标系下的的基向量,显然为
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) XYZ00001 =(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

你可能感兴趣的:(UE源码阅读,ue5,矩阵,线性代数,游戏引擎,unreal,engine)