专业网站建设公司在线咨询,新余网站建设,百度最新收录方法,企业文档管理wordpress前言
最近业务中用到Ogre做基于3D关键点虚拟角色骨骼驱动#xff0c;但是遇到两个问题#xff1a;
身体、头、眼睛、衣服等mesh的骨骼是分开的#xff0c;但是骨骼结构都是一样的#xff0c;需要设置共享骨骼驱动的时候可以直接修改骨骼旋转量#xff0c;或者将旋转量存…前言
最近业务中用到Ogre做基于3D关键点虚拟角色骨骼驱动但是遇到两个问题
身体、头、眼睛、衣服等mesh的骨骼是分开的但是骨骼结构都是一样的需要设置共享骨骼驱动的时候可以直接修改骨骼旋转量或者将旋转量存到动画帧里面去后者会根据播放时间间隔自动插帧
国际惯例参考博客
Ogre3D 实现角色换装
【Ogre-windows】旋转矩阵及位置解析
Ogre 换装系统 shareSkeletonInstanceWith
代码实现
下面分别包括共享骨骼、关节驱动、动画帧驱动、遇到的坑
其中关节驱动和动画帧驱动方法所创建的运动为左小腿伸直弯曲再伸直弯曲再回到伸直弯曲如此反复。
共享骨骼
核心函数是shareSkeletonInstanceWith能够指定将谁的骨骼共享给谁
但是需要注意共享与被共享的骨骼具有同样的拓扑结构不然会报错。
如果想强制共享那就需要使用_notifySkeleton官方描述如下
Internal notification, used to tell the Mesh which Skeleton to use without loading it.
remarks
This is only here for unusual situation where you want to manually set up a Skeleton. Best to let OGRE deal with this, dont call it yourself unless you really know what youre doing.意思就是说告诉一个mesh用另一个骨骼但是不要轻易去用它因为很容易出现问题待会实验就知道了。
额外代码就不贴了源码看文末就行。
首先读取三个模型两个Sinbad.mesh和一个jaiqua.mesh
//主模型
ent scnMgr-createEntity(Sinbad.mesh);
SceneNode* node scnMgr-getRootSceneNode()-createChildSceneNode();
node-attachObject(ent);
// 副模型1
ent1 scnMgr-createEntity(jaiqua.mesh);
SceneNode* node1 node-createChildSceneNode();
node1-setPosition(10, 0, 0);
node1-attachObject(ent1);//副模型2
ent2 scnMgr-createEntity(Sinbad.mesh);
SceneNode* node2 node-createChildSceneNode();
node2-setPosition(-10, 0, 0);
node2-attachObject(ent2);然后共享骨骼
ent1-shareSkeletonInstanceWith(ent);
ent2-shareSkeletonInstanceWith(ent);会发现报错
case Exception::ERR_RT_ASSERTION_FAILED: throw RuntimeAssertionException(number, desc, src, file, line);就是因为jaiqua.mesh与Sinbad.mesh的骨骼不一样所以对于jaiqua.mesh必须增加
ent1-getMesh()-_notifySkeleton(const_castSkeletonPtr(ent-getMesh()-getSkeleton()) );如此便能成功运行了如下图所示左到右分别是副模型2、主模型、副模型1由于副模型1和主模型具有不同的骨骼所以无法正常驱动。 共享骨骼的作用就在于有时候同一个模型分成了几部分设计比如头和身体是分开的便于将表情驱动和肢体驱动分开但是它俩在设计的时候都是完整的人体骨骼所以需要共享骨骼做一个同步。
修改关节旋转的驱动
动画帧驱动方法
分为两种一种是一边创建一边播放另一种是创建完毕再播放
先创建再播放
首先要知道你想创建的动画时长、帧率、播放速度我这里为了测试帧的插值效果创建了6s的动画帧序列首先初始化
anim skel-createAnimation(myanim, 6);
anim-setInterpolationMode(Animation::IM_SPLINE);
tracksnew anim-createNodeTrack(lknee-getHandle(), lknee);
createAnim(); //创建动画帧//animation play
as ent-getAnimationState(myanim);
as-setEnabled(true);
as-setLoop(false);接下来就是创建动画帧具体的创建方法在之前的博客已经介绍过这里直接贴代码
void MyTestApp::createAnim() {for (int i 0; i 6; i) {TransformKeyFrame *newKF tracksnew-createNodeKeyFrame(i);Quaternion quat;quat.FromAngleAxis(Degree(i%2? 0.0f: -90.0f), Vector3::UNIT_X);newKF-setRotation( quat);prev_rotate quat;}ent-refreshAvailableAnimationState();
}注意创建完毕要刷新一下动画的状态不然修改无法生效。
最后在frameRenderingQueued里面设置一下播放间隔
as-addTime(0.033333);表示每次播放接下来的0.0333帧数据如果没有就会自动插值出来。 一边创建一边播放
同样先在setup里面初始化动画但是记得刷新
// create animation
anim skel-createAnimation(myanim, 6);
anim-setInterpolationMode(Animation::IM_SPLINE);
tracksnew anim-createNodeTrack(lknee-getHandle(), lknee);
ent-refreshAvailableAnimationState();//animation
as ent-getAnimationState(myanim);
as-setEnabled(true);
as-setLoop(false);接下来直接在渲染主线程里面去写入动画帧一边渲染一边写
// frame rendering
int i 0;
bool MyTestApp::frameRenderingQueued(const FrameEvent evt){ i;TransformKeyFrame *newKF tracksnew-createNodeKeyFrame(i);Quaternion quat;quat.FromAngleAxis(Degree(i%2? 0.0f: -90.0f), Vector3::UNIT_X);newKF-setRotation(quat);ent-refreshAvailableAnimationState();std::cout as-getTimePosition() std::endl;as-addTime(0.033333);return true;
}这里需要注意一个问题渲染是从第0帧开始的但是你直接修改第0帧这个数值在渲染进行结束前是无法生效的也就是说在渲染线程里面修改的帧必须在当前帧渲染完毕才能生效所以你修改的帧必须在当前渲染帧的后面所以上述代码直接修改的第1帧并不是跟先创建动画后播放一样修改的第0帧。 直接修改关节旋转
非常简单跟创建动画序列无任何关系只需要在setup中将相关关节的setManuallyControlled设置为true
SkeletonInstance *skel ent-getSkeleton();
lshoulder skel-getBone(Humerus.L); lshoulder-setManuallyControlled(true);
lknee skel-getBone(Calf.L); lknee-setManuallyControlled(true);然后再在渲染线程中修改骨骼旋转
int i 0;
bool MyTestApp::frameRenderingQueued(const FrameEvent evt){ i;Quaternion quat;quat.FromAngleAxis(Degree(i%2? 0.0f: -90.0f), Vector3::UNIT_X);lknee-setOrientation(quat);return true;
}因为这个渲染速度太快了所以必须用断点才能看清每一帧的驱动效果视频后半段是取消断点一直驱动的结果 很容易发现这种方法虽然简单但是共享骨骼会失效所以一旦使用此种方法驱动两套一样的骨骼必须手动同步把两套骨骼的所有关节setManuallyControlled设置为true记住要删掉共享骨骼的代码先
for (int j 0; j skel-getNumBones(); j) {skel-getBone(j)-setManuallyControlled(true);skel2-getBone(j)-setManuallyControlled(true);}然后每次修改都要同步每个关节遍历一遍将两个骨骼对应关节同步好
for (int j 0; j skel-getNumBones(); j) { skel2-getBone(j)-setOrientation(skel-getBone(j)-getOrientation());}这样就可以同步运动啦同样没有帧间平滑 注意坑
一定不要在帧动画驱动方法中将骨骼的setManuallyControlled设置为true了不然每一帧都是基于上一帧的结果驱动正常的骨骼动画应该是类似于BVH动画每一帧都应该是独立的且基于初始姿态的变换比如A-pos或者T-pos假设动画帧驱动的方法开启了手动控制那么动画结果就是 后记
本篇博文记录了工作中遇到了多个骨骼共享同一套动作的方法同时这种方法都支持实时驱动比如通过3D关键点计算得到旋转量以后立马渲染出来。
后续应该会更新unity和Unreal Engine里面的肢体驱动方法主要是将引擎与python通过socket通信传递深度学习提取的3D关键点然后使用FABRIK或者其它动力学方法驱动虚拟角色有兴趣可以关注一下。
本博文同步更新到微信公众号中有兴趣可关注一波代码在微信公众号简介的github找得到有问题直接公众号私信。