四元数是一种可以替代矩阵和欧拉角的数学工具。他最初是由William Rowan Hamilton发现的(参考维基百科),它的最大的特点是不满足交换率。也谈一下自己对这一点的体会。在离散数学中有讲到半群、群、环和域的概念,其中环的定义是具有交换率和分配率(详情参考环的数学定义),而域的概念则是在环的基础上加上了交换率。所以说四元数无法满足域的定义,它是除法环的一种。何为除法环?其实很简单,被除数和除数都满足结合律和分配律,但是如果要满足交换律,即被除数和除数交换位置,那么它的结果是不同的(准确地说,如果它们不为0的话,那么结果呈倒数关系)。又由于要在四维解空间上解得i3=-1,所以只能在不满足交换率的条件下得出i、j、k。
四元数在计算机图形学的优势在于运算量小和利于插值,且旋转没有缺陷。可是普通的一个四元数定义又比较难懂,而且OpenGL的API中又没有带四元数的参数的函数,所以需要我们根据公式做一做小小的转换。
原创文章,反对未声明的引用。原博客地址:http://blog.csdn.net/gamesdev/article/details/10036105
设四元数Q(x, y, z, w)表示向量a(xa,ya, za)经过α角旋转后的结果,则x、y、z和w分别为:
x= sin(α/2)·xa
y= sin(α/2)·ya
z= sin(α/2)·za
w= cos(α/2)
在上一篇博客中讲到了如何用一个矩阵表示坐标系沿向量的旋转,这里我直接给出公式:
将这两个公式结合起来。再结合高中所学的半角公式:
sinα = 2sin(α/2)·cos(α/2)
cosα = cos2(α/2) - sin2(α/2)
cos2(α/2) = (1 +cosα)/2
sin2(α/2) = (1 -cosα)/2
可以解出用四元数表示旋转矩阵为:
该来验证一下公式的正确性,同样地,采用OpenGL托管的矩阵来测试,看看使用自带的glRotatef()函数和我们写的公式相比,究竟有没有差距。
首先写了一个简单的Quaternion类,它的定义如下:
#ifndef QUATERNION_H
#define QUATERNION_H
#include
#include
class Quaternion
{
public:
Quaternion( float _x,
float _y,
float _z,
float _w )
{
x = _x;
y = _y;
z = _z;
w = _w;
}
void ToMatrix( float matrix[] )
{
Q_ASSERT( matrix != Q_NULLPTR );
#ifndef MATRIX
#define MATRIX( row, col ) matrix[row * 4 + col]
#endif
MATRIX( 0, 0 ) = 2 * ( x * x + w * w ) - 1;
MATRIX( 0, 1 ) = 2 * ( x * y + z * w );
MATRIX( 0, 2 ) = 2 * ( x * z - y * w );
MATRIX( 0, 3 ) = 0.0f;
MATRIX( 1, 0 ) = 2 * ( x * y - z * w );
MATRIX( 1, 1 ) = 2 * ( y * y + w * w ) - 1;
MATRIX( 1, 2 ) = 2 * ( y * z + x * w );
MATRIX( 1, 3 ) = 0.0f;
MATRIX( 2, 0 ) = 2 * ( x * z + y * w );
MATRIX( 2, 1 ) = 2 * ( y * z - x * w );
MATRIX( 2, 2 ) = 2 * ( z * z + w * w ) - 1;
MATRIX( 2, 3 ) = 0.0f;
MATRIX( 3, 0 ) = 0.0f;
MATRIX( 3, 1 ) = 0.0f;
MATRIX( 3, 2 ) = 0.0f;
MATRIX( 3, 3 ) = 1.0f;
#undef MATRIX
}
static Quaternion FromRotation( float _x,
float _y,
float _z,
float angleInDegree )
{
// 向量的单位化
float length = sqrt( _x * _x + _y * _y + _z * _z );
Q_ASSERT( !qFuzzyCompare( length, 0.0f ) );// 希望length不为0
_x /= length;
_y /= length;
_z /= length;
float alpha = angleInDegree / 180 * 3.1415926;// 已转换弧度制
return Quaternion(
sin( alpha / 2 ) * _x,
sin( alpha / 2 ) * _y,
sin( alpha / 2 ) * _z,
cos( alpha / 2 ) );
}
private:
float x, y, z, w;
};
#endif // QUATERNION_H
下面展示了如何调用这个类。
#include
#include
#include "GLWidget.h"
#include "Quaternion.h"
void PrintMatrix( float matrix[16] )
{
Q_ASSERT( matrix != 0 );
printf( "%8.2f%8.2f%8.2f%8.2f\n"
"%8.2f%8.2f%8.2f%8.2f\n"
"%8.2f%8.2f%8.2f%8.2f\n"
"%8.2f%8.2f%8.2f%8.2f\n",
matrix[0], matrix[1], matrix[2], matrix[3],
matrix[4], matrix[5], matrix[6], matrix[7],
matrix[8], matrix[9], matrix[10], matrix[11],
matrix[12], matrix[13], matrix[14], matrix[15] );
}
GLWidget::GLWidget( QWidget* pParent ):
QGLWidget( pParent )
{
setWindowTitle( "Test OpenGL Quaternion and matrix" );
}
void GLWidget::initializeGL( void )
{
float angle = 30.0f;
float x = 12.0f;
float y = 8.0f;
float z = 3.0f;
float matrix1[16], matrix2[16];
glMatrixMode( GL_MODELVIEW );
glLoadIdentity( );
glGetFloatv( GL_MODELVIEW_MATRIX, matrix1 );
glGetFloatv( GL_MODELVIEW_MATRIX, matrix2 );
printf( "The initial identity matrix is:\n" );
PrintMatrix( matrix2 );
printf( "Now perform OpenGL glRotate function.\n" );
glRotatef( angle, x, y, z );
glGetFloatv( GL_MODELVIEW_MATRIX, matrix1 );
PrintMatrix( matrix1 );
printf( "Now using quaternion to perform rotation.\n" );
Quaternion::FromRotation( x, y, z, angle ).ToMatrix( matrix2 );
PrintMatrix( matrix2 );
}
void GLWidget::paintGL( void )
{
}
以下是运行结果。
这说明上述运算是正确的。