四元数和旋转矩阵_四元数 旋转矩阵-程序员宅基地

技术标签: 图形学  旋转矩阵  四元素  欧拉角  

 

四元数和旋转矩阵


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。这个角度是相对于单位四元数而言的,也可以说是相对于物体的初始方向而言的。
当用一个四元数乘以一个向量时,实际上就是让该向量围绕着这个四元数所描述的旋转轴,转动这个四元数所描述的角度而得到的向量。


四元组的优点
有多种方式可表示旋转,如 axis/angle、欧拉角(Euler angles)、矩阵(matrix)、四元组等。 相对于其它方法,四元组有其本身的优点:
四元数不会有欧拉角存在的 gimbal lock 问题
四元数由4个数组成,旋转矩阵需要9个数
两个四元数之间更容易插值
四元数、矩阵在多次运算后会积攒误差,需要分别对其做规范化(normalize)和正交化(orthogonalize),对四元数规范化更容易
与旋转矩阵类似,两个四元组相乘可表示两次旋转


Quaternion 的基本运算
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;
}
}


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);
}


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);
}



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));
}



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 插值
线性插值
最简单的插值算法就是线性插值,公式如:
    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;
}



---------------------------------------------------------------------------------------------------------

每一个单位四元数都可以对应到一个旋转矩阵

单位四元数q=(s,V)的共轭为q*=(s,-V)

单位四元数的模为||q||=1;

四元数q=(s,V)的逆q^(-1)=q*/(||q||)=q*

一个向量r,沿着向量n旋转a角度之后的向量是哪个(假设为v),这个用四元数可以轻松搞定

构造两个四元数q=(cos(a/2),sin(a/2)*n),p=(0,r)

