XSI Vertex Animation and OGRE Facial Animation

    目前OGRE只有XSI 5.x、Maya的導出器及oFusion Pro for 3ds max(收費)支持頂點動畫的導出,而在衆多建模軟件中XSI(http://www.softimage.com/)以其對變形動畫的支持最爲突出,OGRE提供的Facial Demo中使用的Dr. Bunsen的頭就是XSI中提供的一個示例動畫,該模型就是使用對頂點簇(cluster)的位置改變來產生動畫。

XSI變形動畫(頂點動畫)的製作
1、 將Mode改爲Shape Modeling Mode;
2、將關鍵幀移至期望的動畫開始處存儲並應用物體或頂點簇(cluster)的shape key,該key記錄了物體的初始形狀;
3、將關鍵幀移至所期望的變形動畫結束處,利用Animation中的Deform對物體或頂點簇產生變形;
4、存儲並應用shape key,該key記錄了物體變形後的狀態;
5、你可以在Animation中的Shape菜單的Shape Manager…改變物體變形的程度。
在OGRE中播放頂點動畫
我們需要從ogre官方網站上下載XSI模型導出器安裝後(安裝過程中需要XSI 5.x的目錄)在XSIExport中就會出現OGRE mesh/Skeleton菜單項,你需要按照下列步驟來導出頂點動畫模型:SoftImage XSI 5.0 Exporter v1.2.3
1、 選中需要導出的模型;
2、在ogre export的Basic頁中輸入導出mesh的路徑及文件名,在Materials頁中填入導出材質的路徑及文件名(需要說明的是在該導出器中材質導出有BUG,不一定能正確導出材質),在Animation頁中選中Export Vertex Animation複選框(其實,如果選中物體中有Vertex Animation或Export Skeleton,那麼複選框會自動選上),如果模型包含動畫,則在Animation欄目會出現動畫的信息,包括:名字、開始幀、結束幀、採樣頻率;
3、按ok導出。
我們可以在ogre提供的Facial Demo基礎上修改代碼以適合導出的模型,同時,你可以利用OgreCommandLineTools中的OgreXmlConverter來將.mesh文件逆向轉換爲.xml文件便於研究模型及動畫數據的格式,下面給出了一個簡單球體頂點動畫模型及其xml數據:
<mesh>
    <submeshes>
        <submesh material="Scene_Material" usesharedvertices="false" use32bitindexes="false" operationtype="triangle_list">
            <faces count="288">
                <face v1="0" v2="1" v3="2" />
                                                                 :(省略)
                                                                 :
                <face v1="145" v2="16" v3="17" />
            </faces>
            <geometry vertexcount="146">
                <vertexbuffer positions="true">[1] 
                    <vertex>
                        <position x="-3.21394" y="-9.39693" z="1.16978" />
                    </vertex>
                                                                                            
                                                                                            
                    <vertex>
                        <position x="-3.21394" y="9.39693" z="-1.16978" />
                   </vertex>
                </vertexbuffer>
                <vertexbuffer normals="true">[2] 
                    <vertex>
                        <normal x="-0.321129" y="-0.939795" z="0.116881" />
                    </vertex>
                                                                                            
                                                                                             
                    <vertex>
                        <normal x="-0.321129" y="0.939795" z="-0.116881" />
                    </vertex>
                </vertexbuffer>
            </geometry>
        </submesh>
    </submeshes>
    <submeshnames>
        <submeshname name="sphere" index[3] ="0" />
    </submeshnames>
    <poses>[4] 
            <poseoffset index[7] ="0" x="0" y="-0.457566" z="0.0569602" />
                                                                                            
                                                                                            
            <poseoffset index="145" x="0" y="0.457566" z="-0.0569602" />
        </pose>
    </poses>
            <tracks>[10] 
                    <keyframes>[12] 
                        <keyframe time="0">
                            <poseref poseindex="0" influence="1" />[13] 
                        </keyframe>
                        <keyframe time="0.0344828">
                            <poseref poseindex="0" influence="1" />[14] 
                        </keyframe>
                        <keyframe time="1.68966">
                            <poseref poseindex="0" influence="0.00123248" />[15] 
                            <poseref poseindex="1" influence="0.998767" />[16] 
                        </keyframe>
                        <keyframe time="1.72414" />
                    </keyframes>
                </track>
            </tracks>
        </animation>
    </animations>
</mesh> 

OGRE程序實現分下面步驟:

1、  由於OGRE中採用了CEGUICrazy Eddie's GUI System)作爲GUI,所以我們可以通過其提供的CELayoutEditor來編輯出想要的GUI。下圖是該程序的一個簡單的GUI,右邊空白區域作爲場景渲染區;

2、 將其保存成一個layout文件,如aaa.layout,並拷貝至OGRESDK/media/gui下。

下面我着重對程序代碼進行說明:
000001    #include <CEGUI/CEGUIImageset.h>
000002    #include <CEGUI/CEGUISystem.h>
000003    #include <CEGUI/CEGUILogger.h>
000004    #include <CEGUI/CEGUISchemeManager.h>
000005    #include <CEGUI/CEGUIWindowManager.h>
000006    #include <CEGUI/CEGUIWindow.h>
000007    #include <CEGUI/elements/CEGUICombobox.h>
000008    #include <CEGUI/elements/CEGUIListbox.h>
000009    #include <CEGUI/elements/CEGUIListboxTextItem.h>
000010    #include <CEGUI/elements/CEGUIPushButton.h>
000011    #include <CEGUI/elements/CEGUIScrollbar.h>
000012    #include <CEGUI/elements/CEGUIStaticImage.h>
000013    #include <CEGUI/elements/CEGUIRadioButton.h>
000014    #include "OgreCEGUIRenderer.h"
000015    #include "OgreCEGUIResourceProvider.h"
000016    #include "ExampleApplication.h"
000017    
000018    // 將OGRE中的鼠標按鍵映射成CEGUI的按鍵
000019    CEGUI::MouseButton convertOgreButtonToCegui(int buttonID)
000020    {
000021        switch (buttonID)
000022        {
000023        case MouseEvent::BUTTON0_MASK:
000024            return CEGUI::LeftButton;
000025        case MouseEvent::BUTTON1_MASK:
000026            return CEGUI::RightButton;
000027        case MouseEvent::BUTTON2_MASK:
000028            return CEGUI::MiddleButton;
000029        case MouseEvent::BUTTON3_MASK:
000030            return CEGUI::X1Button;
000031        default:
000032            return CEGUI::LeftButton;
000033        }
000034    }
000035    
000036    AnimationState* morphAnimState;    // 定義用於自動播放的變形動畫的指針
000037    AnimationState* manualAnimState;// 定義用於手動播放的變形動畫的指針
000038    VertexPoseKeyFrame* manualKeyFrame;// 定義用於手動播放VertexPose關鍵幀指針
000039    
000040    String scrollbarNames = "aaa/Morph_Scroll";    // 用於產生橫向滾動條狀態變化時,確定是哪個滾動條(當然這是針對有多個滾動條的情況)
000041    
000042    CEGUI::Scrollbar* scrollbars;    // 滾動條指針,通過後面的代碼從layout文件中獲取
000043    
000044    // 定義GUI事件監聽器類
000045    class GuiFrameListener : public ExampleFrameListener, public MouseMotionListener, public MouseListener
000046    {
000047    private:
000048        CEGUI::Renderer* mGUIRenderer;
000049        bool mShutdownRequested;
000050    
000051    public:
000052        GuiFrameListener(RenderWindow* win, Camera* cam, CEGUI::Renderer* renderer)
000053            : ExampleFrameListener(win, cam, true, true),
000054            mGUIRenderer(renderer),
000055            mShutdownRequested(false)
000056        {
000057            mEventProcessor->addMouseMotionListener(this);// 添加鼠標動作監聽器
000058            mEventProcessor->addMouseListener(this);     // 添加鼠標監聽器
000059            mEventProcessor->addKeyListener(this);         // 添加鍵盤按鍵監聽器
000060        }
000061    
000062        // 設置退出標誌
000063        void requestShutdown(void)
000064        {
000065            mShutdownRequested = true;
000066        }
000067    
000068        bool frameEnded(const FrameEvent& evt)
000069        {
000070            if (mShutdownRequested)
000071                return false;
000072            else
000073                return ExampleFrameListener::frameEnded(evt);
000074        }
000075    
000076        void mouseMoved (MouseEvent *e)
000077        {
000078            // CEGUI類方法,將鼠標移動事件及其參數“注入”GUI系統以便處理
000079            CEGUI::System::getSingleton().injectMouseMove(
000080                e->getRelX() * mGUIRenderer->getWidth(),
000081                e->getRelY() * mGUIRenderer->getHeight());
000082            // OGRE類方法,銷燬該事件消息,以使其不被它的產生者以默認的方式來處理
000083            e->consume();
000084        }
000085    
000086        void mouseDragged (MouseEvent *e)
000087        {
000088            mouseMoved(e);
000089        }
000090    
000091        void mousePressed (MouseEvent *e)
000092        {
000093            CEGUI::System::getSingleton().injectMouseButtonDown(
000094                convertOgreButtonToCegui(e->getButtonID()));
000095            e->consume();
000096        }
000097    
000098        void mouseReleased (MouseEvent *e)
000099        {
000100            CEGUI::System::getSingleton().injectMouseButtonUp(
000101                convertOgreButtonToCegui(e->getButtonID()));
000102            e->consume();
000103        }
000104    
000105        void mouseClicked(MouseEvent* e) {}
000106        void mouseEntered(MouseEvent* e) {}
000107        void mouseExited(MouseEvent* e) {}
000108    
000109        void keyPressed(KeyEvent* e)
000110        {
000111            if(e->getKey() == KC_ESCAPE)
000112            {
000113                mShutdownRequested = true;
000114                e->consume();
000115                return;
000116            }
000117    
000118            if (e->getKey() == KC_SYSRQ)
000119            {
000120                mWindow->writeContentsToTimestampedFile("screenshot", ".png");
000121            }
000122    
000123            CEGUI::System::getSingleton().injectKeyDown(e->getKey());
000124            CEGUI::System::getSingleton().injectChar(e->getKeyChar());
000125            e->consume();
000126        }
000127    
000128        void keyReleased(KeyEvent* e)
000129        {
000130            CEGUI::System::getSingleton().injectKeyUp(e->getKey());
000131            e->consume();
000132        }
000133        void keyClicked(KeyEvent* e)
000134        {
000135            // 什麼事都不做
000136            e->consume();
000137        }
000138    
000139        bool frameStarted(const FrameEvent& evt)
000140        {
000141            morphAnimState->addTime(evt.timeSinceLastFrame);
000142            return ExampleFrameListener::frameStarted(evt);
000143    
000144        }
000145    };
000146    
000147    // 定義變形動畫的應用程序類主類
000148    class MorphApplication : public ExampleApplication
000149    {
000150    private:
000151        CEGUI::OgreCEGUIRenderer* mGUIRenderer; // 定義OGRE中CEGUI的渲染器指針
000152        CEGUI::System* mGUISystem; // 定義GUI系統指針
000153        CEGUI::Listbox* mList;    // 列表框指針
000154    
000155    public:
000156        FacialApplication()
000157            : mGUIRenderer(0),
000158            mGUISystem(0)
000159        {
000160    
000161        }
000162    
000163        ~FacialApplication()
000164        {
000165            if(mGUISystem)
000166            {
000167                delete mGUISystem;
000168                mGUISystem = 0;
000169            }
000170            if(mGUIRenderer)
000171            {
000172                delete mGUIRenderer;
000173                mGUIRenderer = 0;
000174            }
000175        }
000176    
000177    protected:
000178    
000179        bool mPlayAnimation; // 自動/手動控制Radio Button對應變量
000180    
000181        // 滾動條狀態改變事件相應函數
000182        bool handleScrollChanged(const CEGUI::EventArgs& e)
000183        {
000184            if (!mPlayAnimation)
000185            {
000186                const CEGUI::WindowEventArgs& args = static_cast<const CEGUI::WindowEventArgs&>(e);
000187                // 得到Scroll的名字
000188                String name = args.window->getName().c_str();
000189                // 判斷是否是我想要改變的那一個(在多個scrollbar時)
000190                if(scrollbarNames == name)
000191                {
000192                    // 調試信息:顯示scrollbar當前位置(用於調試)
000193                    mWindow->setDebugText(StringConverter::toString(scrollbars->getScrollPosition()));
000194                    // 根據滾動條的位置更新頂點變形百分比
000195                    manualKeyFrame->updatePoseReference(
000196                        1, scrollbars->getScrollPosition());
000197                    // 得到 AnimationStateSet(動畫狀態集)指針,
000198                    // 並通知當前動畫狀態已經改變
000199                    manualAnimState->getParent()->_notifyDirty();
000200                }
000201            }
000202            return true;
000203    
000204        }
000205    
000206        // Radio Button狀態改變響應函數
000207        bool handleRadioChanged(const CEGUI::EventArgs& e)
000208        {
000209            mPlayAnimation = !mPlayAnimation;
000210            morphAnimState->setEnabled(mPlayAnimation);
000211            manualAnimState->setEnabled(!mPlayAnimation);
000212            // 允許/禁止scrollbar
000213            scrollbars->setEnabled(!mPlayAnimation);
000214    
000215            return true;
000216    
000217        }
000218    
000219        // List狀態改變響應函數
000220        bool handleListBoxChanged(const CEGUI::EventArgs& e)
000221        {
000222            // 根據選擇項,改變多邊形模式
000223            if(mList->getFirstSelectedItem()->getText() == CEGUI::String("Solid Mode"))
000224                mCamera->setPolygonMode(PM_SOLID);
000225            else if(mList->getFirstSelectedItem()->getText() == CEGUI::String("Wireframe Mode"))
000226                mCamera->setPolygonMode(PM_WIREFRAME);
000227            else
000228                mCamera->setPolygonMode(PM_POINTS);
000229            return true;
000230        }
000231    
000232        // 創建場景函數,重寫ExampleApplication::createScene()
000233        void createScene(void)
000234        {
000235            // 設置環境光
000236            mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
000237    
000238            // 創建一個點光源
000239            Light* l = mSceneMgr->createLight("MainLight");
000240            // 設置光源位置
000241            l->setPosition(20,80,50);
000242            // 設置表面反射的漫射光顏色
000243            l->setDiffuseColour(0.5, 0.0, 0.5);
000244    
000245            // 創建一個點光源
000246            l = mSceneMgr->createLight("MainLight2");
000247            // 設置光源位置
000248            l->setPosition(-120,-80,-50);
000249            // 設置表面反射的漫射光顏色
000250            l->setDiffuseColour(0.7, 0.7, 0.6);
000251    
000252            // 載入mesh
000253            MeshPtr mesh = MeshManager::getSingleton().load("ss.mesh", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
000254            Animation* anim = mesh->createAnimation("smile", 0);
000255            // 創建頂點動畫軌跡,參數說明請參考OGRE中的人臉動畫一文
000256            VertexAnimationTrack* track = anim->createVertexTrack(1, VAT_POSE);
000257            // 創建時間位置爲0的關鍵幀並將其添加到動畫中
000258            manualKeyFrame = track->createVertexPoseKeyFrame(0);
000259            // 添加一個新的pose參考,第一個參數爲poseindex,第二個參數爲效果值(這裏是0%)
000260            manualKeyFrame->addPoseReference(1, 0.0f);
000261    
000262            // 創建GUI渲染器對象和GUI系統對象
000263            mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow,
000264                Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);
000265    
000266            mGUISystem = new CEGUI::System(mGUIRenderer);
000267            // 開啓GUI系統日誌
000268            CEGUI::Logger::getSingleton().setLoggingLevel(CEGUI::Informative);
000269            // 創建實體,命名爲Head1
000270            Entity* head = mSceneMgr->createEntity("Head1", "ss.mesh");
000271            // 從mesh中取得名字爲ss的自動動畫(請參考xml中的tag<animations>)
000272            morphAnimState = head->getAnimationState("ss");
000273            morphAnimState->setEnabled(true);
000274            // 得到前面mesh->createAnimation("smile", 0)創建的手動動畫smile,並將其時間位置置爲0
000275            manualAnimState = head->getAnimationState("smile");
000276            manualAnimState->setTimePosition(0);
000277            // 創建場景子節點,並將實體head加入到其中
000278            SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
000279            headNode->attachObject(head);
000280            // 設置攝像機參數
000281            mCamera->setPosition(0, 0, 50);
000282            mCamera->lookAt(0,0,0);
000283    
000284            // 載入GUI皮膚元素,並將layout文件作爲一個sheet(頁)添加進GUI系統對象中
000285            CEGUI::SchemeManager::getSingleton().loadScheme(
000286                (CEGUI::utf8*)"TaharezLookSkin.scheme");
000287            mGUISystem->setDefaultMouseCursor(
000288                (CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow");
000289            mGUISystem->setDefaultFont((CEGUI::utf8*)"BlueHighway-12");
000290    
000291            CEGUI::Window* sheet =
000292                CEGUI::WindowManager::getSingleton().loadWindowLayout(
000293                (CEGUI::utf8*)"aaa.layout");
000294            mGUISystem->setGUISheet(sheet);
000295            // 得到窗口管理器
000296            CEGUI::WindowManager& wmgr = CEGUI::WindowManager::getSingleton();
000297            // 從窗口管理器中得到滾動條對象
000298            scrollbars = static_cast<CEGUI::Scrollbar*>(
000299                wmgr.getWindow(scrollbarNames));
000300            // 註冊滾動條事件處理函數
000301            scrollbars->subscribeEvent(
000302                CEGUI::Scrollbar::EventScrollPositionChanged,
000303                CEGUI::Event::Subscriber(&FacialApplication::handleScrollChanged, this));
000304            // 缺省禁止使用滾動條
000305            scrollbars->setEnabled(false);
000306            // 得到Animation Radio按鈕對象
000307            CEGUI::RadioButton* btn = static_cast<CEGUI::RadioButton*>(
000308                wmgr.getWindow((CEGUI::utf8*)"aaa/Radio/Play"));
000309            // 允許使用Radio按鈕
000310            btn->setSelected(true);
000311            // 註冊該Radio按鈕事件處理函數
000312            btn->subscribeEvent(CEGUI::RadioButton::EventSelectStateChanged,
000313                CEGUI::Event::Subscriber(&FacialApplication::handleRadioChanged, this));
000314    
000315            mPlayAnimation = true;
000316            // 得到List對象,並向其中加入Solid Mode/Wireframe Mode/Point Mode三個選項
000317            mList = static_cast<CEGUI::Listbox*>(
000318                wmgr.getWindow((CEGUI::utf8*)"aaa/ListBox/PolygonMode"));
000319            CEGUI::ListboxTextItem *listboxitem =
000320                new CEGUI::ListboxTextItem ("Solid Mode");
000321            listboxitem->setSelectionBrushImage("TaharezLook", "ListboxSelectionBrush");
000322            listboxitem->setSelected(mList->getItemCount() == 0);
000323            mList->addItem(listboxitem);
000324            listboxitem = new CEGUI::ListboxTextItem("Wireframe Mode");
000325            listboxitem->setSelectionBrushImage("TaharezLook", "ListboxSelectionBrush");
000326            mList->addItem(listboxitem);
000327            listboxitem = new CEGUI::ListboxTextItem("Point Mode");
000328            listboxitem->setSelectionBrushImage("TaharezLook", "ListboxSelectionBrush");
000329            mList->addItem(listboxitem);
000330            // 註冊List選擇事件處理函數
000331            mList->subscribeEvent(CEGUI::Listbox::EventSelectionChanged ,
000332                CEGUI::Event::Subscriber(&FacialApplication::handleListBoxChanged , this));
000333            mCamera->setPolygonMode(PM_SOLID);
000334        }
000335    
000336        // 創建幀(事件)監聽器
000337        void createFrameListener(void)
000338        {
000339            mFrameListener= new GuiFrameListener(mWindow, mCamera, mGUIRenderer);
000340            mRoot->addFrameListener(mFrameListener);
000341        }
000342    
000343        void setupEventHandlers(void)
000344        {
000345        }
000346    
000347    
000348        void setupLoadedLayoutHandlers(void)
000349        {
000350        }
000351    
000352        bool handleQuit(const CEGUI::EventArgs& e)
000353        {
000354            static_cast<GuiFrameListener*>(mFrameListener)->requestShutdown();
000355            return true;
000356        }
000357    
000358    
000359    };
000360    
000361    #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
000362    #define WIN32_LEAN_AND_MEAN
000363    #include "windows.h"
000364    
000365    INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
000366    #else
000367    int main(int argc, char *argv[])
000368    #endif
000369    {
000370    
000371        // 創建應用程序對象
000372        FacialApplication app;
000373    
000374        try {
000375            app.go();    // 運行
000376        } catch( Ogre::Exception& e ) {
000377    #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
000378            MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
000379    #else
000380            std::cerr << "An exception has occured: " <<
000381                e.getFullDescription().c_str() << std::endl;
000382    #endif
000383        }
000384    
000385    
000386        return 0;
000387    }

程序運行的最終效果:


 [1]頂點座標
 [2]頂點法線
 [3]Submesh的索引號,按照submeshs中submesh出現的先後順序還確定。
 [4]該tag下pose的poseoffset用於標明index對應頂點產生變形後的偏移量,在手動動畫中我們將根據橫向滑塊位置的百分比來確定頂點變化偏移量的百分之幾。
 [5]索引號爲0的頂點動畫,在手動動畫關鍵幀的addPoseReferenceupdatePoseReference函數的第一個參數中引用。
 [6]索引號爲1的頂點動畫。
 [7]頂點的索引號,按照vertexbuffer中vertex出現的先後次序來確定。
 [8]定義自動播放的動畫。
 [9]定義一個名字爲ss長度爲1.72414(該值可作爲橫向滑塊的長度,請參考layout說明)的動畫。
 [10]定義動畫軌跡。
 [11]針對索引號爲0的submesh定義類型爲pose的動畫軌跡。
 [12]定義關鍵幀
 [13]在幀時間爲0時,讓索引號爲0的頂點動畫達到100%變形,因爲0號pose沒有poseoffset數據,因而表現爲保持原來形態不變。
 [14]同上。
 [15]產生?%的變形,但是由於沒有poseoffset數據,故實際上沒有任何效果。
 [16]在幀時間爲1.68966時,讓索引號爲1的頂點動畫達到99%形變,也就是說從幀時間0到此時是一個漸變的效果,OGRE會根據時間間隔與端點時間的形變百分比自動計算中間的形變百分比。
發佈了69 篇原創文章 · 獲贊 2 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章