[unity3D基础知识]之Quaternion(四元数)和旋转

本文介绍了四元数以及如何在OpenGL中使用四元数表示旋转。
Quaternion 的定义

四元数一般定义如下:

    q=w+xi+yj+zk

其中 w,x,y,z是实数。同时,有:

    i*i=-1

    j*j=-1

    k*k=-1

四元数也可以表示为:

    q=[w,v]

其中v=(x,y,z)是矢量,w是标量,虽然v是矢量,但不能简单的理解为3D空间的矢量,它是4维空间中的的矢量,也是非常不容易想像的。

通俗的讲,一个四元数(Quaternion)描述了一个旋转轴和一个旋转角度。这个旋转轴和这个角度可以通过 Quaternion::ToAngleAxis转换得到。

当然也可以随意指定一个角度一个旋转轴来构造一个Quaternion。这个角度是相对于单位四元数而言的,也可以说是相对于物体的初始方向而言的。

当用一个四元数乘以一个向量时,实际上就是让该向量围绕着这个四元数所描述的旋转轴,转动这个四元数所描述的角度而得到的向量

四元组的优点1

有多种方式可表示旋转,如 axis/angle、欧拉角(Euler angles)、矩阵(matrix)、四元组等。 相对于其它方法,四元组有其本身的优点:

  • 四元数不会有欧拉角存在的 gimbal lock 问题
  • 四元数由4个数组成,旋转矩阵需要9个数
  • 两个四元数之间更容易插值
  • 四元数、矩阵在多次运算后会积攒误差,需要分别对其做规范化(normalize)和正交化(orthogonalize),对四元数规范化更容易
  • 与旋转矩阵类似,两个四元组相乘可表示两次旋转
Quaternion 的基本运算1
Normalizing a quaternion
// normalising a quaternion works similar to a vector. This method will not do anything
// if the quaternion is close enough to being unit-length. define TOLERANCE as something
// small like 0.00001f to get accurate results
void Quaternion::normalise()
{
	// Don't normalize if we don't have to
	float mag2 = w * w + x * x + y * y + z * z;
	if (  mag2!=0.f && (fabs(mag2 - 1.0f) > TOLERANCE)) {
		float mag = sqrt(mag2);
		w /= mag;
		x /= mag;
		y /= mag;
		z /= mag;
	}
}

The complex conjugate of a quaternion
// We need to get the inverse of a quaternion to properly apply a quaternion-rotation to a vector
// The conjugate of a quaternion is the same as the inverse, as long as the quaternion is unit-length
Quaternion Quaternion::getConjugate()
{
	return Quaternion(-x, -y, -z, w);
}

Multiplying quaternions
// Multiplying q1 with q2 applies the rotation q2 to q1
Quaternion Quaternion::operator* (const Quaternion &rq) const
{
	// the constructor takes its arguments as (x, y, z, w)
	return Quaternion(w * rq.x + x * rq.w + y * rq.z - z * rq.y,
	                  w * rq.y + y * rq.w + z * rq.x - x * rq.z,
	                  w * rq.z + z * rq.w + x * rq.y - y * rq.x,
	                  w * rq.w - x * rq.x - y * rq.y - z * rq.z);
}

Rotating vectors
// Multiplying a quaternion q with a vector v applies the q-rotation to v
Vector3 Quaternion::operator* (const Vector3 &vec) const
{
	Vector3 vn(vec);
	vn.normalise();

	Quaternion vecQuat, resQuat;
	vecQuat.x = vn.x;
	vecQuat.y = vn.y;
	vecQuat.z = vn.z;
	vecQuat.w = 0.0f;

	resQuat = vecQuat * getConjugate();
	resQuat = *this * resQuat;

	return (Vector3(resQuat.x, resQuat.y, resQuat.z));
}

How to convert to/from quaternions1
Quaternion from axis-angle
// Convert from Axis Angle
void Quaternion::FromAxis(const Vector3 &v, float angle)
{
	float sinAngle;
	angle *= 0.5f;
	Vector3 vn(v);
	vn.normalise();

	sinAngle = sin(angle);

	x = (vn.x * sinAngle);
	y = (vn.y * sinAngle);
	z = (vn.z * sinAngle);
	w = cos(angle);
}

