想象一個(gè)物體在3D空間中移動(dòng)的過(guò)程,該物體必然會(huì)涉及到旋轉(zhuǎn)。例如一個(gè)怪物,他的運(yùn)動(dòng)方向會(huì)改變,要改變其方向只需要對(duì)其進(jìn)行旋轉(zhuǎn)即可。

旋轉(zhuǎn)的方式大致分為三種:Euler旋轉(zhuǎn),矩陣旋轉(zhuǎn),以及四元數(shù)旋轉(zhuǎn)。
這里稍微記錄下我目前對(duì)于四元數(shù)旋轉(zhuǎn)的理解。對(duì)于四元數(shù)方面的數(shù)學(xué),以及其原理,這里不關(guān)心,只需要學(xué)會(huì)如何使用即可。
無(wú)論是哪一種旋轉(zhuǎn),物體與該物體的局部坐標(biāo)系之間的相對(duì)位置,相對(duì)方位都是不會(huì)改變的。因此,在進(jìn)行兩個(gè)局部旋轉(zhuǎn)(即相對(duì)于局部坐標(biāo)系)時(shí),要注意結(jié)果可能不是你預(yù)期的。

對(duì)于Euler旋轉(zhuǎn),OGRE中為SceneNode提供了yaw, pitch, roll之類的接口。這些接口默認(rèn)都是參照局部坐標(biāo)系旋轉(zhuǎn),可以通過(guò)第二個(gè)參數(shù)來(lái)指定,例如 yaw( Degree( 90 ), SceneNode::TS_WORLD );

OGRE中的Quaternion類用于四元數(shù)處理。該類(也可以說(shuō)是四元數(shù)本身)有四個(gè)成員:x,y,z,w。這四個(gè)數(shù)分別代表什么?
在OGRE論壇上我找到了一些可以讓人很容易理解的信息:

Quaternions can seem pretty daunting because of the use of 'imaginary' numbers. It's much easier to understand if you just ignore this concept completely. The basic formula for creating a quaternion from angle/axis is:

Q = cos (angle/2) + i (x * sin(a/2)) + j (y * sin(a/2)) + k(z * sin(a/2))

or

Code:
  1. Q.w = cos (angle / 2)
  2. Q.x = axis.x * sin (angle / 2)
  3. Q.y = axis.y * sin (angle / 2)
  4. Q.z = axis.z * sin (angle / 2)


稍微忽略下那些復(fù)數(shù)之類的概念,使用角度/軸的方式創(chuàng)建四元數(shù)的公式為:
Q = cos (angle/2) + i (x * sin(a/2)) + j (y * sin(a/2)) + k(z * sin(a/2))

對(duì)應(yīng)的代碼為:
  1. Q.w = cos (angle / 2)
  2. Q.x = axis.x * sin (angle / 2)
  3. Q.y = axis.y * sin (angle / 2)
  4. Q.z = axis.z * sin (angle / 2)


再看一下OGRE中關(guān)于Quaternion的一個(gè)構(gòu)造四元數(shù)的函數(shù)源代碼:
  1. void Quaternion::FromAngleAxis (const Radian& rfAngle,const Vector3& rkAxis)
  2. {
  3. // assert: axis[] is unit length
  4. //
  5. // The quaternion representing the rotation is
  6. // q = cos(A/2)+sin(A/2)*(x*i+y*j+z*k)
  7. Radian fHalfAngle ( 0.5*rfAngle );
  8. Real fSin = Math::Sin(fHalfAngle);
  9.   w = Math::Cos(fHalfAngle);
  10.   x = fSin*rkAxis.x;
  11.   y = fSin*rkAxis.y;
  12.   z = fSin*rkAxis.z;
  13. }


雖然可以說(shuō)四元數(shù)中的w代表旋轉(zhuǎn)量,x, y, z代表對(duì)應(yīng)軸,但是這也不全正確。因?yàn)槲覀兛吹剑瑢?duì)于真正的旋轉(zhuǎn)量啊之類的數(shù)據(jù),是需要進(jìn)行有些公式變換后,才得到w, x, y, z 的。

但是,即使如此,我們還是可以這樣簡(jiǎn)單地構(gòu)造一個(gè)四元數(shù)用于旋轉(zhuǎn):
Quaternion q( Degree( -90 ), Vector3::UNIT_X );

該構(gòu)造函數(shù)第一個(gè)參數(shù)指定旋轉(zhuǎn)角度,第二個(gè)參數(shù)指定旋轉(zhuǎn)軸(可能不是),上面的代碼就表示,饒著X軸(正X方向),旋轉(zhuǎn)-90度。將該四元數(shù)用于一個(gè)Scene Node旋轉(zhuǎn):
sceneNode->rotate( q );
即可實(shí)現(xiàn)該node饒X軸旋轉(zhuǎn)-90度的效果。


再看一下OGRE tutorial中的一段代碼:
  1. Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;
  2. Ogre::Quaternion quat = src.getRotationTo(mDirection);
  3. mNode->rotate(quat);

SceneNode的getOrientation獲得該node的方位,用一個(gè)四元數(shù)來(lái)表示。什么是方位?這里我也不清楚,但是對(duì)于一個(gè)四元數(shù),它這里表示的是一種旋轉(zhuǎn)偏移,偏移于初始朝向。

OGRE論壇上有這么一段話:

The reason there's no other way to convert a quaternion to a vector is because a quaternion is relative. It has no direction.
With a direction (like a vector) you could say "face north east".

But with a quaternion, you say "face 45 degrees clockwise from whatever direction you are already facing" (very simplified example). Without knowing which way the object is already facing, a quaternion is virtually meaningless with respect to orientation. So we just default it to some initial direction, like Unit Z, and make all orientations relative to that.

然后,getOrientation() * Vector3::UINT_X又會(huì)得到什么?我可以告訴你,第一句代碼整體的作用就是獲取該物體當(dāng)前面向的方向。關(guān)于四元數(shù)與向量相乘,如圖所示:


可以進(jìn)一步看出,四元數(shù)表示了一個(gè)旋轉(zhuǎn)偏移,它與一個(gè)向量相乘后就獲得了另一個(gè)向量。該結(jié)果向量代表了這個(gè)旋轉(zhuǎn)偏移所確定的方向。

那么第一句代碼中為什么要乘上UNIT_X呢?因?yàn)檫@里所代表的物體的初始朝向就是正X方向。

第二句話由向量構(gòu)造一個(gè)四元數(shù),它表示,從當(dāng)前的朝向,旋轉(zhuǎn)到目的朝向所需要的一個(gè)四元數(shù)。第三句話就直接使用該四元數(shù)來(lái)旋轉(zhuǎn)該node。但是有時(shí)候似乎旋轉(zhuǎn)不正確(在我的實(shí)驗(yàn)中,我使用的模型其初始朝向是負(fù)Y方向,在初始化時(shí)我又將其饒著X軸旋轉(zhuǎn)了負(fù)90度,后來(lái)旋轉(zhuǎn)時(shí)就不正確了),這可以通過(guò) rotate( q, SceneNode::TS_WORLD)來(lái)矯正。(所以估計(jì)是之前旋轉(zhuǎn)導(dǎo)致的錯(cuò)誤)(有時(shí)候我在想,為什么對(duì)于所有物體的旋轉(zhuǎn)之類的變換,都不直接參照于世界坐標(biāo)系?因?yàn)槲覀冏罱K看到的就是在世界坐標(biāo)系中。)

注意,當(dāng)旋轉(zhuǎn)角度是180度時(shí),這里就會(huì)出現(xiàn)錯(cuò)誤,為了防止這種錯(cuò)誤,可以這樣做:
  1. Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;
  2. if ((1.0f + src.dotProduct(mDirection)) < 0.0001f)
  3. {
  4.   mNode->yaw(Degree(180));
  5. }
  6. else
  7. {
  8.   Ogre::Quaternion quat = src.getRotationTo(mDirection);
  9.   mNode->rotate(quat);
  10. } // else