XSI Vertex Animation and OGRE Facial Animation

来源:互联网 发布:数据平滑处理算法spss 编辑:程序博客网 时间:2024/05/05 21:09

    目前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] 
        <pose target="submesh" index="0" name="s1" />[5] 
        <pose target="submesh" index="0" name="s2">[6] 
            <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>
    <animations>[8] 
        <animation name="ss" length="1.72414">[9] 
            <tracks>[10] 
                <track target="submesh" index="0" type="pose">[11] 
                    <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会根据时间间隔与端点时间的形变百分比自动计算中间的形变百分比。
原创粉丝点击