Quaternion from Euler angles
// Convert from Euler Angles
void Quaternion::FromEuler(float pitch, float yaw, float roll)
{
	// Basically we create 3 Quaternions, one for pitch, one for yaw, one for roll
	// and multiply those together.
	// the calculation below does the same, just shorter

	float p = pitch * PIOVER180 / 2.0;
	float y = yaw * PIOVER180 / 2.0;
	float r = roll * PIOVER180 / 2.0;

	float sinp = sin(p);
	float siny = sin(y);
	float sinr = sin(r);
	float cosp = cos(p);
	float cosy = cos(y);
	float cosr = cos(r);

	this->x = sinr * cosp * cosy - cosr * sinp * siny;
	this->y = cosr * sinp * cosy + sinr * cosp * siny;
	this->z = cosr * cosp * siny - sinr * sinp * cosy;
	this->w = cosr * cosp * cosy + sinr * sinp * siny;

	normalise();
}

Quaternion to Matrix
// Convert to Matrix
Matrix4 Quaternion::getMatrix() const
{
	float x2 = x * x;
	float y2 = y * y;
	float z2 = z * z;
	float xy = x * y;
	float xz = x * z;
	float yz = y * z;
	float wx = w * x;
	float wy = w * y;
	float wz = w * z;

	// This calculation would be a lot more complicated for non-unit length quaternions
	// Note: The constructor of Matrix4 expects the Matrix in column-major format like expected by
	//   OpenGL
	return Matrix4( 1.0f - 2.0f * (y2 + z2), 2.0f * (xy - wz), 2.0f * (xz + wy), 0.0f,
			2.0f * (xy + wz), 1.0f - 2.0f * (x2 + z2), 2.0f * (yz - wx), 0.0f,
			2.0f * (xz - wy), 2.0f * (yz + wx), 1.0f - 2.0f * (x2 + y2), 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f)
}

Quaternion to axis-angle
// Convert to Axis/Angles
void Quaternion::getAxisAngle(Vector3 *axis, float *angle)
{
	float scale = sqrt(x * x + y * y + z * z);
	axis->x = x / scale;
	axis->y = y / scale;
	axis->z = z / scale;
	*angle = acos(w) * 2.0f;
}

Quaternion 插值2
线性插值

最简单的插值算法就是线性插值,公式如:

    q(t)=(1-t)q1 + t q2

但这个结果是需要规格化的,否则q(t)的单位长度会发生变化,所以

    q(t)=(1-t)q1+t q2 / || (1-t)q1+t q2 ||

球形线性插值

尽管线性插值很有效,但不能以恒定的速率描述q1到q2之间的曲线,这也是其弊端,我们需要找到一种插值方法使得q1->q(t)之间的夹角θ是线性的,即θ(t)=(1-t)θ1+t*θ2,这样我们得到了球形线性插值函数q(t),如下:

q(t)=q1 * sinθ(1-t)/sinθ + q2 * sinθt/sineθ

如果使用D3D,可以直接使用 D3DXQuaternionSlerp 函数就可以完成这个插值过程。

用 Quaternion 实现 Camera 旋转

总体来讲,Camera 的操作可分为如下几类:

  • 沿直线移动
  • 围绕某轴自转
  • 围绕某轴公转

下面是一个使用了 Quaternion 的 Camera 类:

    class Camera {

    private:
        Quaternion m_orientation;

    public:
        void rotate (const Quaternion& q);
        void rotate(const Vector3& axis, const Radian& angle);

        void roll (const GLfloat angle);
        void yaw (const GLfloat angle);
        void pitch (const GLfloat angle);


    };


    void Camera::rotate(const Quaternion& q)
    {
        // Note the order of the mult, i.e. q comes after
        m_Orientation = q * m_Orientation;

    }

    void Camera::rotate(const Vector3& axis, const Radian& angle)
    {
        Quaternion q;
        q.FromAngleAxis(angle,axis);
        rotate(q);
    }

    void Camera::roll (const GLfloat angle) //in radian
    {

        Vector3 zAxis = m_Orientation * Vector3::UNIT_Z;
        rotate(zAxis, angleInRadian);

    }


    void Camera::yaw (const GLfloat angle)  //in degree
    {

        Vector3 yAxis;

        {
            // Rotate around local Y axis
            yAxis = m_Orientation * Vector3::UNIT_Y;
        }

        rotate(yAxis, angleInRadian);



    }



    void Camera::pitch (const GLfloat angle)  //in radian
    {

        Vector3 xAxis = m_Orientation * Vector3::UNIT_X;
        rotate(xAxis, angleInRadian);

    }



    void Camera::gluLookAt() {
        GLfloat m[4][4];

        identf(&m[0][0]);
        m_Orientation.createMatrix (&m[0][0]);

        glMultMatrixf(&m[0][0]);
        glTranslatef(-m_eyex, -m_eyey, -m_eyez);
    }

