目前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
的目录)在
XSI
的
Export
中就会出现
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中采用了CEGUI(Crazy 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 }
程序运行的最终效果:
[3]Submesh的索引号,按照submeshs中submesh出现的先后顺序还确定。
[4]该tag下pose的poseoffset用于标明index对应顶点产生变形后的偏移量,在手动动画中我们将根据横向滑块位置的百分比来确定顶点变化偏移量的百分之几。
[5]索引号为0的顶点动画,在手动动画关键帧的
addPoseReference
与
updatePoseReference
函数的第一个参数中引用。
[7]顶点的索引号,按照vertexbuffer中vertex出现的先后次序来确定。
[9]定义一个名字为ss长度为1.72414(该值可作为横向滑块的长度,请参考layout说明)的动画。
[11]针对索引号为0的submesh定义类型为pose的动画轨迹。
[13]在帧时间为0时,让索引号为0的顶点动画达到100%变形,因为0号pose没有poseoffset数据,因而表现为保持原来形态不变。
[15]产生?%的变形,但是由于没有poseoffset数据,故实际上没有任何效果。
[16]在帧时间为1.68966时,让索引号为1的顶点动画达到99%形变,也就是说从帧时间0到此时是一个渐变的效果,OGRE会根据时间间隔与端点时间的形变百分比自动计算中间的形变百分比。