前言
这篇博客主要针对三种问题
- 如何创建动画帧
- 如何获取全局位置
- 如何计算全局旋转矩阵
仿真环境为VS2013+Ogre1.10.9与matlab验证
创建动画帧
这里只做一个简单的实验: 将自带的人物模型Jaiqua
的run
运动给新创建的运动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
给新的运动的当前帧当前关节即可
播放动画的时候直接获取新运动即可
//创建RobotmEntity = 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
的转换函数有点差异, 实验一下
可以发现它俩刚好互为转置, 所以在matlab中仿真的时候注意一下这一点,回过头看我们的问题:原始局部和当前局部是如何通过Jaiqua.skeleton
中的数据计算得到?
原始局部很简单,其实就是bones
中定义的初始骨骼旋转
当前局部也即第1帧Spineroot
旋转是如何得到的?
最终的验证结果是
当前关节局部旋转矩阵=动画帧定义旋转×初始骨骼旋转当前关节局部旋转矩阵=动画帧定义旋转\times 初始骨骼旋转 当前关节局部旋转矩阵=动画帧定义旋转×初始骨骼旋转
vrrotvec2mat([-0.802018 0.597271 -0.00580183 0.25543])'*vrrotvec2mat([0.0283593 -0.998889 0.0376242 1.55034])'ans =0.1725 0.0026 0.98500.1380 0.9901 -0.0268-0.9753 0.1405 0.1704
#关节位置
全局位置关于这一个, 我目前还没搞清楚每帧keyframe
中translate
参与计算的方式, 按理说这个数据是没有任何作用的, 因为我们知道初始骨骼, 直接依据当前帧定义的旋转矩阵就可以得到当前姿态, 无需额外的translate
设置, 而且在matlab的仿真结果证明此数据并无用
【更新日志】并非无用, 这个对整个人体的位置还是有用的, 而除了指定人体位置的关节之外的关节, 这个translate
是几乎无用的, 而且可以发现它们的值都很小很小, 基本都乘以e−16e^{-16}e−16了
在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
代表当前关节父关节的全局旋转,最终计算的位置坐标如下:
Jaiqua0 0 0GlobalSRT1.0e-15 *-0.1110 0 0Spineroot2.1796 8.7556 -53.7141Spine012.1796 8.7556 -53.7141Spine022.2534 10.6250 -53.7025Spine032.3300 12.5657 -53.6904Lshoulderroot2.3426 14.3376 -53.6515Lshoulder2.3426 14.3376 -53.6515Larmroot0.7550 14.1863 -53.5923Lbicept0.7550 14.1863 -53.5923Lforearm-0.6631 11.6248 -53.2319Lhandroot-1.8638 9.4263 -53.9285Lhand-1.8638 9.4263 -53.9285Lfingers-2.5909 8.5843 -54.4267Rshoulderroot2.3382 14.3351 -53.6398Rshoulder2.3382 14.3351 -53.6398Rarmroot3.9311 14.3725 -53.7863Rbicept3.9311 14.3725 -53.7863Rforearm5.0354 11.6399 -53.6601Rhandroot4.6609 9.0743 -53.8534Rhand4.6609 9.0743 -53.8534Rfingers4.7810 7.8851 -54.0929neckroot2.3827 14.9406 -53.5743neck2.3827 14.9406 -53.5743head2.3325 15.4175 -53.7158Llegroot0.9231 8.9329 -53.5043Lthigh0.9231 8.9329 -53.5043Lshin1.1398 4.0498 -54.2153Lfooteffector1.5438 -0.4803 -53.4399Lfoot1.5438 -0.4803 -53.4399Ltoe1.2300 -1.0743 -54.5649Rlegroot3.3706 8.5802 -53.9321Rthigh3.3706 8.5802 -53.9321Rshin2.7365 3.8570 -55.2306Rfootroot3.2952 1.1392 -51.5247Rfoot3.2952 1.1392 -51.5247Rtoe3.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)(0,0,0)(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() == 0cout << "时间: " << 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人体当前位置=初始骨骼定义位置position+动画帧定义的关节translate 人体当前位置=初始骨骼定义位置position+动画帧定义的关节translate
后记
本篇博客主要就是未后续的将任意的关节数据设置到OGRE
中做准备, 重点就是要了解旋转矩阵的到全局位置的计算方法。
以后想套入比如BVH或者ASF/AMC动捕数据的时候, 只需要保证初始骨骼姿态相同的情况下, 就可以将欧拉角转换得到的旋转矩阵应用到Ogre
中,重点注意细节上的问题, 比如这个矩阵是否需要转置或者翻转后再运用之类的。
matlab仿真代码:链接:https://pan.baidu.com/s/1kWUF3iZ 密码:u792
Ogre仿真代码:链接: https://pan.baidu.com/s/1uWEJ39pJcSvHvHSmTRD6_w 密码: stmc
最后再贴一下可视化初始骨骼姿态结果(代码都包含了)
Run
运动第一帧的结果