旋转的基本概念

大家好,我是 xChester。

在 3D 场景中,表示物体的位移比较简单,使用(x, y, z)坐标即可。但表示旋转则是一个相对复杂的话题。尽管看上去在游戏引擎中,指定旋转也是通过绕 x 轴、y 轴、z 轴的旋转即可表达,但实际情况更为复杂一些。

我们可以先考虑一个简单的场景:我们以右手方向为正,将点(1, 0, 0)作如下 2 种变换:

变换 1. 先绕 x 轴旋转 90°,再绕 y 轴旋转 90°

变换 2. 先绕 y 轴旋转 90°,再绕 x 轴旋转 90°

我们会发现,虽然两种变换绕各轴变换的角度相同,只是改变了旋转的顺序,得到的结果却是不一样的。即旋转并不满足交换律,这与位移是不同的,也是旋转更为复杂的原因。

本文将探讨几种常见的旋转表达方式。

欧拉角

首先我们回到如何表达旋转上来,刚刚的例子里,我们使用三个角度分别绕着三个主轴(通常是 X、Y、Z 轴)的旋转来表示物体的方向。这种方法简单直观,容易实现。这种表示方式称为欧拉角(Euler Angles)

欧拉证明了 3D 空间中任意的旋转都可以拆分成沿着物体自身三个正交坐标轴的旋转。由于旋转存在顺序,即使我们规定了沿每个轴只能旋转一次,且每次旋转角度在 0 到 360 之间,同一个旋转变换可能对应多种不同的欧拉角表示方式。

回到 3D 引擎的场景,无论是 Unity、RealityKit 还是其他引擎,在指定物体旋转时都只让用户指定了绕各个轴旋转的角度,并没有指定顺序。而且无论我们按照何种顺序输入各轴的旋转度数,最终旋转的效果总是相同的,这又是为什么呢?

原因很简单,3D 引擎已经预先设定了旋转的顺序,即无论我们按何种顺序输入物体绕各轴的旋转角度,其变换总是绕着特定的顺序进行的。如 Unity 中的旋转顺序为先 Z 轴,后 X 轴,最后 Y 轴。

In Unity these rotations are performed around the Z axis, the X axis, and the Y axis, in that order. Documentation

但预先指定了顺序的表达方式存在一个问题,继续看一个例子:

  • 旋转一个物体,旋转顺序为 Y 轴(绿色),X 轴(红色),Z 轴(蓝色)
  • 在 Y 轴旋转完成后,将 X 轴旋转 90°

可以发现,此时 Z 轴的与 Y 轴发生了重合,此时再进行旋转,则 Y 轴和 Z 轴同时旋转,原本的 3 自由度变成了 2 自由度,这一问题称为万向死锁问题(Gimbal Lock) 。其核心是由于我们采用了固定的旋转顺序。

变换矩阵

线性变换

首先是线性变换。线性变换是一个映射,将一个向量空间中的向量映射到另一个向量空间,同时保持向量加法和标量乘法的运算。可以用来表示旋转、缩放、倾斜等集合变换。可以用矩阵乘法来实现。

举一个简单的例子:

  • 如果我们有一对基 x(1, 0)和 y(0, 1),在这一对基表示的向量空间中有一个向量 v(-1, 2)
  • 将其转换到新的向量空间中,基 x(1, -2),y(3, 0)

新的向量空间中,v 向量转换后为:

