【Ogre-windows】旋转矩阵及位置解析

前言

这篇博客主要针对三种问题

  • 如何创建动画帧
  • 如何获取全局位置
  • 如何计算全局旋转矩阵

仿真环境为VS2013+Ogre1.10.9与matlab验证

创建动画帧

这里只做一个简单的实验: 将自带的人物模型Jaiquarun运动给新创建的运动myrun中并播放,直接贴代码了

void JaiQua::createanim(){
    SkeletonInstance* skel = mEntity->getSkeleton();
    Animation *anim = skel->createAnimation("mywalk", 1.2667);
    anim->setInterpolationMode(Ogre::Animation::IM_SPLINE);
    Animation *orianim = skel->getAnimation("Walk");//原始运动

    Animation::NodeTrackIterator tracks = orianim->getNodeTrackIterator();
    while (tracks.hasMoreElements()){
        NodeAnimationTrack *track = tracks.getNext();//原始运动各关节遍历
        Bone *bone = skel->getBone(track->getHandle());//获取原始运动的各关节
        Bone *nbone = skel->getBone(bone->getName());
        //nbone->setManuallyControlled(true);
        Real framenum = track->getNumKeyFrames();//原始各关节总帧数
        NodeAnimationTrack *tracksnew = anim->createNodeTrack(nbone->getHandle(),nbone);//为新运动创建一个关节
        //依据原始运动各关节每帧数据对新运动各关节赋值
        cout << bone->getName() << ": ";
        for (Real i = 0; i < framenum; i++){
            Real currentframe = track->getKeyFrame(i)->getTime();
            //cout << "当前帧" <<currentframe << " "<<endl;
            TransformKeyFrame *newKf = tracksnew->createNodeKeyFrame(currentframe);//为当前关节创建一帧,输入参数是时间点
            TransformKeyFrame *oriKf = track->getNodeKeyFrame(i);//获取当前帧的原始运动关节,输入参数是时间的索引
          //关节旋转赋值
            Quaternion quat = oriKf->getRotation();
            Ogre::Matrix3 mat;
            quat.ToRotationMatrix(mat);
            newKf->setRotation(Quaternion(mat));

        }
        cout << endl;
    }
    mEntity->refreshAvailableAnimationState();
}

流程:

  • 这里需要注意的是Jaiqua的初始关节数是大于当前帧具有运动数据的关节数的,所以赋值的时候需要先看看初始的Walk运动中到底都给哪些关节赋值了, 方法就是代码中的迭代器Animation::NodeTrackIterator tracks = orianim->getNodeTrackIterator(); 然后不断循环即可, 获取下一个关节的位置是迭代器的next函数.
  • 在循环体内部就可以依据每帧对每个关节的旋转矩阵赋值, 关键一步是针对新运动mywalk创建关键帧, 函数是createNodeKeyFrame(),传入参数是当前关键帧的时间点,比如0.3333,而非1、2之类的
  • 创建了关键帧, 就可以获取一下原始运动Walk的当前关节当前帧getNodeKeyFrame(),注意传入参数是时间点的索引如1、2,指示的是第几帧, 而非0.333之类的
  • 最后就可以获取此关节当前帧的选转四元数getRotation, 随后setRotation给新的运动的当前帧当前关节即可

播放动画的时候直接获取新运动即可

//创建Robot
    mEntity = mSceneMgr->createEntity("jaiqua.mesh");
    mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(0, 0, 25.0));
    mNode->attachObject(mEntity);
    //创建动画
    createanim();
    //动画状态
    mAnimationState = mEntity->getAnimationState("mywalk");
    mAnimationState->setEnabled(true);
    mAnimationState->setLoop(true);

骨骼旋转矩阵、帧旋转矩阵

旋转一般都包含局部旋转和全局旋转, 需要注意的是与ASF/AMC动捕数据类似, 除了关节长度信息外, 骨骼自身每个还有旋转信息, 这与BVH动画数据不同, BVH骨骼只有offset骨骼长度信息, 这就导致了当前帧的局部旋转并非由当前动画帧中的rotation angle-axis直接得到, 还必须考虑骨骼自身的旋转, 即当前帧当前关节局部旋转是骨骼定义的旋转与帧动画定义的旋转的乘积关系,具体是谁乘以谁,下面验证

在渲染每一帧的时候,也就是函数frameRenderingQueued中, 我们加入如下语句获取某个关节的骨骼内部旋转(原始局部)、当前局部旋转

