在Unity中,所有物体即使是空物体,也至少绑定Transform这个组件,这个组件有三个属性:position、rotation、scale,它们分别用于控制物体的平移、旋转和缩放三种变化,而其中最为复杂的一种就是旋转,它就对应于transform组件中的rotation属性,这个属性的类型其实就是四元数。
引言:
常用的控制旋转的方法有:矩阵旋转和欧拉旋转,还有本篇要介绍的重点四元数,它也是实现旋转的方式之一。下面简单介绍一下前面的两种实现方式:
1.矩阵旋转:使用一个4*4的矩阵来表示绕任意轴旋转时的变换矩阵,这个矩阵具有的特点:乘以一个向量的时候只改变向量的方向而不会改变向量的大小;
优点:旋转轴可以是任意向量;
缺点:进行旋转其实我们只需要知道一个向量和一个角度,4个值的信息,而旋转变换矩阵使用了4*4=16个元素;
变换过程增加乘法运算量,耗时;
2.欧拉旋转:在旋转时,按照一定的顺序(例如:x、y、z,Unity中的顺序是z、x、y)每个轴旋转一定的角度,来变换坐标或者是向量,实际上欧拉旋转也可以理解为:一系列坐标旋转的组合;
优点:只需使用3个值,即三个坐标轴的旋转角度;
缺点:必须严格按照顺序进行旋转(顺序不同结果就不同);
容易造成“万向节锁”现象,造成这个现象的原因是因为欧拉旋转是按顺序先后旋转坐标轴的,并非同时旋转,所以当旋转中某些坐标重合就会发生万向节锁,这时就会丢失一个方向上的选择能力,除非打破原来的旋转顺序或者三个坐标轴同时旋转;
由于万向节锁的存在,欧拉旋转无法实现球面平滑插值;
一、四元数:
根据前面所述,我们知道了四元数是用于表示旋转的一种方式,而且transform中的rotation属性的数据类型就是四元数,那么四元数该如何表示呢?从本质上来讲,四元数就是一个高阶复数,也就是一个四维空间。
普通的低阶复数形式一般是:x = a + bi,其中a、b为实数,而i则是虚数单位,而且存在i^2 = -1这样的运算规律,用坐标表示时其实就是由实轴和虚轴构成的二维空间。
说四元数是高阶复数,是因为它一般表示为:x = a + bi + cj + dk,其中i、j、k都是虚数单位,所以也都满足:i2=j2=k2=−1。此外,这三个虚数单位还有以下特点:ij = k,jk = i,ki = j
关于四元数的优缺点:
优点:避免万向节锁现象;
可绕任意过原点的向量旋转;
可提供球面平滑插值;
缺点:比欧拉旋转复杂,多了一个维度;
不够直观;
二、四元数与欧拉角:
根据上述,我们可以这样表示一个四元数:q = w + xi + yj + zk,为了与三维旋转联系起来,可以简化表示为:q = ((x,y,z),w) = (v,w),其中v是一个向量,而w是个实数。此外,向量可以看做实部为0的四元数,而实数亦可以看做虚部为0的四元数。
四元数基本运算法则:(证明:http://www.cnblogs.com/yiyezhai/p/3176725.html)
假设我们想让点P绕单元向量u = (x,y,z)表示的旋转轴转θ角度,具体步骤:
1.将点P坐标转换到四元数空间:P = (P,0);
2.使用q=((x,y,z)sinθ2,cosθ2) 来执行这个旋转;
3.旋转后的结果p'的坐标为:p′=qpq−1;
三、实际应用:
上述讲解的是四元数的原理,但是在实际的使用中并没有那么复杂,我们只要调用Unity为我们提供的接口来修改旋转角度即可,例如为对象直接设置一个旋转值:
float speed = 100.0f;
float x;
float z;
void Update () {
if(Input.GetMouseButton(0)){//鼠标按着左键移动
y = Input.GetAxis("Mouse X") * Time.deltaTime * speed;
x = Input.GetAxis("Mouse Y") * Time.deltaTime * speed;
}else{
x = y = 0 ;
}
//旋转角度(增加)
transform.Rotate(new Vector3(x,y,0));
/**---------------其它旋转方式----------------**/
//transform.Rotate(Vector3.up *Time.deltaTime * speed);//绕Y轴 旋转
//用于平滑旋转至自定义目标
pinghuaxuanzhuan();
}
//平滑旋转至自定义角度
void OnGUI(){
if(GUI.Button(Rect(Screen.width - 110,10,100,50),"set Rotation")){
//自定义角度
targetRotation = Quaternion.Euler(45.0f,45.0f,45.0f);
// 直接设置旋转角度
//transform.rotation = targetRotation;
// 平滑旋转至目标角度
iszhuan = true;
}
}
bool iszhuan= false;
Quaternion targetRotation;
void pinghuaxuanzhuan(){
if(iszhuan){
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 3);
}
}
就像上述的代码中,在实际应用中我们只需通过Quaternion.Euler和Quaternion.Slerp来完成Rolation的赋值操作。