Ogre共享骨骼与两种骨骼驱动方法
前言
最近業務中用到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, don't call it yourself unless you really know what you're 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_cast<SkeletonPtr&>(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找得到,有問題直接公眾號私信。
總結
以上是生活随笔為你收集整理的Ogre共享骨骼与两种骨骼驱动方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql升级-rpm安装
- 下一篇: 仿射变换和透视变换