Ogre::SkeletonInstance *skel = mEntity->getSkeleton();
    Ogre::Animation *anim = skel->getAnimation("mywalk");
    Animation::NodeTrackIterator tracks = anim->getNodeTrackIterator();
    while (tracks.hasMoreElements()){
        NodeAnimationTrack *track = tracks.getNext();
        TransformKeyFrame *kf = track->getNodeKeyFrame(mAnimationState->getTimePosition()); //是索引,而非时间      
        Bone *bone = skel->getBone(track->getHandle());
        if (bone->getName() == "Spineroot"){
            cout << "时间: " << mAnimationState->getTimePosition() << " 关节: " << bone->getName() << endl;
            Ogre::Quaternion init_localrot = bone->getInitialOrientation();
            Ogre::Quaternion localrot = bone->getOrientation(); //bone->convertWorldToLocalOrientation(bone->getOrientation());
            Ogre::Matrix3 initlocalmat,localmat;
            localrot.ToRotationMatrix(localmat);
            init_localrot.ToRotationMatrix(initlocalmat);
            cout << "原始局部" << endl;
            showmatrix(initlocalmat);
            cout << "当前局部" << endl;
            showmatrix(localmat);
            cout << "----------------------------" << endl;
        }
    }

提取出Spineroot关节的第0时刻,即第一帧的旋转信息

这里写图片描述

那么,这个数据怎么来的呢?用matlab仿真, 流程是就是读取Jaiqua骨骼和运动帧的轴角对, 并用vrrotvec2mat()转换成旋转矩阵, 注意这个函数接受的是一个数组, 数组前三个元素分别为(x,y,z)角度, 第四个元素为角度制的角度(不要用deg2rad转弧度制),但是转换的结果通常与Ogre的转换函数有点差异, 实验一下

这里写图片描述

扫描二维码关注公众号,回复: 2058844 查看本文章

这里写图片描述
可以发现它俩刚好互为转置, 所以在matlab中仿真的时候注意一下这一点,回过头看我们的问题:原始局部和当前局部是如何通过Jaiqua.skeleton中的数据计算得到?

原始局部很简单,其实就是bones中定义的初始骨骼旋转

这里写图片描述

这里写图片描述

当前局部也即第1帧Spineroot旋转是如何得到的?

这里写图片描述

最终的验证结果是

=×

vrrotvec2mat([-0.802018 0.597271 -0.00580183 0.25543])'*vrrotvec2mat([0.0283593 -0.998889 0.0376242 1.55034])'

ans =

    0.1725    0.0026    0.9850
    0.1380    0.9901   -0.0268
   -0.9753    0.1405    0.1704

关节位置

全局位置关于这一个, 我目前还没搞清楚每帧keyframetranslate参与计算的方式, 按理说这个数据是没有任何作用的, 因为我们知道初始骨骼, 直接依据当前帧定义的旋转矩阵就可以得到当前姿态, 无需额外的translate设置, 而且在matlab的仿真结果证明此数据并无用
【更新日志】并非无用, 这个对整个人体的位置还是有用的, 而除了指定人体位置的关节之外的关节, 这个translate是几乎无用的, 而且可以发现它们的值都很小很小, 基本都乘以 e16

在matlab中求解关节位置的关键代码如下

tdof = squeeze(localRotM(ind,:,:));
xyzStruct(ind).xyz=skel.tree(ind).offset*xyzStruct(parent).rot+xyzStruct(parent).xyz;
xyzStruct(ind).rot=tdof*skel.tree(ind).axisOrder*xyzStruct(parent).rot;

tdof代表的就是当前帧当前关节的局部旋转, skel.tree(ind).offset代表初始骨骼定义的骨骼长度即position, skel.tree(ind).axisOrder代表的就是初始骨骼定义的旋转, xyzStruct(parent).rot代表当前关节父关节的全局旋转,最终计算的位置坐标如下:

Jaiqua
     0     0     0

GlobalSRT
   1.0e-15 *

   -0.1110         0         0

Spineroot
    2.1796    8.7556  -53.7141

Spine01
    2.1796    8.7556  -53.7141

Spine02
    2.2534   10.6250  -53.7025

Spine03
    2.3300   12.5657  -53.6904

Lshoulderroot
    2.3426   14.3376  -53.6515

