[Unity]Unity3D中的旋转
Unity 3D 中的旋转
⼀、Unity 3D 中 Rotation
在Unity中,旋转通常可以⽤⼀个三维向量(x,y,z)表⽰。实际上这是欧拉⾓。三个分量分别是绕x轴、y轴和z轴的旋转⾓度。
要对⼀个GameObject进⾏旋转,可以直接通过如下代码:
transform.Rotate(xAngle, yAngle, zAngle);
那么有如下疑问:
1. 上述的x轴、y轴、z轴指的是哪组基?是世界坐标系下的xyz轴,还是局部坐标系下的xyz轴?还是其他?
2. 旋转的正⽅向如何?
3. 旋转的顺序如何?
下⾯⼀⼀解答。
⼆、旋转轴
⾸先,回答第⼀个问题,到底旋转轴是哪个坐标系的基?分为如下三种情况。
1. 旋转轴:Inspector 中 Transform
的旋转数值
The position, rotation and scale values of a Transform are measured relative to the Transform’s parent. If the
Transform has no parent, the properties are measured in world space.
即,Editor中Transform组件的旋转轴是⽗节点的模型空间坐标轴,如果没有⽗节点,则旋转轴是世界空间坐标轴。Array
上图显⽰了如果Transform有⽗节点,如图中的”Mesh”,则Position将是在其⽗节点(这⾥是”Cow”)的模型空间中的位置;如果没有⽗节点,Position就是在世界空间中的位置。同样,Transform中的Rotation和Scale也是相同的道理。
2. 旋转轴:在Script中使⽤Rotate函数,在Space.Self中旋转
public void Rotate(Vector3 eulerAngles, Space relativeTo = Space.Self);
public void Rotate(float xAngle, float yAngle, float zAngle, Space relativeTo = Space.Self);
public void Rotate(Vector3 axis, float angle, Space relativeTo = Space.Self);
有上述三种重载函数,这⾥主要以第⼀种为例。其中第⼆个参数的取值有两种:Space.Self 或者 Space.World。
使⽤如下代码,测试上述函数的作⽤。
using UnityEngine;
using System.Collections;
public class Rotate : MonoBehaviour {
public Space m_RotateSpace;
public float m_RotateSpeed = 20f;
// Update is called once per frame
void Update()
{
transform.Rotate(Vector3.up * m_RotateSpeed * Time.deltaTime, m_RotateSpace);
}
}
场景中进⾏测试的是⼀个长⽅体,其⽗节点的旋转为(30,30,0),圆柱体的初始旋转为(0,0,0)。在Inspector中将Rotate Space设置为Self 后,运⾏结果见下图。可见,长⽅体是绕着局部坐标系的Y轴旋转的。
得出结论:在Space.Self中进⾏旋转,旋转轴就是局部坐标系的坐标轴。
3. 旋转轴:在Script中使⽤Rotate函数,在Space.World中旋转
在Inspector中将Rotate Space设置为World后,运⾏结果见下图。这⾥我们知道,长⽅体的⽗节点的Y轴不是World的Y轴,⽽这⾥的长⽅体是绕着世界坐标系下的Y轴旋转的。
所以得出结论:在Space.World中进⾏旋转,旋转轴是世界坐标系的坐标轴。
4. 静态欧拉⾓和动态欧拉⾓
前⾯说到的旋转轴的问题,在数学上有对应的概念。这就是所谓的静态欧拉⾓和动态欧拉⾓。
所谓静态欧拉⾓,就是其旋转轴使⽤的是静⽌不动的参考系。动态欧拉⾓,使⽤的是刚体本⾝作为参考系,因⽽参考系会随着刚体的旋转⽽旋转。
因此,使⽤Space.World旋转,以及Inspector中的旋转是静态欧拉⾓;使⽤Space.Self旋转,是动态欧拉⾓。
三、旋转的正⽅向
来到第⼆个问题,由于Unity中局部坐标系和世界坐标系都是左⼿坐标系,所以这⾥旋转的正⽅向可由右⼿法则判定。
四、旋转的顺序
下⾯来看第三个问题,旋转的顺序,即我们的欧拉⾓(xAngle, yAngle, zAngle)由三个分量组成,分别对应着绕x轴旋转,绕y轴旋转和绕z 轴旋转,那么是如何绕着这三个轴进⾏旋转的呢?
这⾥也分为静态欧拉⾓和动态欧拉⾓的情况进⾏讨论。
1. 静态欧拉⾓
这种情况对应着上⾯所述的使⽤Space.World进⾏旋转,以及Inspector中的旋转。即使旋转轴在旋转的过程中保持不变,旋转的顺序会决定最后的旋转结果。我们看下⾯的例⼦会很清晰的理解:
情形⼀:⾸先绕世界坐标系的x轴旋转90度,再绕世界坐标系的y轴旋转90度
情形⼆:⾸先绕世界坐标系的y轴旋转90度,再绕世界坐标系的x轴旋转90度
可以看到,由于旋转顺序的不同,最终导致了旋转结果的不同!(究其本质,是因为矩阵乘法不满⾜交换律)
对于旋转的顺序,⼀般没有定式,因此,需要在使⽤时明确的指定出其顺序。对此有⼀个专门的术语,称为顺规。如果在坐标系中的旋转,先绕x轴旋转,再绕y轴,最后再绕z轴,则称之为X-Y-Z顺规。以此类推。
对于Unity,从⽂档中可以看到,其transform.Rotate()使⽤的是Z-X-Y顺规。因此如果在Unity中,使⽤静态欧拉⾓旋转(90,90,0)得到情形⼀的情况。
2. 动态欧拉⾓
这种情况对应着上⾯所述的使⽤Space.Self进⾏旋转。动态欧拉⾓除了上⾯说到的顺规问题(同样是Z-X-Y顺规),还有⼀个疑问:⽐如⼀个物体,初始状态记为A,以Z-X-Y顺规旋转(90,90,0),由于没有z轴旋转,第⼀步当然是绕着当前的x轴旋转90度,此时状态记为B,那么第⼆步要绕着y轴旋转90的时候,是绕着初始状态A时的y轴旋转,还是绕着此时的B状态下的y轴旋转呢?
⾸先来看下两者的区别:
情形⼀:以状态A时的y轴旋转
情形⼆:以状态B时的y轴旋转
Unity中的情况究竟如何呢?直接运⾏下⾯的代码会看到结果:
void Start () {
transform.Rotate(90, 90, 0, Space.Self);
}
可以发现Unity中的情况与情形⼀相同。所以第⼆步要绕着y轴旋转90的时候,是绕着初始状态A时的y轴旋转。
为了得到情形⼆中的效果,可以分两次旋转,运⾏如下代码:
void Start () {
transform.Rotate(90, 0, 0, Space.Self);
transform.Rotate(0, 90, 0, Space.Self);
}
可以发现,此时的效果与情形⼀中相同了。
最终,我们的结论是:Unity中每次使⽤Space.Self进⾏Rotate时,都是绕着调⽤时刻的局部坐标系的坐标轴进⾏旋转的。
3. 静态欧拉⾓和动态欧拉⾓的等价形式
静态欧拉⾓和动态欧拉⾓是可以相互转换的。
转化规则就是:静态欧拉⾓中,在某⼀坐标系E下按照某⼀顺规如X-Y-Z旋转⾓度(a, b, c),等价于动态欧拉⾓中,在E下旋转(0, 0, c),在旋转后的坐标系E’中旋转(0, b, 0),在旋转后的新坐标系E”中旋转(a, 0, 0)。
在Space.Self中旋转以 Z-X-Y 顺规旋转⾓度(a, b, c),等价于在Space.Self中旋转(0, b, 0),在新的Space.Self中旋转(a, 0, 0),在更新的Space.Self中旋转(0, 0, c)。
下⾯我们来证明上述两种旋转是等价的。通过复合旋转矩阵的⽅式。
记:
绕坐标系E下的Z轴旋转c的旋转矩阵为Rz,
绕坐标系E下X轴旋转a的旋转矩阵为Rx,
绕坐标系E下Y轴旋转b的旋转矩阵为Ry;
绕坐标系E下的Y轴旋转b的矩阵为Rb(Rb == Ry),
绕坐标系E在绕Y轴旋转b后的新坐标系E’下的X轴旋转a的旋转矩阵为Ra,
绕坐标系E’在绕X轴旋转a后的新坐标系E”下的Z轴旋转c的旋转矩阵为Rc。
另外,这⾥将矩阵R的逆记为R~。
求证:Rz * Rx * Ry == Rb * Ra * Rc
证明:
Rb == Ry,由定义相同可知。
Ra = (Rb~) * Rx * Rb,要得到绕坐标系E在绕Y轴旋转b后的新坐标系E’下的X轴旋转a的旋转矩阵Ra,先应⽤Rb~旋转到坐标系E下,然后绕坐标系E下的X轴旋转a,最后应⽤Rb转回到坐标系E’。
Rc = ((Rb * Ra)~) * Rz * (Rb * Ra),理由同上。
所以有,
右边 = Rb * Ra * Rc
= Rb * Ra * ((Rb * Ra)~) * Rz * (Rb * Ra)
= Rz * Rb * Ra
= Rz * Rb * (Rb~) * Rx * Rb
= Rz * Rx * Rb
= Rz * Rx * Ry = 左边
证毕!
从代码上来说,就是下⾯两个函数是等价的。
private void RotateStatic(float a, float b, float c)
{
// 静态欧拉⾓,依次绕着调⽤Rotate时的局部坐标系的z,x,y轴旋转a,b,c⾓度
transform.Rotate(a, b, c, Space.Self);
}
private void RotateDynamic(float a, float b, float c)
{
// 动态欧拉⾓,绕着调⽤Rotate时的局部坐标系的y轴旋转b⾓度
transform.Rotate(0, b, 0, Space.Self);
// 动态欧拉⾓,绕着调⽤Rotate时的局部坐标系的y轴旋转a⾓度
transform.Rotate(a, 0, 0, Space.Self);
// 动态欧拉⾓,绕着调⽤Rotate时的局部坐标系的y轴旋转c⾓度
unity3d入门transform.Rotate(0, 0, c, Space.Self);
}
五、万向节锁(Gimbal Lock)
1. 什么是万向节锁
2. 如何产⽣万向节锁
3. 万向节锁的问题
3. 在欧拉旋转中尽⼒避免万向节锁
六、四元数(Quaternion)旋转
1. 什么是四元数
2. ⽤四元数进⾏旋转
参考
1.
2.
3. 《Unity Shader ⼊门精要》第四章
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论