Unreal Transformation
游戏中的旋转变换——四元数和欧拉角
UE4 三种旋转 (一)
UE4_源码浅析篇_矩阵
Unreal Transformation
UE4 三种旋转 (一)
父空间坐标转局部空间坐标。
FTransform.InverseTransformLocation(FVector Location)
局部空间坐标转父空间坐标。
FTransform.TransformLocation(FVector Location)
局部空间旋转转父空间旋转。
FTransform.TransformRotation(FRotator Rotator)
获取使当前坐标系X轴旋转到X_V向量的Rotator。
MakeRotFromX(FVector X_V)
template<typename T>
TMatrix<T> TRotationMatrix<T>::MakeFromX(TVector<T> const& XAxis)
{
TVector<T> const NewX = XAxis.GetSafeNormal();
// try to use up if possible
TVector<T> const UpVector = (FMath::Abs(NewX.Z) < (1.f - UE_KINDA_SMALL_NUMBER)) ? TVector<T>(0, 0, 1.f) : TVector<T>(1.f, 0, 0);
const TVector<T> NewY = (UpVector ^ NewX).GetSafeNormal();
const TVector<T> NewZ = NewX ^ NewY;
return TMatrix<T>(NewX, NewY, NewZ, TVector<T>::ZeroVector);
}
实际上就是MakeRotFromX(Target-Start)。
FindLookAtRotation(FVector Start,FVector Target)
// RotationMatrix.h
// 保证FRotationMatrix的X轴指向给定方向X时,也保证了FRotationMatrix的Y轴约束在给定的方向X和方向Y的平面上,而TRotationMatrix::MakeFromX只指定X轴指向给定方向X,但是Y的方向并没有约束
FRotationMatrix::MakeFromXY
template<typename T>
TMatrix<T> TRotationMatrix<T>::MakeFromXY(TVector<T> const& XAxis, TVector<T> const& YAxis)
{
// 1. 计算单位向量
TVector<T> NewX = XAxis.GetSafeNormal();
TVector<T> Norm = YAxis.GetSafeNormal();
// if they're almost same, we need to find arbitrary vector
if (FMath::IsNearlyEqual(FMath::Abs(NewX | Norm), T(1.f)))
{
// make sure we don't ever pick the same as NewX
Norm = (FMath::Abs(NewX.Z) < (1.f - UE_KINDA_SMALL_NUMBER)) ? TVector<T>(0, 0, 1.f) : TVector<T>(1.f, 0, 0);
}
// 2. NewZ = NewX 叉乘 Norm
// NewY = NewZ 叉乘 NewX
const TVector<T> NewZ = (NewX ^ Norm).GetSafeNormal();
const TVector<T> NewY = NewZ ^ NewX;
return TMatrix<T>(NewX, NewY, NewZ, TVector<T>::ZeroVector);
}
FRotationMatrix::MakeFromXZ
template<typename T>
TMatrix<T> TRotationMatrix<T>::MakeFromXZ(TVector<T> const& XAxis, TVector<T> const& ZAxis)
{
TVector<T> const NewX = XAxis.GetSafeNormal();
TVector<T> Norm = ZAxis.GetSafeNormal();
// if they're almost same, we need to find arbitrary vector
if (FMath::IsNearlyEqual(FMath::Abs(NewX | Norm), T(1.f)))
{
// make sure we don't ever pick the same as NewX
Norm = (FMath::Abs(NewX.Z) < (1.f - UE_KINDA_SMALL_NUMBER)) ? TVector<T>(0, 0, 1.f) : TVector<T>(1.f, 0, 0);
}
const TVector<T> NewY = (Norm ^ NewX).GetSafeNormal();
const TVector<T> NewZ = NewX ^ NewY;
return TMatrix<T>(NewX, NewY, NewZ, TVector<T>::ZeroVector);
}
// Matrix.h, Matrix.inl
TMatrix<T>
// MathFwd.h
using FMatrix = UE::Math::TMatrix<double>; // UE_DECLARE_LWC_TYPE(Matrix, 44);
FMatrix是unreal中表示3D变换的一个4x4的浮点数矩阵,其中前三列代表旋转和缩放,第四列代表平移。
FMatrix的元素排列方式为列主序(column-major order),即按列顺序依次填写。
下面是一个示例,展示了如何填写一个包含平移、旋转和缩放的变换矩阵:
float ScaleX = 1;
float ScaleY = 2;
float ScaleZ = 3;
float RotationPitch = 4;
float RotationYaw = 5;
float RotationRoll = 6;
float TranslationX = 7;
float TranslationY = 8;
float TranslationZ = 9;
FMatrix/FMatrix44f/FMatrix44d TransformMatrix;
TransformMatrix.M[0][0] = ScaleX * FMath::Cos(RotationYaw) * FMath::Cos(RotationRoll);
TransformMatrix.M[0][1] = ScaleX * FMath::Sin(RotationYaw) * FMath::Cos(RotationRoll);
TransformMatrix.M[0][2] = -ScaleX * FMath::Sin(RotationRoll);
TransformMatrix.M[0][3] = 0.0f;
TransformMatrix.M[1][0] = ScaleY * (FMath::Cos(RotationYaw) * FMath::Sin(RotationRoll) * FMath::Sin(RotationPitch) - FMath::Sin(RotationYaw) * FMath::Cos(RotationPitch));
TransformMatrix.M[1][1] = ScaleY * (FMath::Sin(RotationYaw) * FMath::Sin(RotationRoll) * FMath::Sin(RotationPitch) + FMath::Cos(RotationYaw) * FMath::Cos(RotationPitch));
TransformMatrix.M[1][2] = ScaleY * FMath::Cos(RotationRoll) * FMath::Sin(RotationPitch);
TransformMatrix.M[1][3] = 0.0f;
TransformMatrix.M[2][0] = ScaleZ * (FMath::Cos(RotationYaw) * FMath::Sin(RotationRoll) * FMath::Cos(RotationPitch) + FMath::Sin(RotationYaw) * FMath::Sin(RotationPitch));
TransformMatrix.M[2][1] = ScaleZ * (FMath::Sin(RotationYaw) * FMath::Sin(RotationRoll) * FMath::Cos(RotationPitch) - FMath::Cos(RotationYaw) * FMath::Sin(RotationPitch));
TransformMatrix.M[2][2] = ScaleZ * FMath::Cos(RotationRoll) * FMath::Cos(RotationPitch);
TransformMatrix.M[2][3] = 0.0f;
TransformMatrix.M[3][0] = TranslationX;
TransformMatrix.M[3][1] = TranslationY;
TransformMatrix.M[3][2] = TranslationZ;
TransformMatrix.M[3][3] = 1.0f;
其中,TransformMatrix.M[i][j]表示矩阵中第i列第j行的元素值。
FMatrix、FMatrix44f、FMatrix44d
相同点
三者都可以用于表示3D空间中的变换。
都支持矩阵乘法、加法、减法、转置、求逆等操作。
都可以相互转换。不同点:
数据类型不同:FMatrix使用单精度浮点数,FMatrix44f使用单精度浮点数,FMatrix44d使用双精度浮点数。
计算精度不同:FMatrix使用单精度浮点数,精度较低;FMatrix44f使用单精度浮点数,精度较高但是内存占用较大;FMatrix44d使用双精度浮点数,精度最高但是内存占用最大。
相互转换时需要进行类型转换。
可以使用强制类型转换来相互转换
平移矩阵,用于表示沿x、y、z轴的平移变换,一个只有平移变换的矩阵。
构造函数接受一个FVector类型的参数,表示需要进行平移的向量
一个只有旋转变换的矩阵
// RotationMatrix.h
TRotationMatrix<T>
FRotationMatrix比较特殊,只能在纯C++中使用,不能用UFUNCTION等暴露给蓝图,见:Unable to find ‘class’, ‘delegate’, ‘enum’, or ‘struct’ with name ‘FRotationMatrix’
// 编译报错
UFUNCTION(BlueprintCallable)
FRotationMatrix Convert(const FRotator& Rot);
// 修改成FMatrix
UFUNCTION(BlueprintCallable)
FMatrix Convert(const FRotator& Rot);
// 函数实现内部可以按照如下方式转换
FMatrix myRotationMatrix=FRotationMatrix::Make(FRotator(0,0,0));
FRotationMatrix与FRotator、FQuat的转换
// 1.FRotator==>FRotationMatrix
FRotator Rotation(roll, pitch, yaw); //其中roll、pitch和yaw分别表示绕x、y和z轴的旋转角度。
FMatrix RotationMatrix = FRotationMatrix(Rotation);
// 2.FRotationMatrix==>FRotator
FRotationMatrix RotationMatrix = ...;
FRotator Rotation = RotationMatrix.Rotator();
// 3.FQuat==>FRotationMatrix
FQuat QuatRotation = ...;
FRotationMatrix RotationMatrix = FRotationMatrix(QuatRotation.Rotator());
// 4.FRotationMatrix==>FQuat
FRotationMatrix RotationMatrix = ...;
FQuat RotationQuat = RotationMatrix.ToQuat();
FQuat QuatRotation = FQuat(RotationMatrix);
欧拉角,FRotator是一个包含三个浮点数Pitch、Yaw、Roll的结构体,用于表示3D旋转。
TRotator<T>: Rotator.h
// 注意FRotator的构造函数中参数顺序是 Pitch, Yaw, Roll
// Pitch:俯仰角,即绕Y轴旋转;
// Yaw:偏角,即绕Z轴旋转;
// Roll:滚角,即绕X轴旋转。
FORCEINLINE TRotator( T InPitch, T InYaw, T InRoll );
// UKismetMathLibrary::MakeRotator中的顺序是Roll, Pitch, Yaw
FRotator UKismetMathLibrary::MakeRotator(float Roll, float Pitch, float Yaw)
{
return FRotator(Pitch,Yaw,Roll);
}
// MathFwd.h
using FRotator = UE::Math::TRotator<double>; // UE_DECLARE_LWC_TYPE(Rotator, 3);
// 使用示例
//绕`z`轴旋转10度
FRotator rotator(0, 10, 0);
AActorT->SetActorRotation(rotator);
四元数,x、y、z、w。用于表示3D旋转。四元数比欧拉角更加高效. FQuat通常用于表示游戏角色的旋转。避免万向锁,以及更方便做差值计算。
FQuat(FVector Axis, float AngleRad)
struct FQuat
{
public:
/** The quaternion's X-component. */
float X;
/** The quaternion's Y-component. */
float Y;
/** The quaternion's Z-component. */
float Z;
/** The quaternion's W-component. */
float W;
}
// 构造函数: 创建和初始化一个新的四元数(根据给定轴旋转 a 弧度)
FQuat(FVector Axis, float AngleRad)
//绕z轴旋转45度
FQuat quat = FQuat(FVector(0, 0, 1), PI / 4.f);
GetOwner()->SetActorRotation(quat);
FQuat axisRot(FVector::RightVector, FMath::DegreesToRadians(90));
SetActorRotation((GetActorRotation().Quaternion() * axisRot).Rotator());
FQuat axisRot(FVector::UpVector, FMath::DegreesToRadians(90);
SetActorRotation((axisRot * GetActorRotation().Quaternion()).Rotator());
缩放矩阵,用于表示沿x、y、z轴的缩放变换,一个只有缩放变换的矩阵。
FTranslationMatrix、FRotationMatrix、FScaleMatrix来构建一个FTransform
FVector ScaleVector = FVector(2.f, 3.f, 4.f);
FScaleMatrix ScaleMatrix(ScaleVector);
FRotator Rotation(0.f, 45.f, 0.f);
FRotationMatrix RotationMatrix = FRotationMatrix(Rotation);
FVector Translation(100.0f, 200.0f, 300.0f);
FTranslationMatrix TranslationMatrix(Translation);
FTransform Transform(RotationMatrix.ToQuat(), TranslationMatrix.GetOrigin(), ScaleMatrix.GetScaleVector());
UKismetMathLibrary::ClampAxis
// KismetMathLibrary.cpp
float UKismetMathLibrary::ClampAxis(float Angle)
{
return FRotator::ClampAxis(Angle);
}
// Rotator.h
template<typename T>
FORCEINLINE T TRotator<T>::ClampAxis( T Angle )
{
// returns Angle in the range (-360,360)
Angle = FMath::Fmod(Angle, (T)360.0);
if (Angle < (T)0.0)
{
// shift to [0,360) range
Angle += (T)360.0;
}
return Angle;
}
A位置的actor看向B位置,实际就是将A位置的actor的forward vector转向A->B
// 解释
KismetMathLibrary.FindLookAtRotation 等价于 UKismetMathLibrary::MakeRotFromX 等价于
TVector<T>::ToOrientationRotator() 等价于
TVector<T>::Rotation()
// 源码 Vector.h
FORCEINLINE UE::Math::TRotator<T> Rotation() const
{
return ToOrientationRotator();
}
// UnrealMath.cpp
template<typename T>
UE::Math::TRotator<T> UE::Math::TVector<T>::ToOrientationRotator() const
{
UE::Math::TRotator<T> R;
// Find yaw.
R.Yaw = FMath::RadiansToDegrees(FMath::Atan2(Y, X));
// Find pitch.
R.Pitch = FMath::RadiansToDegrees(FMath::Atan2(Z, FMath::Sqrt(X*X + Y*Y)));
// Find roll.
R.Roll = 0;
...
return R;
}
// MakeRotFromXY相比MakeRotFromX,除了使forward vector指向X外,还约束了right vector在XY平面上,
KismetMathLibrary.MakeRotFromXY
KismetMathLibrary.MakeRotFromXZ
// 示例
FVector LookDirection = target->GetActorLocation() - GetOwner()->GetActorLocation();
FMatrix LookRotationMatrix = FRotationMatrix::MakeFromXZ(LookDirection, GetOwner()->GetActorUpVector());
GetOwner()->SetActorRotation(LookRotationMatrix.Rotator());
// 源码KismetMathLibrary.inl
FRotator UKismetMathLibrary::FindLookAtRotation(const FVector& Start, const FVector& Target)
{
return MakeRotFromX(Target - Start);
}
FRotator UKismetMathLibrary::MakeRotFromX(const FVector& X)
{
return FRotationMatrix::MakeFromX(X).Rotator();
}
FRotator UKismetMathLibrary::MakeRotFromXY(const FVector& X, const FVector& Y)
{
return FRotationMatrix::MakeFromXY(X, Y).Rotator();
}
FRotator UKismetMathLibrary::MakeRotFromXZ(const FVector& X, const FVector& Z)
{
return FRotationMatrix::MakeFromXZ(X, Z).Rotator();
}
// 求出令forward vector和InVec同向的FRotator
FRotator UKismetMathLibrary::Conv_VectorToRotator(FVector InVec)
// KismetMathLibrary.inl
KISMET_MATH_FORCEINLINE
FRotator UKismetMathLibrary::Conv_VectorToRotator(FVector InVec)
{
return InVec.ToOrientationRotator();
}
// UnrealMath.cpp
template<typename T>
UE::Math::TRotator<T> UE::Math::TVector<T>::ToOrientationRotator() const
{
UE::Math::TRotator<T> R;
// Find yaw.
R.Yaw = FMath::RadiansToDegrees(FMath::Atan2(Y, X));
// Find pitch.
R.Pitch = FMath::RadiansToDegrees(FMath::Atan2(Z, FMath::Sqrt(X*X + Y*Y)));
// Find roll.
R.Roll = 0;
#if ENABLE_NAN_DIAGNOSTIC || (DO_CHECK && !UE_BUILD_SHIPPING)
if (R.ContainsNaN())
{
logOrEnsureNanError(TEXT("TVector::Rotation(): Rotator result %s contains NaN! Input FVector = %s"), *R.ToString(), *this->ToString());
R = UE::Math::TRotator<T>::ZeroRotator;
}
#endif
return R;
}
旋转矩阵是正交矩阵,逆等于转置, 旋转矩阵(Rotate Matrix)的性质分析
UnrotateVector: 将世界坐标系下的向量转换到旋转矩阵表示的局部坐标系下
RotateVector: 将向量从旋转矩阵表示的局部坐标系下转换到世界坐标系下
使用示例1:让A点绕B点旋转:UE4之A点绕B点旋转
使用示例2:ALS中相机镜头控制部分的插值逻辑 AALSPlayerCameraManager::CalculateAxisIndependentLag
// 关于RotateVector和UnrotateVector的作用,在ALS的相机镜头控制中使用示例如下
// 先用UnrotateVector转换到相机旋转矩阵表示的局部坐标系下,然后在局部坐标系下的三个轴分别插值
// 然后将插值结果用RotateVector局部坐标系下转换到世界坐标系下
// 区别直接在世界坐标系下的三个轴插值分别插值,
FVector AALSPlayerCameraManager::CalculateAxisIndependentLag(FVector CurrentLocation, FVector TargetLocation,
FRotator CameraRotation, FVector LagSpeeds,
float DeltaTime)
{
CameraRotation.Roll = 0.0f;
CameraRotation.Pitch = 0.0f;
const FVector UnrotatedCurLoc = CameraRotation.UnrotateVector(CurrentLocation);
const FVector UnrotatedTargetLoc = CameraRotation.UnrotateVector(TargetLocation);
const FVector ResultVector(
FMath::FInterpTo(UnrotatedCurLoc.X, UnrotatedTargetLoc.X, DeltaTime, LagSpeeds.X),
FMath::FInterpTo(UnrotatedCurLoc.Y, UnrotatedTargetLoc.Y, DeltaTime, LagSpeeds.Y),
FMath::FInterpTo(UnrotatedCurLoc.Z, UnrotatedTargetLoc.Z, DeltaTime, LagSpeeds.Z));
return CameraRotation.RotateVector(ResultVector);
}
dir_rot.RotateVector(ue.Vector(-150, 30, 20))
// Rotator.h
CORE_API TVector<T> RotateVector( const UE::Math::TVector<T>& V ) const;
// UnrealMath.cpp
template<typename T>
UE::Math::TVector<T> UE::Math::TRotator<T>::UnrotateVector(const UE::Math::TVector<T>& V) const
{
return UE::Math::TRotationMatrix<T>(*this).GetTransposed().TransformVector( V );
}
template<typename T>
UE::Math::TVector<T> UE::Math::TRotator<T>::RotateVector(const UE::Math::TVector<T>& V) const
{
return UE::Math::TRotationMatrix<T>(*this).TransformVector( V );
}
// Quat.h
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;
}
template<typename T>
FORCEINLINE TVector<T> TQuat<T>::UnrotateVector(TVector<T> V) const
{
const TVector<T> Q(-X, -Y, -Z); // Inverse
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;
}
// UnrealMath.cpp
// 注意,插值速度小于等于0时,直接返回的是Target
// 注意,插值速度小于等于0时,直接返回的是Target
// UnrealMathUtility.h
/** Interpolate float from Current to Target. Scaled by distance to Target, so it has a strong start speed and ease out. */
template<typename T1, typename T2 = T1, typename T3 = T2, typename T4 = T3>
UE_NODISCARD static auto FInterpTo( T1 Current, T2 Target, T3 DeltaTime, T4 InterpSpeed )
{
using RetType = decltype(T1() * T2() * T3() * T4());
// If no interp speed, jump to target value
if( InterpSpeed <= 0.f )
{
return static_cast<RetType>(Target);
}
// Distance to reach
const RetType Dist = Target - Current;
// If distance is too small, just set the desired location
if( FMath::Square(Dist) < UE_SMALL_NUMBER )
{
return static_cast<RetType>(Target);
}
// Delta Move, Clamp so we do not over shoot.
const RetType DeltaMove = Dist * FMath::Clamp<RetType>(DeltaTime * InterpSpeed, 0.f, 1.f);
return Current + DeltaMove;
}
可以看看UE4 插值相关函数中的插值过程图示意
TickComponent: 中
TargetYaw = 90
FRotator OpenDoor(0.f,TargetYaw,0.f);
OpenDoor.Yaw = FMath::Lerp(CurrentYaw, TargetYaw,0.02f);
// 插值的速度与帧率无关
OpenDoor.Yaw = FMath::FInterpConstantTo(CurrentYaw, TargetYaw,DeltaTime,45);
FMath::Lerp线性插值的问题。OpenDoor.Yaw会一直接近90度,但是不会到达90度。同时电脑帧率快慢会影响OpenDoor.Yaw插值的速度
在两个向量之间进行比例插值
UKismetMathLibrary::Ease
提供了多种内置的缓动插值方式
// 头文件
#include "DrawDebugHelpers.h"
// 几个示例
点: DrawDebugPoint(GetWorld(), LocationOne, 200, FColor(52,220,239), true, 999);
球体:DrawDebugSphere(GetWorld(), LocationTwo, 200, 26, FColor(181,0,0), true, 999, 0, 2);
圆: DrawDebugCircle(GetWorld(), CircleMatrix, 200, 50, FColor(0,104,167), true, 999, 0, 10);
DrawDebugCircle(GetWorld(), LocationFour, 200, 50, FColor(0,0,0), true, 999, 0, 10);
DrawDebugSolidBox(GetWorld(), MyBox, FColor(20, 100, 240), MyTransform, true, 999);
盒: DrawDebugBox(GetWorld(), LocationFive, FVector(100,100,100), FColor::Purple, true, 999, 0, 10);
线: DrawDebugLine(GetWorld(), LocationTwo, LocationThree, FColor::Emerald, true, 999, 0, 10);
方向箭头:DrawDebugDirectionalArrow(GetWorld(), FVector(-300, 600, 600), FVector(-300, -600, 600), 120.f, FColor::Magenta, true, 999, 0, 5.f);
交叉准星:DrawDebugCrosshairs(GetWorld(), FVector(0,0,1000), FRotator(0,0,0), 500.f, FColor::White, true, 999, 0);
KismetMathLibrary库为常用的数学使用库,包含了对向量、矩阵等数学变量的常规操作;
// 头文件
#include "Kismet/KismetMathLibrary.h"
// 一些常用示例
// Cur_Pitch: [0, 360), Min_Pitch:[-180, 0), Max_Pitch:[0, 180)
// 将[0, 360)的输入角Cur_Pitch 限制到[-180, 180)之间,返回的是一个[-180, 180)之间的角度
Ret_Pitch = KismetMathLibrary.ClampAngle(Cur_Pitch, Min_Pitch, Max_Pitch)
// 将[-180, 180)之间的角度转换回[0, 360)之间
UE::Math::TRotator<T>::ClampAxis
// 将[0, 360)之间的角度转换回[-180, 180)之间
UE::Math::TRotator<T>::NormalizeAxis
// 求出从一个点看向另一个点时的Rotator
KismetMathLibrary.FindLookAtRotation
// 求出令forward vector和InVec同向的FRotator
KismetMathLibrary.Conv_VectorToRotator
KismetMathLibrary.MakeRotFromZX
KismetMathLibrary.MakeRotFromZY
KismetMathLibrary.MakeRotFromZX
KismetMathLibrary.DegAcos
KismetMathLibrary.DegreesToRadians
KismetMathLibrary.FMod
GameplayStatics库为常用的Gameplay操作库,包含Gameplay操作的各类静态函数
// 头文件
#include "Kismet/GameplayStatics.h"
python中 math模块下 atan 和 atan2的区别
atan2(y, x) 返回射线从原点到点 (x, y) 与正 x 轴之间的角度 θ,限制为 (−π, π]。
从 −π 到 +π 的切函数图,带有相应的 y/x 符号。绿色箭头指向 atan2(−1, −1) 和 atan2(1, 1) 的结果。
如果 x > 0,则所需的角度测量值为 atan2(y, x)=arctan(y/x),但是,当 x < 0 时,角度与所需角度 arctan(y/x)截然相反,并且必须添加±π(半圈)才能将点放置在正确的象限中。 [1] 使用该 atan2 函数可以消除这种更正,简化代码和数学公式