Lshoulder
    2.3426   14.3376  -53.6515

Larmroot
    0.7550   14.1863  -53.5923

Lbicept
    0.7550   14.1863  -53.5923

Lforearm
   -0.6631   11.6248  -53.2319

Lhandroot
   -1.8638    9.4263  -53.9285

Lhand
   -1.8638    9.4263  -53.9285

Lfingers
   -2.5909    8.5843  -54.4267

Rshoulderroot
    2.3382   14.3351  -53.6398

Rshoulder
    2.3382   14.3351  -53.6398

Rarmroot
    3.9311   14.3725  -53.7863

Rbicept
    3.9311   14.3725  -53.7863

Rforearm
    5.0354   11.6399  -53.6601

Rhandroot
    4.6609    9.0743  -53.8534

Rhand
    4.6609    9.0743  -53.8534

Rfingers
    4.7810    7.8851  -54.0929

neckroot
    2.3827   14.9406  -53.5743

neck
    2.3827   14.9406  -53.5743

head
    2.3325   15.4175  -53.7158

Llegroot
    0.9231    8.9329  -53.5043

Lthigh
    0.9231    8.9329  -53.5043

Lshin
    1.1398    4.0498  -54.2153

Lfooteffector
    1.5438   -0.4803  -53.4399

Lfoot
    1.5438   -0.4803  -53.4399

Ltoe
    1.2300   -1.0743  -54.5649

Rlegroot
    3.3706    8.5802  -53.9321

Rthigh
    3.3706    8.5802  -53.9321

Rshin
    2.7365    3.8570  -55.2306

Rfootroot
    3.2952    1.1392  -51.5247

Rfoot
    3.2952    1.1392  -51.5247

Rtoe
    3.0920   -0.0266  -52.0873

