四元数是一种可以替代矩阵和欧拉角的数学工具。他最初是由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 <QtGlobal> #include <math.h> 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 <assert.h> #include <stdio.h> #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 ) { }
以下是运行结果。
这说明上述运算是正确的。