关于OGRE四元数和旋转的一些浅显认识`

来源:互联网 发布:可以打12315投诉淘宝吗 编辑:程序博客网 时间:2024/05/08 12:19

对于OGRE四元数原理的理解,其实我也并非很清楚,但在我的使用过程中,

我逐渐的认知到了其实际是对于一种空间偏移,可以是绕轴旋转theta的偏移,

也可以理解为对w, x, y, z一种相对抽象表示的偏移。

(在OGRE中默认的相对轴好像是OGRE::Vector3::NEGATIVE_UNIT_Z吧...)

如果我的理解有误,也可以请各位大侠斗胆指出,小生不吝求教,

或许我也该找个时间好好看看《计算机图形学》了,毕竟知道原理才是根本呀!!


好了,多余的话也不说了,下面我写出一些在实践过程中使用OGRE四元数的经验,

也好让那些同样使用OGRE图形引擎的童鞋门有个好的参考:


1.OGRE中四元数中轴axis,弧度角rad和四元数quat的基本转换

(这里只是指出基本的转换原理,没有具体的代码实践, 不要骂我变量没有初始化=。=)


(       Ogre::Quaternion quat;        Ogre::Vector3 axis;        Ogre::Radian rad;         //类型表示 )quat.w = cos (rad / 2) ;quat.x = axis.x * sin (rad / 2) ;quat.y = axis.y * sin (rad / 2) ;quat.z = axis.z * sin (rad / 2);



2.已知源模型的朝向,要使其向着世界空间特定方向的旋转。

(这里我以一个雷龙AI的对象为例举例说明,

m_DragonNode表示雷龙AI被添加到的场景节点,m_Direction表示域使其面向的方向,

这个向量是通过m_DragonNode与攻击对象的场景节点位置作差计算出来的)

  //设置雷龙AI的朝向(其默认局部空间的X轴为其正向朝向)  Ogre::Vector3 src = m_DragonNode->getOrientation()* Ogre::Vector3::UNIT_X;  //雷龙AI的X轴到目标方向的旋转  Ogre::Quaternion quat = src.getRotationTo(m_Direcion);  m_DragonNode->rotate(quat, Ogre::Node::TS_WORLD);



解释:首先我们通过雷龙AI场景节点用getOrientation()得到一个关于其朝向旋转的四元数

(这个四元数是默认相对局部空间的旋转偏移量),通过其与正面的局部向量位置Ogre::Vector3::UNIT_X

相乘,就能得到这个雷龙AI对象模型在世界空间中其正面朝向的向量src;

然后在第二步中,通过getRotationTo函数来计算一个该向量到目标向量m_Direction的

表示旋转偏移的四元数,最后,我们用rotate函数基于该四元数,便能使其旋转的目标方向了。


但是这里有一个问题,如果目标不是在地面,而是在空中这样有空间全方位朝向的情况,

那么,旋转后可能会导致目标并非背对着天空,甚至有时候会出现相对着天空,我们不希望

这样的情况出现,那么我们可以用以下代码来再执行一遍,调整其局部空间Ogre::Vector3::UNIT_Z

的朝向:

 

//将雷龙AI的Z轴再次旋转,使得雷龙AI始终背对着天空  Ogre::Vector3 vecZ = m_Direcion.crossProduct(Ogre::Vector3::UNIT_Y);  src = m_DragonNode->getOrientation()*Ogre::Vector3::UNIT_Z;  quat = src.getRotationTo(vecZ);  m_DragonNode->rotate(quat, Ogre::Node::TS_WORLD);



3.四元数相乘使其叠加旋转偏移

最简单的形式就是 quat1 * quat2 如果quat1表示绕Z轴旋转45度,而quat2表示绕Z轴旋转-10度,

那么叠加的结果便是绕Z轴旋转30度,可以理解为后一个四元数基于前一个四元数的旋转偏移再

做旋转偏移(而quat1,quat2本身都是相对于其局部空间的旋转偏移)


还有一种形式便是quat1 = quat1 + t * (quat1 * quat2 - quat1);

(这里的+和-的含义是相对目标四元数旋转空间的旋转偏移增量(如这的quat1),

而t表示一个旋转偏移增量的缩放比例系数。)

那么和*的区别是什么呢?区别在于其结果的相对目标。

如quat2-quat1的结果表示这个旋转增量是相对于quat1而言的,

而quat2*quat1的结果表示这个旋转是相对于模型本身的局部空间而言的。


好了,下面依然写出一个例子,便于加强对该方式使用的理解:

假如我们想要我们的雷龙AI对象围绕着目标攻击对象进行一个随机方向的旋转环绕,

那么我们该这么做呢?


首先得有一个生成-1到1的随机浮点数的函数:

float randFloat(){        return ((float)rand() / RAND_MAX)*2.0f - 1.0f;}



然后在初始化中添加如下代码生成一个随机的四元数运行轨道:

 Ogre::Vector3 ax, ay, az; ax = Ogre::Vector3(randFloat(), randFloat(), randFloat()); ay = Ogre::Vector3(randFloat(), randFloat(), randFloat()); az = ax.crossProduct(ay); ay = az.crossProduct(ax); ax.normalise(); ay.normalise(); az.normalise(); m_Orbit.FromAxes(ax, ay, az);      //m_Orbit 为Ogre::Quaternion类型的类成员变量



最后我们在参数为const Ogre::FrameEvent & evt的函数中更新其每一帧的行为:

   static Ogre::Vector3 randVec, x, y, z;   static Ogre::Quaternion qua;   //先将雷龙AI移动至目标点,便于四元数的面向旋转   m_DragonNode->translate(m_Direcion, Ogre::Node::TS_WORLD);   //计算旋转后的四元数,并将其设为新的旋转朝向   qua = m_DragonNode->getOrientation() * m_Orbit;   qua = m_DragonNode->getOrientation() + 0.4f * evt.timeSinceLastFrame * (qua -     m_DragonNode->getOrientation());   m_DragonNode->setOrientation(qua);   //根据雷龙AI新的正面朝向将其按原距离平移回   m_DragonNode->getOrientation().ToAxes(x, y, z);   x.normalise();   m_DragonNode->translate(-x * m_Distance, Ogre::Node::TS_WORLD);



解释:m_Distance为雷龙AI到目标的距离,可通过m_Direction.length()计算,getOrientation()得到其

相对局部空间的旋转偏移,getOrientation.ToAxes(x, y, z)可以得到经过旋转偏移后其局部空间的x, y, z

轴方向向量,这里大致的意思是将雷龙AI先平移到目标攻击点所在的位置,然后通过与随机轨道旋转偏移

比例的叠加就可以得到一个相对于原旋转偏移一定偏移的新的旋转偏移四元数,然后将这个四元数设为

其新的朝向,然后在将其局部x轴(即模型的朝向提取出来),再按反方向等距离平移回去便得到了一个

雷龙AI的新运动位置,通过每一帧的叠加,你便可以看到雷龙AI会绕着某一个特定的随机轨道进行绕圆

运动。


4.四元数的FromAngleAxis()和ToAngleAxis()函数

其参数只有两个Ogre::Radian & rfAngle和Ogre::Vector3 & rkAxis。

FromAngleAxis()表示用绕rkAxis旋转Radian弧度来构造四元数,

而ToAngleAxis()表示将四元数分解为可绕轴的rkAxis和可旋转角度rfAngle。

(OGRE中是右手坐标系,所以饶轴旋转应该是逆时针方向。)


我经常将ToAngleAxis用在使模型对象缓慢旋转的技术应用当中,

因为我不希望在一帧的画面当中看到对象向一个方向立即旋转,这样会给玩家不好的体验,

有一种一个世界立即变换到一个世界的感觉,没有平滑过渡。


这里依旧用雷龙AI来一个ToAngleAxis的示例:

//雷龙AI的X轴到逃离方向的旋转   static Ogre::Radian rad;   static Ogre::Vector3 axis, x, y, z;   Ogre::Quaternion quat = src.getRotationTo(-m_Direcion);   quat.ToAngleAxis(rad, axis);   if (rad >= Ogre::Radian(0.01))       m_DragonNode->rotate(axis, Ogre::Radian(Ogre::Math::PI / 30.0f), Ogre::Node::TS_WORLD);



解释:我通过得到axis而限制雷龙AI的旋转最终目标,再将旋转角度分解为每一帧

Ogre::Radian(Ogre::Math::PI / 30.0f)弧度,这样便可以看到雷龙AI一个缓慢的调转旋转逃跑的过程,

而不是一个急速的调转逃跑,当然,为了让其始终背对天空,还要添加如下代码配合使用:


//将雷龙AI的Z轴再次旋转,使得雷龙AI始终背对着天空   src = m_DragonNode->getOrientation()* Ogre::Vector3::UNIT_X;   Ogre::Vector3 vecZ = src.crossProduct(Ogre::Vector3::UNIT_Y);   src = m_DragonNode->getOrientation()*Ogre::Vector3::UNIT_Z;   quat = src.getRotationTo(vecZ);   m_DragonNode->rotate(quat, Ogre::Node::TS_WORLD);



最后来一张雷龙AI的截图,便于大家的理解:

(模型没有优化过,三角形有点多,Debug下帧率有点低。。。)

嗯,对OGRE四元数的使用理解差不多就这样子了,如何有什么好的方法大家可以跟我交流,

不对的地方大家也可以指出,我也希望能够得到更多更好的四元数使用方法,得到进步,加油吧,世界!


1 0