p`=q * p * q^(-1) 这个可以保证求出来的p`也是(0,r`)形式的,求出的r`就是r旋转后的向量

另外其实对p做q * p * q^(-1)操作就是相当于对p乘了一个旋转矩阵,这里先假设 q=(cos(a/2),sin(a/2)*n)=(s,(x, y, z))


两个四元数相乘也表示一个旋转
Q1 * Q2 表示先以Q2旋转,再以Q1旋转

则这个矩阵为

同理一个旋转矩阵也可以转换为一个四元数,即给你一个旋转矩阵可以求出(s,x,y,z)这个四元数,

方法是:




版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ycl295644/article/details/50961201

智能推荐

windows server 2012 服务器打开系统远程功能_请确认将rd授权管理器安装为-程序员宅基地

文章浏览阅读974次。然后在服务器池中选择你需要使用的服务器。需要选择安装的服务器类型,如图所示。安装完成后,点击左边服务器,打开“验证完成后,就可以使用远程服务了。选择完成确认内容并等待安装完成。选择完成后,在图示列表下勾选“”并使用服务器ID进行验证。在右键其中服务器,点击“_请确认将rd授权管理器安装为

如何验证自己编译的交叉工具链是否可用_musl-x86_64.so库作用-程序员宅基地

文章浏览阅读2.1k次,点赞2次,收藏2次。自己编译的交叉工具链,使用工具链生成可执行程序来验证工具链是否可用_musl-x86_64.so库作用

富文本编辑器不能正常显示(使用freemarker生成模板)_form generator 富文本编辑器不显示-程序员宅基地

文章浏览阅读1.6k次。一、快速解决:1、使用freemarker模板生成的html,有时不能被富文本编辑器插件识别。2、其中一个解决方案,HTML压缩。网上找一个在线的HTML压缩网站,例如https://tool.oschina.net/jscompress/,把模板压缩后,再次生成的HTML就可以被富文本识别。二、具体实例:1、场景:在一些政府项目,财务金融项目,办公系统项目等等都会大量使用到freemarker模板,来生成HTML。2、问题:使用freemarker模板生成的HTML文本,富.._form generator 富文本编辑器不显示

Script error.全面解析_#r北:script error: luaj.luaerror: @/data/data/com.t-程序员宅基地

文章浏览阅读3.4k次。一些用户向我们反馈,Fundebug的JavaScript监控插件抓到了很多Script error.,然后行号和列号都是0…这就很尴尬了。今天,我们来详细地解析一下Script error.,后续我们还会深度测试并且提供解决方法。同源策略 (Same origin policy)解释Script error.之前,我们先简单聊聊同源策略。摘自MDN - Same-origin policy:_#r北:script error: luaj.luaerror: @/data/data/com.tencent.mobile**.sb/co

视频会议常用术语——转自华为TE40帮助文档_华为会议终端演示会场锁定-程序员宅基地

文章浏览阅读2.9k次。术语查看Web页面在线帮助中用到的术语。数字1080i 分辨率为1920×1080的隔行扫描图像格式。 1080p 分辨率为1920×1080的逐行扫描图像格式。 2CIF 分辨率为352×576的逐行扫描图像格式。 2SIF 分辨率为352×480的逐行扫描图像格式。 4CIF 分辨率为704×576的逐行扫描图像格式。 4SIF 分辨率为704×480的逐行扫描图像格式。 720p 分辨率为1_华为会议终端演示会场锁定

【 linux如何查看cuda版本】_linux cuda version-程序员宅基地

文章浏览阅读1.5w次,点赞2次,收藏2次。linux如何查看cuda版本终端输入nvcc -V命令,即可查询到CUDA版本号_linux cuda version

随便推点

2023年3月| 红帽RHCE考试战报-微思红帽官方授权培训中心_rhce全国考点-程序员宅基地

文章浏览阅读1.1k次。每一张RHCE证书不仅代表了学员成为一名红帽认证工程师,同时也是对微思老师和学员辛勤的付出的肯定。正所谓“不想当将军的士兵不是好兵”,对于一个Linux从业人员来说升职加薪是他们的共同愿望,而考取红帽认证是行之有效的方式之一。企业对Linux人才特别是RHCE的需求不断的增大,而RHCE认证在Linux行业里属于含金量较高的一个认证,这种专业的技能认证越来越多的成为公司考虑一个员工加薪、升职、晋升的标准和参考。热烈祝贺微思的15名学员成为真正意义上的红帽认证工程师。来看看部分学员的高分成绩单。_rhce全国考点

【传智播客】Javaweb程序设计任务教程 黑马程序员 第二章 课后答案_javaweb程序设计任务教程第二版课后答案-程序员宅基地

文章浏览阅读6.5k次,点赞3次,收藏18次。第二章 问题【测一测】学习完前面的内容,下面来动手测一测吧,请思考以下问题:1、简述HTTP1.1协议的通信过程?2、简述POST请求和GET请求有什么不同?(至少2点)3、请列举出Tomcat安装目录下的子目录,并对其进行简要说明?(至少列出5个)4、请编写一个格式良好的XML文档,要求包含足球队一支,队名为Madrid,球员5人:Ronaldo、Casillas、Ramos、Modric、Benzema;篮球队一支,队名为Lakers,队员2人:Oneal,Bryant。里面要求含有注释,注_javaweb程序设计任务教程第二版课后答案

testng的用法--未消化的,未研究明白-程序员宅基地

文章浏览阅读117次。2019独角兽企业重金招聘Python工程师标准>>> ..._f网站

二、svn分支策略原理:-程序员宅基地

文章浏览阅读159次。零、说明:-----欢迎拍砖1、下面内容是找的网上资料总结的,不是生产环境内容,svn分支策略好麻烦啊2、merge很重要而且不好理解,merge修改的只是本地的工作副本,所以只要不提交,不会对服务端造成影响3、多个项目互相依赖,会不会混乱,版本怎么管理那????一、Trunk,Branches,Tags说明1、Branches、Tags生成都是使用svncopy命令生成..._svn切换分支的底层原理

阿里云轻量应用服务器LAMP镜像下搭建网站_阿里云轻量服务器lamp不用域名怎么建站-程序员宅基地

文章浏览阅读915次。阿里云轻量应用服务器LAMP镜像下搭建网站1.服务器选择我配的是2GB内存1核 40GB SSD系统盘。阿里的云翼计划24岁以下自动获取学生身份,这样购买的话一个月只要9.5元。2.选择应用镜像因为部署的是php项目所以选择的是LAMP应用镜像。3.购买之后,环境都是搭配好的。剩下的就是按照步骤配置MySQL和上传文件。1.解析域名。阿里云解析域名可在此快速添加DNS解析,其他域..._阿里云轻量服务器lamp不用域名怎么建站

Makefile教程-程序员宅基地

文章浏览阅读2次。Makefile学习教程: 跟我一起写 Makefile 0 Makefile概述 0.1 关于程序的编译和链接1 Makefile 介绍 1.1 Makefile的规则1.2 一个示例1.3 make是如何工作的1.4 makefile中使用变量1.5 让make自动推导1.6 另类风格的makefile1.7 清空目标文件的规则2 Makefile 总述 2.1 Makefile里...

推荐文章

热门文章

相关标签