簡單的說,三角度系統無法表現任意軸的旋轉,只要一開始旋轉,物體本身即失去對任意軸的自主性。
四元數(Quaternions)為數學家Hamilton於1843年所創造的,您可能學過的是複數,例如:a + b i 這樣的數,其中i * i = -1,Hamilton創造了三維的複數,其形式為 w + x i + y j + z k,其中i、j、k的關係如下:
i2 = j2 = k2 = -1
i * j = k = -j * i
j * k = i = -k * j
k * i = j = -i * k
假設有兩個四元數:
q1 = w1 + x1 i + y1 j + z1 k
q2 = w2 + x2 i + y2 j + z2 k
四元數的加法定義如下:
q1 + q2 = (w1+w2) + (x1+x2) i + (y1+y2) j + (z1+z2) k
四元數的乘法定義如下,利用簡單的分配律就是了:
q1 * q2 =
(w1*w2 - x1*x2 - y1*y2 - z1*z2) +
(w1*x2 + x1*w2 + y1*z2 - z1*y2) i +
(w1*y2 - x1*z2 + y1*w2 + z1*x2) j +
(w1*z2 + x1*y2 - y1*x2 + z1*w2) k
由於q = w + x i + y j + z k中可以分為純量w與向量x i + y j + z k,所以為了方便表示,將q表示為(S, V),其中S表示純量w,V表示向量x i + y j + z k,所以四元數乘法又可以表示為:
q1 * q2 = (S1 + V1)*(S2 + V2) = S1*S2 - V1.V2 + V1XV2 + S1*V2 + S2*V1
其中V1.V2表示向量內積,V1XV2表示向量外積。
定義四元數q = w + x i + y j +z k 的norm為:
N(q) = |q| = x2 + y2 + z2 + w2
滿足N(q) = 1的四元數集合,稱之為單位四元數(Unit quaternions)。
定義四元數定義四元數q = w + x i + y j +zk的共軛(Conjugate)為:
q* = 定義四元數q = w - x i - y j -z k = [S - V]
定義四元數的倒數為:
1/ q = q* / N(q)
說明了一些數學,您所關心的或許是,四元數與旋轉究竟有何關係,假設有一任意旋轉軸的向量A(Xa, Ya, Za)與一旋轉角度θ,如下圖所示:
可以將之轉換為四元數:
x = s * Xa
y = s * Xb
z = s * Xc
w = cos(θ/2)
s = sin(θ/2)
所以使用四元數來表示的好處是:我們可以簡單的取出旋轉軸與旋轉角度。那麼四元數如何表示三維空間的任意軸旋轉?假設有一向量P(X, Y, Z)對著一單位四元數q作旋轉,則將P視為無純量的四元數X i + Y j + Z k,則向量的旋轉經導證如下:
Rot(P) = q p q*
四元數具有純量與向量,為了計算方便,將之以矩陣的方式來表現四元數的乘法,假設將四元數表示如下:
q = [w, x, y, z] = [S, V]
兩個四元數相乘q" = q * q'的矩陣表示法如下所示:
若令q = [S, V] = [cosθ, u*sinθ],其中u為單位向量,而令q'= [S', V']為一四元數,則經過導證,可以得出q * q' * q^(-1)會使得q'繞著u軸旋轉2θ。
由四元數的矩陣乘法與四元數的旋轉,可以導證出上面的旋轉公式可以使用以下的矩陣乘法來達成:
講了這麼多,其實就是要引出上面這個矩陣乘法,也就是說如果您要讓向量(x', y', z')(w'為0)對某個單位向量軸u(x, y, z)旋轉角度2θ,則w = cosθ,代入以上的矩陣乘法,即可得旋轉後的(x", y", z"),如果為了方便,轉換矩陣的最下列與最右行會省略不寫出來,而如下所示:
關於四元數的其它說明,您可以參考 向量外積與四元數 這篇文章。
關於旋轉的轉換矩陣導證,在Game Programming Gems第二章有詳細的說明。
關於 Gimbal lock。
Quaternion(四元素)和旋转
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)、四元组等。 相对于其它方法,四元组有其本身的优点:
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;
}
}
定義四元數q = w + x i + y j +z k 的norm為:
N(q) = |q| = x2 + y2 + z2 + w2
定義四元數的倒數為:
1/ q = q* / N(q)
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);
}
q* = 定義四元數q = w - x i - y j -z
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);
}
四元數的乘法定義如下,利用簡單的分配律就是了:
q1 * q2 =
(w1*w2 - x1*x2 - y1*y2 - z1*z2) +
(w1*x2 + x1*w2 + y1*z2 - z1*y2) i +
(w1*y2 - x1*z2 + y1*w2 + z1*x2) j +
(w1*z2 + x1*y2 - y1*x2 + z1*w2) k
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));
}
說明了一些數學,您所關心的或許是,四元數與旋轉究竟有何關係,假設有一任意旋轉軸的向量A(Xa, Ya, Za)與一旋轉角度θ,如下圖所示:
可以將之轉換為四元數:
x = s * Xa
y = s * Xb
z = s * Xc
w = cos(θ/2)
s = sin(θ/2)
所以使用四元數來表示的好處是:我們可以簡單的取出旋轉軸與旋轉角度。
那麼四元數如何表示三維空間的任意軸旋轉?假設有一向量P(X, Y, Z)對著一單位四元數q作旋轉,則將P視為無純量的四元數X i + Y j + Z k,則向量的旋轉經導證如下:
Rot(P) = q p q*
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 |
Pitch – Lateral axis
pitch |
Roll – Longitudinal axis
roll |
The Position of All three axes
Yaw Pitch Roll |
Reference
· Main Page
· Related Pages
· Modules
· Namespaces
· Classes
· Files
Tutorial page 8 - Geometry
In this tutorial, we will briefly introduce the many possibilities offered by the geometry module, namely 2D and 3D rotations and projective or affine transformations.
Table of contents
Eigen's Geometry module provides two different kinds of geometric transformations:
Note
If you are working with OpenGL 4x4 matrices then Affine3f and Affine3d are what you want. Since Eigen defaults to column-major storage, you can directly use the Transform::data() method to pass your transformation matrix to OpenGL.
You can construct a Transform from an abstract transformation, like this:
Transform t(AngleAxis(angle,axis));
or like this:
Transform t;
t = AngleAxis(angle,axis);
But note that unfortunately, because of how C++ works, you can not do this:
Transform t = AngleAxis(angle,axis);
Explanation: In the C++ language, this would require Transform to have a non-explicit conversion constructor from AngleAxis, but we really don't want to allow implicit casting here.
Transformation type |
Typical initialization code |
2D rotation from an angle |
Rotation2D<float> rot2(angle_in_radian); |
3D rotation as an angle + axis |
AngleAxis<float> aa(angle_in_radian, Vector3f(ax,ay,az)); The axis vector must be normalized. |
3D rotation as a quaternion |
Quaternion<float> q; q = AngleAxis<float>(angle_in_radian, axis); |
N-D Scaling |
Scaling(sx, sy) Scaling(sx, sy, sz) Scaling(s) Scaling(vecN) |
N-D Translation |
Translation<float,2>(tx, ty) Translation<float,3>(tx, ty, tz) Translation<float,N>(s) Translation<float,N>(vecN) |
N-D Affine transformation |
Transform<float,N,Affine> t = concatenation_of_any_transformations; Transform<float,3,Affine> t = Translation3f(p) * AngleAxisf(a,axis) *Scaling(s); |
N-D Linear transformations |
Matrix<float,N> t = concatenation_of_rotations_and_scalings; Matrix<float,2> t = Rotation2Df(a) * Scaling(s); Matrix<float,3> t = AngleAxisf(a,axis) * Scaling(s); |
Notes on rotations
To transform more than a single vector the preferred representations are rotation matrices, while for other usages Quaternion is the representation of choice as they are compact, fast and stable. Finally Rotation2Dand AngleAxis are mainly convenient types to create other rotation objects.
Notes on Translation and Scaling
Like AngleAxis, these classes were designed to simplify the creation/initialization of linear (Matrix) and affine (Transform) transformations. Nevertheless, unlike AngleAxis which is inefficient to use, these classes might still be interesting to write generic and efficient algorithms taking as input any kind of transformations.
Any of the above transformation types can be converted to any other types of the same nature, or to a more generic type. Here are some additional examples:
Rotation2Df r; r = Matrix2f(..); // assumes a pure rotation matrix AngleAxisf aa; aa = Quaternionf(..); AngleAxisf aa; aa = Matrix3f(..); // assumes a pure rotation matrix Matrix2f m; m = Rotation2Df(..); Matrix3f m; m = Quaternionf(..); Matrix3f m; m = Scaling(..); Affine3f m; m = AngleAxis3f(..); Affine3f m; m = Scaling(..); Affine3f m; m = Translation3f(..); Affine3f m; m = Matrix3f(..); |
top
To some extent, Eigen's geometry module allows you to write generic algorithms working on any kind of transformation representations:
Concatenation of two transformations |
gen1 * gen2; |
Apply the transformation to a vector |
vec2 = gen1 * vec1; |
Get the inverse of the transformation |
gen2 = gen1.inverse(); |
Spherical interpolation |
rot3 = rot1.slerp(alpha,rot2); |
top
Generic affine transformations are represented by the Transform class which internaly is a (Dim+1)^2 matrix. In Eigen we have chosen to not distinghish between points and vectors such that all points are actually represented by displacement vectors from the origin ( ). With that in mind, real points and vector distinguish when the transformation is applied.
Apply the transformation to a point |
VectorNf p1, p2; p2 = t * p1; |
Apply the transformation to a vector |
VectorNf vec1, vec2; vec2 = t.linear() * vec1; |
Apply a general transformation |
VectorNf n1, n2; MatrixNf normalMatrix = t.linear().inverse().transpose(); n2 = (normalMatrix * n1).normalized(); |
Apply a transformation with pure rotation |
n2 = t.linear() * n1; |
OpenGL compatibility 3D |
glLoadMatrixf(t.data()); |
OpenGL compatibility 2D |
Affine3f aux(Affine3f::Identity()); aux.linear().topLeftCorner<2,2>() = t.linear(); aux.translation().start<2>() = t.translation(); glLoadMatrixf(aux.data()); |
Component accessors
full read-write access to the internal matrix |
t.matrix() = matN1xN1; // N1 means N+1 matN1xN1 = t.matrix(); |
coefficient accessors |
t(i,j) = scalar; <=> t.matrix()(i,j) = scalar; scalar = t(i,j); <=> scalar = t.matrix()(i,j); |
translation part |
t.translation() = vecN; vecN = t.translation(); |
linear part |
t.linear() = matNxN; matNxN = t.linear(); |
extract the rotation matrix |
matNxN = t.rotation(); |
Transformation creation
While transformation objects can be created and updated concatenating elementary transformations, theTransform class also features a procedural API:
procedural API |
equivalent natural API |
|
Translation |
t.translate(Vector_(tx,ty,..)); t.pretranslate(Vector_(tx,ty,..)); |
t *= Translation_(tx,ty,..); t = Translation_(tx,ty,..) * t; |
Rotation |
t.rotate(any_rotation); t.prerotate(any_rotation); |
t *= any_rotation; t = any_rotation * t; |
Scaling |
t.scale(Vector_(sx,sy,..)); t.scale(s); t.prescale(Vector_(sx,sy,..)); t.prescale(s); |
t *= Scaling(sx,sy,..); t *= Scaling(s); t = Scaling(sx,sy,..) * t; t = Scaling(s) * t; |
Shear transformation |
t.shear(sx,sy); t.preshear(sx,sy); |
Note that in both API, any many transformations can be concatenated in a single expression as shown in the two following equivalent examples:
t.pretranslate(..).rotate(..).translate(..).scale(..); |
t = Translation_(..) * t * RotationType(..) * Translation_(..) * Scaling(..); |
top
Euler angles might be convenient to create rotation objects. On the other hand, since there exist 24 different conventions, they are pretty confusing to use. This example shows how to create a rotation matrix according to the 2-1-2 convention. |
Matrix3f m; m = AngleAxisf(angle1, Vector3f::UnitZ()) * AngleAxisf(angle2, Vector3f::UnitY()) * AngleAxisf(angle3, Vector3f::UnitZ()); |