本文参考网上的一些文章,进行了一些整理和注释。理解了下面的main函数执行的过程基本就能理解四元数的大致过程。
以下是内容:
四元数常常可以在3D的书上看到。
但我的那本3D图形学书上,在没讲四元数是干什么的之前,就列了几张纸的公
式,
大概因为自己还在上高中,不知道的太多,看了半天没看懂。。。
终于,在gameres上看到了某强人翻译的一个“4元数宝典 ”(原文是日本人写
的。。。),感觉很好,分享下。
★旋转篇:
我将说明使用了四元数(si yuan shu, quaternion)的旋转的操作步骤
(1)四元数的虚部,实部和写法
所谓四元数,就是把4个实数组合起来的东西。
4个元素中,一个是实部,其余3个是虚部。
比如,叫做Q的四元数,实部t而虚部是x,y,z构成,则像下面这样写。
Q = (t; x, y, z)
又,使用向量 V=(x,y,z),
Q = (t; V)
也可以这么写。
正规地用虚数单位i,j,k的写法的话,
Q = t + xi + yj + zk
也这样写,不过,我不大使用
(2)四元数之间的乘法
虚数单位之间的乘法
ii = -1, ij = -ji = k (其他的组合也是循环地以下同文)
有这么一种规则。(我总觉得,这就像是向量积(外积),对吧)
用这个规则一点点地计算很麻烦,所以请用像下面这样的公式计算。
A = (a; U)
B = (b; V)
AB = (ab - U·V; aV + bU + U×V)
不过,“U·V”是内积,「U×V」是外积的意思。
注意:一般AB<>BA所以乘法的左右要注意!
(3)3次元的坐标的四元数表示
如要将某坐标(x,y,z)用四元数表示,
P = (0; x, y, z)
则要这么写。
另外,即使实部是零以外的值,下文的结果也一样。用零的话省事所以我推荐。
(4)旋转的四元数表示
以原点为旋转中心,旋转的轴是(cosα, cosβ, cosγ) 【原文用额是α而不是cosα,这里和下面相对应部分改动下便于理解,因为α一般就是指角度】
(但 cosα^2 + cosβ^2 + cosγ^2 = 1),
(右手系的坐标定义的话,望向向量(cosα, cosβ, cosγ)的前进方向反时针地)
【注意,这里α, β, γ指的是旋转轴这个向量相对各个坐标轴的角度。说的简单点就是cosα=x坐标/向量的长度,其中向量的长度是sqrt(x坐标的平方+y坐标的平方+z坐标的平方)】
转θ角的旋转,用四元数表示就是,
Q = (cos(θ/2); cosα sin(θ/2), cosβ sin(θ/2), cosγ sin(θ/2))
R = (cos(θ/2); -cosα sin(θ/2), -cosβ sin(θ/2), -cosγ sin(θ/2))
(另外R 叫 Q 的共轭四元数。)
那么,如要实行旋转,
则 R P Q = (0; 答案)
请像这样三明治式地计算。这个值的虚部就是旋转之后的点的坐标值。
(另外,实部应该为零。请验算看看)
例子代码
/// Quaternion.cpp
/// (C) Toru Nakata, [email protected]
/// 2004 Dec 29
#include
#include
/// Define Data type
typedef struct
{
double t; // real-component
double x; // x-component
double y; // y-component
double z; // z-component
} quaternion;
//// Bill 注:Kakezan 在日语里是 “乘法”的意思
//按照定义,两个四元数相乘结果还是一个四元数
quaternion Kakezan(quaternion left, quaternion right)
{
quaternion ans;
double d1, d2, d3, d4;
d1 = left.t * right.t;
d2 = -left.x * right.x;
d3 = -left.y * right.y;
d4 = -left.z * right.z;
ans.t = d1+ d2+ d3+ d4;
d1 = left.t * right.x;
d2 = right.t * left.x;
d3 = left.y * right.z;
d4 = -left.z * right.y;
ans.x = d1+ d2+ d3+ d4;
d1 = left.t * right.y;
d2 = right.t * left.y;
d3 = left.z * right.x;
d4 = -left.x * right.z;
ans.y = d1+ d2+ d3+ d4;
d1 = left.t * right.z;
d2 = right.t * left.z;
d3 = left.x * right.y;
d4 = -left.y * right.x;
ans.z = d1+ d2+ d3+ d4;
return ans;
}
//// Make Rotational quaternion
//四个参数从左至右依次是要旋转的角度radian(也就是上面所说的θ角,使用弧度制)
//AxisX是旋转轴这个向量的x坐标,AxisY和AxisZ同理
//整个函数的返回值返回的是“用旋转角度和旋转轴向量”表示的四元数
//也就是上面所说的Q = (cos(θ/2); cosα sin(θ/2), cosβ sin(θ/2), cosγ sin(θ/2)),返回的就是Q
quaternion MakeRotationalQuaternion(double radian, double AxisX, double AxisY, double AxisZ)
{
quaternion ans;
double norm;
double ccc, sss;
ans.t = ans.x = ans.y = ans.z = 0.0;
norm = AxisX * AxisX + AxisY * AxisY + AxisZ * AxisZ;
if(norm <= 0.0) return ans;
norm = 1.0 / sqrt(norm);
AxisX *= norm; //此句过后,AxisX=x坐标/向量的长度,所以此时AxisX就相当于上面所说的cosα
AxisY *= norm;
AxisZ *= norm;
ccc = cos(0.5 * radian);
sss = sin(0.5 * radian);
ans.t = ccc;
ans.x = sss * AxisX;
ans.y = sss * AxisY;
ans.z = sss * AxisZ;
return ans;
}
//// Put XYZ into quaternion
quaternion PutXYZToQuaternion(double PosX, double PosY, double PosZ)
{
quaternion ans;
ans.t = 0.0;
ans.x = PosX;
ans.y = PosY;
ans.z = PosZ;
return ans;
}
///// main
int main()
{
double px, py, pz;
double ax, ay, az, th;
quaternion ppp, qqq, rrr;
cout << "Point Position (x, y, z) " << endl;
cout << " x = ";
cin >> px; //px变量保存待旋转点P的x坐标 ,是输入参数
cout << " y = ";
cin >> py;
cout << " z = ";
cin >> pz;
ppp = PutXYZToQuaternion(px, py, pz);
while(1) {
cout << "\nRotation Degree ? (Enter 0 to Quit) " << endl;
cout << " angle = ";
cin >> th; //th保存待旋转点将要围绕旋转轴旋转的角度,这里使用角度值,是输入参数
if(th == 0.0) break;
cout << "Rotation Axis Direction ? (x, y, z) " << endl;
cout << " x = ";
cin >> ax; //ax保存旋转轴向量的x坐标,是输入参数
cout << " y = ";
cin >> ay;
cout << " z = ";
cin >> az;
th *= 3.1415926535897932384626433832795 / 180.0; /// 角度制转为弧度制;
qqq = MakeRotationalQuaternion(th, ax, ay, az); //qqq是指上面所说的四元数Q
rrr = MakeRotationalQuaternion(-th, ax, ay, az); //rrr是指上面所说的四元数Q的共轭四元数R
ppp = Kakezan(rrr, ppp);
ppp = Kakezan(ppp, qqq); //以上两步完成R P Q = (0; 答案);这一步
cout << "\nAnser X = " << ppp.x //ppp.x是旋转过后P点的x坐标,是输出值
<< "\n Y = " << ppp.y
<< "\n Z = " << ppp.z << endl;
}
return 0;
}
总结:main函数里面输入了待旋转的点、要旋转的角度、旋转轴向量这些输入值,最终求得旋转后的点。
下面是Ogre中关于四元数的一些教程,转自:http://blog.csdn.net/kevinlynx/article/details/1701354
想象一个物体在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:
Q.w = cos (angle / 2)
Q.x = axis.x * sin (angle / 2)
Q.y = axis.y * sin (angle / 2)
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))
对应的代码为:
Q.w = cos (angle / 2)
Q.x = axis.x * sin (angle / 2)
Q.y = axis.y * sin (angle / 2)
Q.z = axis.z * sin (angle / 2)
再看一下OGRE中关于Quaternion的一个构造四元数的函数源代码:
void Quaternion::FromAngleAxis (const Radian& rfAngle,
const Vector3& rkAxis)
{
// assert: axis[] is unit length
//
// The quaternion representing the rotation is
// q = cos(A/2)+sin(A/2)*(x*i+y*j+z*k)
Radian fHalfAngle ( 0.5*rfAngle );
Real fSin = Math::Sin(fHalfAngle);
w = Math::Cos(fHalfAngle);
x = fSin*rkAxis.x;
y = fSin*rkAxis.y;
z = fSin*rkAxis.z;
}
虽然可以说四元数中的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中的一段代码:
Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;
Ogre::Quaternion quat = src.getRotationTo(mDirection);
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度时,这里就会出现错误,为了防止这种错误,可以这样做:
Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;
if ((1.0f + src.dotProduct(mDirection)) < 0.0001f)
{
mNode->yaw(Degree(180));
}
else
{
Ogre::Quaternion quat = src.getRotationTo(mDirection);
mNode->rotate(quat);
} // else
原因之类的可以参看OGRE tutorial。以上信息都是个人理解,行文也比较乱,主要是自己做的笔记,一些基本定义可以参看其他书籍。
2007-7-21
更深的理解:
一个四元数(Quaternion)其实描述了一个旋转轴和一个旋转角度。这个旋转轴和这个角度可以通过Quaternion::ToAngleAxis转换得到。当然也可以随意指定一个角度一个旋转轴来构造一个Quaternion。这个角度是相对于单位四元数而言的,也可以说是相对于物体的初始方向而言的。
当用一个四元数乘以一个向量时,实际上就是让该向量围绕着这个四元数所描述的旋转轴,转动这个四元数所描述的角度而得到的向量。
注意,对于这里的旋转轴而言,是有方向区别的。例如饶+x旋转90度和饶-x旋转90度就是不一样的。可以将其看做饶一个向量旋转,判别旋转正向只需要用右手握住向量,然后大拇指指向向量方向,四指弯曲的方向即为旋转正向。
相关参考:
http://www.ogre3d.org/wiki/index.php/Intermediate_Tutorial_1
http://www.ogre3d.org/phpBB2/viewtopic.php?t=24219&view=previous&sid=ce193664e1d3d7c4af509e6f4e2718c6
http://www.ogre3d.org/phpBB2/viewtopic.php?t=27250&view=previous&sid=86f6055c4b749dab640588cf90d6f745
http://www.ogre3d.org/phpBB2/viewtopic.php?p=238826
other infomations:
Euler rotations can be useful and are supported in Ogre's API. There are problems however.
One is that the order of operations is important. If you pitch an object 90 degrees, then yaw it 90 you get a different result than if yaw is done before pitch. Try it.
However to actually rotate an object in Ogre, you'll either need to convert it to an axis/angle
rotation, store the data in the matrix as an axis/angle and do the conversions yourself or will need to convert it to a quaternion.
A quaternion is composed of four components: a vector with x, y, z coordinates and a w rotation.
X, Y and Z describe a vector which is the axis of rotation.W is the amount of rotation around the vector axis.
Multiplying a quaternion by a vector gives us a vector describing the rotational offset from that vector (a rotated vector).
Vector3::getRotationTo(Vector3) returns a quaternion describing a relative rotation from one vector to the next.
This orientation quaternion can be thought of as a rotational offset from an objects initial facing vector.
getOrientation函数返回的四元数代表一个旋转偏移值,相对于物体初始朝向的方向
物体参照局部坐标系旋转,无论如何旋转,它相对于该坐标系而言其方位还是没变化。
物体相对于局部坐标旋转,只是旋转整个坐标系而已。
You can think about Quaternions as a rotation around and axis with a given angle.
SceneNode::getOrientation returns a quaternion which describe a rotation around and axis/angle from the identity orientation