<math xmlns="http://www.w3.org/1998/Math/MathML"> v ′ ⃗ = − 1 ⋅ [ 1 − 2 ] + 2 ⋅ [ 3 0 ] = [ 5 2 ] \vec {v'} = -1 \cdot \begin{bmatrix} 1 \\ -2 \end{bmatrix} + 2 \cdot \begin{bmatrix} 3 \\ 0 \end{bmatrix} = \begin{bmatrix} 5 \\ 2 \end{bmatrix} </math>v′ =−1⋅[1−2]+2⋅[30]=[52]

写成矩阵乘法,变换矩阵第一列为 x,第二列为 y:

<math xmlns="http://www.w3.org/1998/Math/MathML"> v ′ ⃗ = [ 1 3 − 2 0 ] [ − 1 2 ] = [ 5 2 ] \vec {v'} = \begin{bmatrix} 1 & 3\\ -2 & 0 \end{bmatrix} \begin{bmatrix} -1 \\ 2 \end{bmatrix} = \begin{bmatrix} 5 \\ 2 \end{bmatrix} </math>v′ =[1−230][−12]=[52]

二维情况下的旋转(Rotate)

逆时针旋转α,此时对应的两个基为:

  • x(cos α, sin α)
  • y(-sin α, cos α)

暂时无法在飞书文档外展示此内容

对应的旋转矩阵为

<math xmlns="http://www.w3.org/1998/Math/MathML"> [ c o s α − s i n α s i n α c o s α ] \begin{bmatrix} cos α & -sin α\\ sin α & cos α \end{bmatrix} </math>[cosαsinα−sinαcosα]

二维情况下的倾斜(Shear)

举个例子,x 基不变,y 从(0, 1)变成(1, 1)。对应的矩阵为

<math xmlns="http://www.w3.org/1998/Math/MathML"> [ 1 1 0 1 ] \begin{bmatrix} 1 & 1\\ 0 & 1 \end{bmatrix} </math>[1011]

可以发现:

  • 沿着 x 轴方向对 y 进行倾斜时,矩阵右上角的数值发生变化
  • 对应的,如果沿着 y 轴方向对 x 进行倾斜,矩阵左下角的数值会发生变化

二维情况下的缩放(Scale)

举个例子,x 从(1, 0)变成(2, 0),y 从(0, 1)变成(0, 2)。对应的矩阵为

<math xmlns="http://www.w3.org/1998/Math/MathML"> [ 2 0 0 2 ] \begin{bmatrix} 2 & 0\\ 0 & 2 \end{bmatrix} </math>[2002]

通过上述 2x2 的矩阵,可以表达物体的旋转、倾斜、缩放等操作。

  • 缩放控制左上角和右下角的元素值

  • 倾斜控制右上角和左下角的元素值

  • 旋转需要按一定关系同时改变四个值,同时也说明旋转效果总是可以通过缩放和倾斜来得到

仿射变换

但线性变换并不会移动原点,因此为了表示物体的移动,还需要添加位置移动信息。这样的变换就是仿射变换

从上述线形变换的部分我们知道,对于变换后的一组基 x(a, c)和 y (b, d),总是可以将其表示为矩阵:

<math xmlns="http://www.w3.org/1998/Math/MathML"> [ a b c d ] \begin{bmatrix} a & b\\ c & d \end{bmatrix} </math>[acbd]

并且变换可以表示为

<math xmlns="http://www.w3.org/1998/Math/MathML"> v ′ ⃗ = [ a b c d ] ⋅ v ⃗ \vec {v'} = \begin{bmatrix} a & b\\ c & d \end{bmatrix} \cdot \vec {v} </math>v′ =[acbd]⋅v

在此基础上增加位移,则可以表示为

<math xmlns="http://www.w3.org/1998/Math/MathML"> v ′ ⃗ = [ a b c d ] ⋅ v ⃗ + [ T x T y ] \vec {v'} = \begin{bmatrix} a & b\\ c & d \end{bmatrix} \cdot \vec {v} + \begin{bmatrix} T_x\\ T_y \end{bmatrix} </math>v′ =[acbd]⋅v +[TxTy]

对向量和矩阵进行扩展,可以通过一个矩阵表达这一变换:

<math xmlns="http://www.w3.org/1998/Math/MathML"> [ v ′ ⃗ 1 ] = [ a b T x c d T y 0 0 1 ] ⋅ [ v ⃗ 1 ] \begin{bmatrix} \vec {v'} \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & T_x \\ c & d & T_y \\ 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} \vec {v} \\ 1 \end{bmatrix} </math>[v′ 1]= ac0bd0TxTy1 ⋅[v 1]

在 3D 的情况下也是类似的,在此基础上扩展了 z 轴的变换。

代码表达

在 Accelerate 库中,有一个数据结构simd_float4x4,用于表达各种情况下的 transform,如 ARAnchor

simd_float4x4 is a matrix of four columns and four rows that contains single-precision values.

Documentation

其对应的变换矩阵形式如下,不同部分表示不同的变换。其中位移部分较容易获得(紫色部分)。图片来源

四元数

四元数(quaternion)是一种数学概念,用于表示三维空间中的旋转,它提供了一种比传统的欧拉角或旋转矩阵更为简洁和鲁棒的方式来处理三维旋转。四元数由一个实部和三个虚部组成,可以表示为 <math xmlns="http://www.w3.org/1998/Math/MathML"> q = a + b i + c j + d k q = a+bi+cj+dk </math>q=a+bi+cj+dk,其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> a , b , c , d a,b,c,d </math>a,b,c,d是实数,而 <math xmlns="http://www.w3.org/1998/Math/MathML"> i , j , k i,j,k </math>i,j,k是虚数单位,满足以下关系: <math xmlns="http://www.w3.org/1998/Math/MathML"> i 2 = j 2 = k 2 = i j k = − 1 i^2 = j^2 = k^2 = ijk = -1 </math>i2=j2=k2=ijk=−1

四元数的优点在于能够避免用欧拉角表示旋转时可能遇到的万向死锁现象。此外,四元数在进行旋转组合时更为高效,因为它避免了矩阵乘法的复杂性,并且可以很容易地进行归一化,以保持旋转的一致性和稳定性。

四元数的基本运算:

  • 加法:两个四元数的加法是逐元素进行的,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> q 1 + q 2 = ( a 1 + a 2 ) + ( b 1 + b 2 ) i + ( c 1 + c 2 ) j + ( d 1 + d 2 ) k q_1 + q_2 = (a_1+a_2) + (b_1+b_2)i + (c_1+c_2)j + (d_1+d_2)k </math>q1+q2=(a1+a2)+(b1+b2)i+(c1+c2)j+(d1+d2)k

  • 乘法:四元数的乘法不是交换的,它遵循特定的乘法规则,基于上面提到的虚数单位的乘法关系

  • 共轭:四元数的共轭是将其虚部取反, <math xmlns="http://www.w3.org/1998/Math/MathML"> q ∗ = a − b i − c j − d k q^* = a - bi - cj - dk </math>q∗=a−bi−cj−dk

  • 模长:四元数的模长(或范数) <math xmlns="http://www.w3.org/1998/Math/MathML"> a 2 + b 2 + c 2 + d 2 \sqrt{a^2 + b^2 + c^2 + d^2} </math>a2+b2+c2+d2

在三维空间中使用四元数表示旋转时,通常采用单位四元数(unit quaternion),即模长为 1 的四元数。一个单位四元数可以表示为 <math xmlns="http://www.w3.org/1998/Math/MathML"> q = cos ⁡ ( θ 2 ) + sin ⁡ ( θ 2 ) ( x i + y j + z k ) q = \cos\left(\frac{\theta}{2}\right) + \sin\left(\frac{\theta}{2}\right)(xi + yj + zk) </math>q=cos(2θ)+sin(2θ)(xi+yj+zk)

其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> θ {\theta} </math>θ是旋转角度, <math xmlns="http://www.w3.org/1998/Math/MathML"> x , y , z x,y,z </math>x,y,z 分别是旋转轴向量的 x、y、z 分量,而 <math xmlns="http://www.w3.org/1998/Math/MathML"> i , j , k i,j,k </math>i,j,k是四元数的虚部单位。

代码表达

在 Accelerate 库中,有一个数据结构simd_quatf用于表达四元数。

simd_quatf is a single-precision quaternion. Documentation

在RealityKit中,如果有一个 Entity,可以直接从entity.transform.rotation中获取该实体的四元组,API Doc

在一些情况下如果只有变换矩阵matrix,也可以直接构造出对应的四元组simd_quatf(matrix)

关注我

欢迎在掘金上关注我和我的专栏VisionOS Workshop,以及各种收藏/围观/评论/反馈/批评/Star/点歌

参考文档

Euler Angles and Gimbal Lock

Euler (gimbal lock) Explained - By GuerrillaCG

Gimbal Lock - By Krasjet

Transformations

Linear Transformation - By 3Blue1Brown

What are affine transformations - By Leios Labs

Affine Transformations

Augmented Reality 911 --- Transform Matrix 4x4 - By Andy Jazz

Spatial Transformation Matrices

ARKit 2 in Xamarin.iOS

Quaternions

Quaternion lecture of UC Davis

四元数与三维旋转 - By Krasjet

Visualizing quaternions (4d numbers) with stereographic projection - By 3Blue1Brown

Quaternions and 3d rotation, explained interactively - By 3Blue1Brown

Accelerate

Working with Quaternions

Working with Vectors

Working with Matrices

相关推荐
前端爆冲5 分钟前
项目中无用export的检测方案
前端
热爱编程的小曾33 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin44 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
鸿蒙布道师2 小时前
鸿蒙NEXT开发对象工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox