想象一个物体在3D空间中移动的过程,该物体必然会涉及到旋转。例如一个怪物,他的运动方向会改变,要改变其方向只需要对其进行旋转即可。

旋转的方式大致分为三种:Euler旋转,矩阵旋转,以及四元数旋转。
这里稍微记录下我目前对于四元数旋转的理解。对于四元数方面的数学,以及其原理,这里不关心,只需要学会如何使用即可。
无论是哪一种旋转,物体与该物体的局部坐标系之间的相对位置,相对方位都是不会改变的。因此,在进行两个局部旋转(即相对于局部坐标系)时,要注意结果可能不是你预期的。

对于Euler旋转,OGRE中为SceneNode提供了yaw, pitch, roll之类的接口。这些接口默认都是参照局部坐标系旋转,可以通过第二个参数来指定,例如 yaw( Degree( 90 ), SceneNode::TS_WORLD );

OGRE中的Quaternion类用于四元数处理。该类(也可以说是四元数本身)有四个成员:x,y,z,w。这四个数分别代表什么?
在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)


稍微忽略下那些复数之类的概念,使用角度/轴的方式创建四元数的公式为:
Q = cos (angle/2) + i (x * sin(a/2)) + j (y * sin(a/2)) + k(z * sin(a/2))

对应的代码为:
  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中关于Quaternion的一个构造四元数的函数源代码:
  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. }


虽然可以说四元数中的w代表旋转量,x, y, z代表对应轴,但是这也不全正确。因为我们看到,对于真正的旋转量啊之类的数据,是需要进行有些公式变换后,才得到w, x, y, z 的。

但是,即使如此,我们还是可以这样简单地构造一个四元数用于旋转:
Quaternion q( Degree( -90 ), Vector3::UNIT_X );

该构造函数第一个参数指定旋转角度,第二个参数指定旋转轴(可能不是),上面的代码就表示,饶着X轴(正X方向),旋转-90度。将该四元数用于一个Scene Node旋转:
sceneNode->rotate( q );
即可实现该node饶X轴旋转-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的方位,用一个四元数来表示。什么是方位?这里我也不清楚,但是对于一个四元数,它这里表示的是一种旋转偏移,偏移于初始朝向。

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又会得到什么?我可以告诉你,第一句代码整体的作用就是获取该物体当前面向的方向。关于四元数与向量相乘,如图所示:


可以进一步看出,四元数表示了一个旋转偏移,它与一个向量相乘后就获得了另一个向量。该结果向量代表了这个旋转偏移所确定的方向。

那么第一句代码中为什么要乘上UNIT_X呢?因为这里所代表的物体的初始朝向就是正X方向。

第二句话由向量构造一个四元数,它表示,从当前的朝向,旋转到目的朝向所需要的一个四元数。第三句话就直接使用该四元数来旋转该node。但是有时候似乎旋转不正确(在我的实验中,我使用的模型其初始朝向是负Y方向,在初始化时我又将其饶着X轴旋转了负90度,后来旋转时就不正确了),这可以通过 rotate( q, SceneNode::TS_WORLD)来矫正。(所以估计是之前旋转导致的错误)(有时候我在想,为什么对于所有物体的旋转之类的变换,都不直接参照于世界坐标系?因为我们最终看到的就是在世界坐标系中。)

注意,当旋转角度是180度时,这里就会出现错误,为了防止这种错误,可以这样做:
  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