用 Quaternion 实现 trackball

用鼠标拖动物体在三维空间里旋转,一般设计一个 trackball,其内部实现也常用四元数。

class TrackBall
{
public:
    TrackBall();


    void push(const QPointF& p);
    void move(const QPointF& p);
    void release(const QPointF& p);

    QQuaternion rotation() const;

private:
    QQuaternion m_rotation;
    QVector3D m_axis;
    float m_angularVelocity;

    QPointF m_lastPos;

};



void TrackBall::move(const QPointF& p)
{

    if (!m_pressed)
        return;


    QVector3D lastPos3D = QVector3D(m_lastPos.x(), m_lastPos.y(), 0.0f);
    float sqrZ = 1 - QVector3D::dotProduct(lastPos3D, lastPos3D);
    if (sqrZ > 0)
        lastPos3D.setZ(sqrt(sqrZ));
    else
        lastPos3D.normalize();


    QVector3D currentPos3D = QVector3D(p.x(), p.y(), 0.0f);
    sqrZ = 1 - QVector3D::dotProduct(currentPos3D, currentPos3D);
    if (sqrZ > 0)
        currentPos3D.setZ(sqrt(sqrZ));
    else
        currentPos3D.normalize();


    m_axis = QVector3D::crossProduct(lastPos3D, currentPos3D);
    float angle = 180 / PI * asin(sqrt(QVector3D::dotProduct(m_axis, m_axis)));


    m_axis.normalize();
    m_rotation = QQuaternion::fromAxisAndAngle(m_axis, angle) * m_rotation;

    m_lastPos = p;


}


Yaw, pitch, roll 的含义3

Yaw – Vertical axis:

yaw
yaw

Pitch – Lateral axis

pitch
pitch

Roll – Longitudinal axis

roll
roll

The Position of All three axes

Yaw Pitch Roll
Yaw Pitch Roll
Reference
  1. Using Quaternions to represent rotation
  2. 四元数
  3. Yaw,pitch,roll的含义
四元數在電腦圖形學中用於表示物體的旋轉,在unity中由x,y,z,w 表示四個值。
四元數是最簡單的超複數。複數是由實數加上元素 組成,其中i^2 -1 \,。 相似地,四元數都是由實數加上三個元素 i、j、k 組成,而且它們有如下的關係: i^2 j^2 k^2 ijk -1 \, 每個四元數都是 1、i、j 和 的線性組合,即是四元數一般可表示為a bi cj dk \,。
具體的四元數知識可從百度、維基等網站瞭解。
http://baike.baidu.com/view/319754.htm
現在只說說在unity3D中如何使用Quaternion來表達物體的旋轉。
基本的旋轉我們可以用腳本內置旋轉函數transform.Rotate()來實現。 
function Rotate (eulerAngles [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url], relativeTo [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Space.html]Space[/url] [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Space.Self.html?from=Transform]Space.Self[/url]) void

但是當我們希望對旋轉角度進行一些計算的時候,就要用到四元數Quaternion了。我對高等數學來說就菜鳥一個,只能用最樸素的方法看效果了。
Quaternion的變量比較少也沒什麼可說的,大家一看都明白。唯一要說的就是x\y\z\w的取值範圍是[-1,1],物體並不是旋轉一周就所有數值回歸初始值,而是兩周。
初始值: (0,0,0,1)
沿著y軸旋轉:180°(0,1,0,0) 360°(0,0,0,-1)540°(0,-1,0,0) 720°(0,0,0,1) 
沿著x軸旋轉:180°(-1,0,0,0) 360°(0,0,0,-1)540°(1,0,0,0) 720°(0,0,0,1)
無旋轉的寫法是Quaternion.identify
現在開始研究Quaternion的函數都有什麼用。


函數
1)function ToAngleAxis (out angle float, out axis [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url]) void
DescriptionConverts rotation to angle-axis representation
這個函數的作用就是返回物體的旋轉角度(物體的z軸和世界坐標z軸的夾角)和三維旋轉軸的向量到變量out angle out axis
 
腳本:
var a=0.0;
var b=Vector3.zero;
transform.rotation.ToAngleAxis(a,b);


輸入:transform.localEularAngles=(0,0,0);
輸出: a=0, b=(1,0,0);
輸入:transform.localEularAngles=(0,90,0);
輸出:a=90, b=(0,1,0);
輸入:transform.localEularAngles=(270,0,0);
輸出:a=90, b=(-1,0,0)


2)function SetFromToRotation (fromDirection [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url], toDirection [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url]) void
DescriptionCreates rotation which rotates from fromDirection to toDirection.
這個函數的作用是把物體的fromDirection旋轉到toDirection
 
腳本:
var a:Vector3;
var b:Vector3;
var q:Quaternion;
var headUpDir:Vector3;


q.SetFromToRotation(a,b);
transform.rotation=q;
headUpDir=transform.TransformDirection(Vector3.Forward);

輸入:a=Vector3(0,0,1); b=Vector3(0,1,0)//把z軸朝向y軸
輸出: q=(-0.7,0,0,0.7); headUpDir=(0,1,0) 
輸入:a=Vector3(0,0,1); b=Vector3(1,0,0)//把z軸朝向x軸
輸出: q=(0,0.7,0,0.7); headUpDir=(1,0,0) 
輸入:a=Vector3(0,1,0); b=Vector3(1,0,0)//把y軸朝向x軸
輸出: q=(0,0,-0.7,0.7); headUpDir=(0,0,1) 


3)function SetLookRotation (view : [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url], up : [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url] = [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3-up.html?from=Quaternion]Vector3.up[/url]) : void
DescriptionCreates a rotation that looks along forward with the the head upwards along upwards
Logs an error if the forward direction is zero.
這個函數建立一個旋轉使z軸朝向view  y軸朝向up。這個功能讓我想起了Maya裡的一種攝像機lol,大家自己玩好了,很有趣。
 
腳本:
var obj1: Transform;
var obj2: Transform;
var q:Quaternion;

q.SetLookRotation(obj1.position, obj2.position);
transform.rotation=q;
然後大家拖動obj1和obj2就可以看到物體永遠保持z軸朝向obj1,並且以obj2的位置來保持y軸的傾斜度。


傻逗我玩了半天 哈哈^^ 這個功能挺實用的。


4)function ToString () string
DescriptionReturns nicely formatted string of the Quaternion
這個一般用不著吧?看不懂的一邊查字典去~


Class Functions
1)四元數乘法 *
建議非特別瞭解的人群就不要用了。
作用很簡單,c=a*b (c,a,b∈Quaternion)可以理解為 ∠c=∠a+∠b
但是a*b 和b*a效果不一樣的。
 2) == 和 !=
不解釋了
3)static function Dot (a Quaternion, Quaternion) float
DescriptionThe dot product between two rotations
點積,返回一個float. 感覺用處不大。Vector3.Angle()比較常用。
 
4)static function AngleAxis (angle float, axis [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url]) Quaternion
DescriptionCreates rotation which rotates angle degrees around axis.
物體沿指定軸向axis旋轉角度angle, 很實用的一個函數也是。
 
腳本:
var obj1: Transform;
var obj2: Transform;
var q:Quaternion;

//物體沿obj2的z軸旋轉,角度等於obj1的z軸。
q=Quaternion.AngleAxis(obj1.localEularAngle.z, obj2.TransformDirection(Vector3.forward));
transform.rotation=q;




5)static function FromToRotation (fromDirection : [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url], toDirection : [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url]) : Quaternion
DescriptionCreates a rotation which rotates from fromDirection to toDirection.
Usually you use this to rotate a transform so that one of its axes eg. the y-axis - follows a target direction toDirection in world space.
跟SetFromToRotation差不多,區別是可以返回一個Quaternion。通常用來讓transform的一個軸向(例如 y軸)與toDirection在世界坐標中同步。
6)static function LookRotation (forward : [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url], upwards : [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3.html]Vector3[/url] = [url=file:///E:/Editor/Data/Documentation/Documentation/ScriptReference/Vector3-up.html?from=Quaternion]Vector3.up[/url]) : Quaternion
DescriptionCreates a rotation that looks along forward with the the head upwards along upwards
Logs an error if the forward direction is zero.
跟SetLootRotation差不多,區別是可以返回一個Quaternion。
 
7)static function Slerp (from : Quaternion, to : Quaternion, t : float) : Quaternion
DescriptionSpherically interpolates from towards to by t.
從from 轉換到to,移動距離為t。也是很常用的一個函數,用法比較多,個人感覺比較難控制。當兩個quaternion接近時,轉換的速度會比較慢。
 
腳本:
var obj1: Transform;
var t=0.1;
var q:Quaternion;

//讓物體旋轉到與obj1相同的方向
q=Quaternion.Slerp(transform.rotation, obj1.rotation,t);
transform.rotation=q;


根據我個人推測,可能t 代表的是from 和to 之間距離的比例。為此我做了實驗並證明了這一點即:


q=Quaternion.Slerp(a,b,t);


q,a,b∈Quaternion


t[0,1]


q=a+(b-a)*t


並且t最大有效範圍為0~1


腳本:
var obj1: Transform;
var obj2:Transform;
var t=0.1;
var q:Quaternion;

//讓物體obj1和obj2 朝向不同的方向,然後改變t
q=Quaternion.Slerp(obj1.rotation, obj2.rotation,t);
transform.rotation=q;


t+=Input.GetAxis("horizontal")*0.1*Time.deltaTime;




7)static function Lerp (a : Quaternion, b : Quaternion, t : float) : Quaternion
DescriptionInterpolates from towards to by t and normalizes the result afterwards.
This is faster than Slerp but looks worse if the rotations are far apart
跟Slerp相似,且比Slerp快,.但是如果旋轉角度相距很遠則會看起來很差。


8)static function Inverse (rotation : Quaternion) : Quaternion
DescriptionReturns the Inverse of rotation.
返回與rotation相反的方向
 
9)static function Angle (a : Quaternion, b : Quaternion) : float
DescriptionReturns the angle in degrees between two rotations a and b.
計算兩個旋轉之間的夾角。跟Vector3.Angle() 作用一樣。


10)static function Euler (x : float, y : float, z : float) : Quaternion
DescriptionReturns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis (in that order).
把旋轉角度變成對應的Quaternion




以上就是Quaternion的所有函數了。


關於應用,就說一個,其他的有需要再補充。
Slerp 函數是非常常用的一個函數,用來產生旋轉。
static function Slerp (from : Quaternion, to : Quaternion, t : float) : Quaternion
對於新手來說,最難的莫過於如何用它產生一個勻速的旋轉。如果想用它產生勻速轉動,最簡單的辦法就是把form和to固定,然後勻速增加t
腳本:
var obj1: Transform;
var obj2:Transform;
var speed:float;
var t=0.1;
var q:Quaternion;


q=Quaternion.Slerp(obj1.rotation, obj2.rotation,t);
transform.rotation=q;
t+=Time.deltaTime;


但是這並不能解決所有情況。 很多時候from 和to都不是固定的,而且上一個腳本也不能保證所有角度下的旋轉速度一致。所以我寫了這個腳本來保證可以應付大多數情況。


腳本:
var target: Transform;
var rotateSpeed=30.0;
var t=float;
var q:Quaternion;

var wantedRotation=Quaternion.FromToRotation(transform.position,target.position);
t=rotateSpeed/Quaternion.Angle(transform.rotation,wantedRotation)*Time.deltaTime;
q=Quaternion.Slerp(transform.rotation, target.rotation,t);
transform.rotation=q;


這個腳本可以保證物體的旋轉速度永遠是rotateSpeed。
第七行用旋轉速度除以兩者之間的夾角得到一個比例。
如果自身坐標和目標之間的夾角是X度,我們想以s=30度每秒的速度旋轉到目標的方向,則每秒旋轉的角度的比例為s/X。再乘以每次旋轉的時間Time.deltaTime我們就得到了用來勻速旋轉的t值
  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值