Ogre中获取当前关节相对于根关节的位置是bone->_getDerivedPosition()函数, 即在渲染函数中添加如下代码

    Ogre::SkeletonInstance *skel = mEntity->getSkeleton();
    Ogre::Animation *anim = skel->getAnimation("mywalk");
    Animation::NodeTrackIterator tracks = anim->getNodeTrackIterator();
    while (tracks.hasMoreElements()){
        NodeAnimationTrack *track = tracks.getNext();
        TransformKeyFrame *kf = track->getNodeKeyFrame(mAnimationState->getTimePosition()); //是索引,而非时间      
        Bone *bone = skel->getBone(track->getHandle());
        if (mAnimationState->getTimePosition()==0){//bone->getName() == "Spineroot"
            cout << "时间: " << mAnimationState->getTimePosition() << " 关节: " << bone->getName() << endl;
            Ogre::Quaternion init_localrot = bone->getInitialOrientation();
            Ogre::Quaternion localrot = bone->getOrientation(); //bone->convertWorldToLocalOrientation(bone->getOrientation());
            Ogre::Matrix3 initlocalmat,localmat;
            localrot.ToRotationMatrix(localmat);
            init_localrot.ToRotationMatrix(initlocalmat);
            //cout << "原始局部" << endl;
            //showmatrix(initlocalmat);
            //cout << "当前局部" << endl;
            //showmatrix(localmat);
            cout << "初始位置" << endl;
            //cout << bone->getInitialPosition()<< endl;
            cout << bone->_getDerivedPosition() << endl;
            cout << "----------------------------" << endl;
        }

得到每个关节相对根关节的位置:

时间: 0 关节: head
初始位置
Vector3(2.33252, 15.4175, -53.7158)
----------------------------
时间: 0 关节: neck
初始位置
Vector3(2.38266, 14.9406, -53.5743)
----------------------------
时间: 0 关节: Rbicept
初始位置
Vector3(3.93105, 14.3725, -53.7863)
----------------------------
时间: 0 关节: Rforearm
初始位置
Vector3(5.03536, 11.6399, -53.6601)
----------------------------
时间: 0 关节: Rshoulder
初始位置
Vector3(2.33821, 14.3351, -53.6397)
----------------------------
时间: 0 关节: Lshoulder
初始位置
Vector3(2.34262, 14.3376, -53.6515)
----------------------------
时间: 0 关节: Lbicept
初始位置
Vector3(0.755036, 14.1863, -53.5923)
----------------------------
时间: 0 关节: Lforearm
初始位置
Vector3(-0.663156, 11.6248, -53.2319)
----------------------------
时间: 0 关节: Spine03
初始位置
Vector3(2.32999, 12.5657, -53.6904)
----------------------------
时间: 0 关节: Spine02
初始位置
Vector3(2.2534, 10.625, -53.7024)
----------------------------
时间: 0 关节: Spine01
初始位置
Vector3(2.17962, 8.75564, -53.7141)
----------------------------
时间: 0 关节: Rthigh
初始位置
Vector3(3.37061, 8.58021, -53.932)
----------------------------
时间: 0 关节: Rshin
初始位置
Vector3(2.73647, 3.857, -55.2306)
----------------------------
时间: 0 关节: Lthigh
初始位置
Vector3(0.923067, 8.93287, -53.5043)
----------------------------
时间: 0 关节: Lshin
初始位置
Vector3(1.13982, 4.04976, -54.2153)
----------------------------
时间: 0 关节: Lfoot
初始位置
Vector3(1.5438, -0.48033, -53.4399)
----------------------------
时间: 0 关节: Rfoot
初始位置
Vector3(3.29522, 1.13914, -51.5247)
----------------------------
时间: 0 关节: Ltoe
初始位置
Vector3(1.22996, -1.07427, -54.5649)
----------------------------
时间: 0 关节: Rtoe
初始位置
Vector3(3.09204, -0.0266533, -52.0873)
----------------------------
时间: 0 关节: Spineroot
初始位置
Vector3(2.17962, 8.75564, -53.7141)
----------------------------

可以发现结果几乎完全一样,计算过程并未使用translate,只有在计算整个人体位置的时候涉及到, 上述代码将人体位置设置为始终未 (0,0,0)

Translate作用

为了了解其作用, 这里在渲染代码中书写如下调试输出

Ogre::SkeletonInstance *skel = mEntity->getSkeleton();
    Ogre::Animation *anim = skel->getAnimation("Sneak");

    Animation::NodeTrackIterator tracks = anim->getNodeTrackIterator();
    while (tracks.hasMoreElements()){
        NodeAnimationTrack *track = tracks.getNext();
        TransformKeyFrame *kf = track->getNodeKeyFrame(mAnimationState->getTimePosition()); //是索引,而非时间      
        Bone *bone = skel->getBone(track->getHandle());
        if (bone->getName() == "Spineroot"){//mAnimationState->getTimePosition() == 0
            cout << "时间: " << mAnimationState->getTimePosition() << " 关节: " << bone->getName() << endl;
            Ogre::Quaternion init_localrot = bone->getInitialOrientation();
            Ogre::Quaternion localrot = bone->getOrientation(); //bone->convertWorldToLocalOrientation(bone->getOrientation());
            Ogre::Matrix3 initlocalmat,localmat;
            localrot.ToRotationMatrix(localmat);
            init_localrot.ToRotationMatrix(initlocalmat);
            /*cout << "原始局部" << endl;
            showmatrix(initlocalmat);
            cout << "当前局部" << endl;
            showmatrix(localmat);
            cout << "帧局部" << endl;
            showmatrix(initlocalmat.Inverse()*localmat);*/
            cout << "初始位置:" ;
            cout << bone->getInitialPosition()<< endl;
            cout << "当前位置:" ;
            cout << bone->_getDerivedPosition() << endl;
            cout << "位置差值";
            cout << bone->_getDerivedPosition() - bone->getInitialPosition() << endl;
            cout << "----------------------------" << endl;
        }
    }

然后对比第一帧输出
这里写图片描述
这样我们就得到结论

=position+translate

后记

本篇博客主要就是未后续的将任意的关节数据设置到OGRE中做准备, 重点就是要了解旋转矩阵的到全局位置的计算方法。

以后想套入比如BVH或者ASF/AMC动捕数据的时候, 只需要保证初始骨骼姿态相同的情况下, 就可以将欧拉角转换得到的旋转矩阵应用到Ogre中,重点注意细节上的问题, 比如这个矩阵是否需要转置或者翻转后再运用之类的。

matlab仿真代码:链接:https://pan.baidu.com/s/1kWUF3iZ 密码:u792

Ogre仿真代码:链接:https://pan.baidu.com/s/1o9Xrlge 密码:yl3k

最后再贴一下可视化初始骨骼姿态结果(代码都包含了)
这里写图片描述

Run运动第一帧的结果

这里写图片描述

猜你喜欢

转载自blog.csdn.net/zb1165048017/article/details/79096349