游戏等软件中经常会遇到能活动的人,其活动过程能够随情景变化,非常具体吸引力, 分析其实现原理,常见的有三种:一种是类 似视频,gif 动画那样的, 事先预存了每帧图像信息,更具要求快速播放指定的图像序列,形成动画,此类可以图像源来自摄像, 所以很逼真,效率也很高,无需太多的数值 运算,但扩充非常麻烦,维护代价很大,数 据过于冗余。还有一类是类似 flash 动画文 件类的,通过脚本配合界面元素发生位置的 便宜,其使用环境很有限,最后就是游戏中 非常常用的蒙皮-骨骼算法。 本文就最后一个 加以说明,其算法在性能和效果之间可以随 意取舍,扩充有无限可能,其思想是非常符 合人体本身的移动本质,所以建模,实现都 很快捷,是三维模拟人体的首选技术。
#include "ExampleApplication.h"
#include "atldebugapi.h"
#define NUM_JAIQUAS 1
AnimationState*
mAnimState[NUM_JAIQUAS];
Real mAnimationSpeed[NUM_JAIQUAS];
Vector3 mSneakStartOffset;
Vector3 mSneakEndOffset;
Quaternion mOrientations[NUM_JAIQUAS];
Vector3 mBasePositions[NUM_JAIQUAS];
SceneNode* mSceneNode[NUM_JAIQUAS];
Degree mAnimationRotation(-60);
Real mAnimChop = 1.256666f;
Real mAnimChopBlend = 0.3f;
va_start(ptr, pszFormat);
char szBuf[2048];
vsprintf(szBuf,pszFormat, ptr);
OutputDebugString(szBuf);
va_end(ptr);
}
// Event handler to animate
class SkeletalAnimationFrameListener :
public ExampleFrameListener
{
protected:
public:
SkeletalAnimationFrameListener(RenderWindow* win, Camera* cam, conststd::string &debugText)
: ExampleFrameListener(win, cam)
{
mDebugText = debugText;
}
bool frameRenderingQueued(const FrameEvent& evt)
{
if( ExampleFrameListener::frameRenderingQueued(evt) == false )
return false;
for (int i = 0; i < NUM_JAIQUAS; ++i)
{
Real inc = evt.timeSinceLastFrame * mAnimationSpeed[i];
//AtlTrace("real o:%f pos:%f", inc, mAnimState[i]->getTimePosition());
if ((mAnimState[i]->getTimePosition() + inc) >= mAnimChop)
{
mAnimState[i]->setTimePosition(0);
}
else
{
Quaternionrot(mAnimationRotation,Vector3::UNIT_Y);
Vector3 startoffset = mSceneNode[i]->getOrientation() * -mSneakStartOffset;
Vector3 endoffset = mSneakEndOffset;
Vector3 offset = rot * startoffset;
Vector3 currEnd = mSceneNode[i]->getOrientation() * endoffset + mSceneNode[i]->getPosition();
//mSceneNode[i]->rotate(mSceneNode[ i]->getOrientation()+15);
offset = Vector3(0, 0, -0.02);
mSceneNode[i]->setPosition(mSceneNo de[i]->getPosition() + offset);
Vector3 rotAxis = Vector3(0,1,0);
mSceneNode[i]->rotate(rotAxis, Radian(10.f));
mAnimState[i]->addTime(inc);
}
}
return true;
}
};
class SkeletalApplication : public ExampleApplication
{
public:
SkeletalApplication() {}
protected:
std::string mDebugText;
// Just override the mandatory create scene method
void createScene(void)
{
mSceneMgr->setShadowTechnique(SHADO WTYPE_TEXTURE_MODULATIVE);
mSceneMgr->setShadowTextureSize(512 );
mSceneMgr->setShadowColour(ColourValue(0.6, 0.6, 0.6));
// Setup animation default
Animation::setDefaultInterpolationMode(Animation::IM_LINEAR);
Animation::setDefaultRotationInterpolationMode(Animation::RIM_LINEAR);
// Set ambient light
mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
// The jaiqua sneak animation doesn't loop properly, so lets hack it so it does
// We want to copy the initialkeyframes of all bones, but alter the Spineroot
// to give it an offset of where the animation ends
SkeletonPtr skel = SkeletonManager::getSingleton().load( "jaiqua.skeleton", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Animation* anim = skel->getAnimation("Run");
Animation::NodeTrackIterator trackIter =anim->getNodeTrackIterator();
Entity *ent;
Real rotInc = Math::TWO_PI / (float)NUM_JAIQUAS;
Real rot = 0.0f;
q.FromAngleAxis(Radian(rot), Vector3::UNIT_Y);
mOrientations[i] = q;
mBasePositions[i] = q * Vector3(0,0,-20);
ent = mSceneMgr->createEntity("jaiqua" + StringConverter::toString(i), "jaiqua.mesh");
// Add entity to the scenenode
mSceneNode[i] = mSceneMgr->getRootSceneNode()->createChildSceneNode();
mSceneNode[i]->attachObject(ent);
mSceneNode[i]->rotate(q);
mSceneNode[i]->translate(mBasePositions[i]);
mAnimState[i] = ent->getAnimationState("Walk");
mAnimState[i]->setEnabled(true);
mAnimState[i]->setLoop(false);
Math::RangeRandom(0.5, 1.5);
rot += rotInc;
}
// Give it a little ambience with lights
Light* l;
l = mSceneMgr->createLight("BlueLight");
l->setType(Light::LT_SPOTLIGHT);
l->setPosition(-200,150,-100);
Vector3 dir(-l->getPosition());
dir.normalise();
l->setDirection(dir);
l->setDiffuseColour(0.5, 0.5, 1.0);
l = mSceneMgr->createLight("GreenLight");
l->setType(Light::LT_SPOTLIGHT);
l->setPosition(0,150,-100);
dir = -l->getPosition();
dir.normalise();
l->setDirection(dir);
l->setDiffuseColour(0.5, 1.0, 0.5);
// Position the camera
mCamera->setPosition(100,20,0);
mCamera->lookAt(0,10,0);
// Report whether hardware skinning is enabled or not
Technique* t = ent->getSubEntity(0)->getMaterial()-> getBestTechnique();
Pass* p = t->getPass(0);
if (p->hasVertexProgram() && p->getVertexProgram()->isSkeletalAnimationIncluded())
mDebugText = "Hardwareskinning is enabled";
else
mDebugText = "Software skinning is enabled";
Plane plane;
plane.normal = Vector3::UNIT_Y;
plane.d = 100;
MeshManager::getSingleton().createPlane("Myplane", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 1500,1500,20,20,true,1,60,60,Vector3::UNIT_Z);
Entity* pPlaneEnt = mSceneMgr->createEntity( "plane", "Myplane" );
pPlaneEnt->setMaterialName("Examples/Rockwall");
pPlaneEnt->setCastShadows(false);
mSceneMgr->getRootSceneNode()->createChildSceneNode(Vector3(0,99,0))->attachObject(pPlaneEnt);
}
// Create new frame listener
void createFrameListener(void)
{
mFrameListener= new SkeletalAnimationFrameListener(mWindo w, mCamera, mDebugText);
mRoot->addFrameListener(mFrameListene r);
}
};
代码实现了一个能持绕圈续走动的小人,蒙皮骨骼算法是有 ogre 引擎实现的, 人体模型是由 3类数据来描述的,蒙皮(代 表肌肉),骨骼,皮肤。在运动的时候,骨骼是不会发生变形,只是做刚体运动,相对 于相连关节的旋转运动,确定了骨骼位置 后,所有的骨骼之间都有依赖关系,根据这 些约束条件,是能够找到从一个动作到另一 个动作的中间骨骼位置变化,这样关键帧需 求大大下降,进一步,相应覆盖在它们上面 的肌肉,皮肤就可以得出了,当然需要细心 的进行一些调整,关节上的网格会发生挤压 或拉伸。事实上得到一个好的人物模型并不 容易,一般的做法是选用先进的输入设备或 者专业的美工通过商业 3d 绘图软件获得, 之后转换为程序能够读取的数据文件。骨骼 动画引擎只是简单的把这些数据读入,根据 程序附加的一些与触发条件,时间相关的控 制指令就可以移动了。
#include "ExampleApplication.h"
#include "atldebugapi.h"
#define NUM_JAIQUAS 1
AnimationState*
mAnimState[NUM_JAIQUAS];
Real mAnimationSpeed[NUM_JAIQUAS];
Vector3 mSneakStartOffset;
Vector3 mSneakEndOffset;
Quaternion mOrientations[NUM_JAIQUAS];
Vector3 mBasePositions[NUM_JAIQUAS];
SceneNode* mSceneNode[NUM_JAIQUAS];
Degree mAnimationRotation(-60);
Real mAnimChop = 1.256666f;
Real mAnimChopBlend = 0.3f;
inline void _cdecl AtlTrace(LPCSTR pszFormat, ...)
{
va_list ptr;va_start(ptr, pszFormat);
char szBuf[2048];
vsprintf(szBuf,pszFormat, ptr);
OutputDebugString(szBuf);
va_end(ptr);
}
// Event handler to animate
class SkeletalAnimationFrameListener :
public ExampleFrameListener
{
protected:
public:
SkeletalAnimationFrameListener(RenderWindow* win, Camera* cam, conststd::string &debugText)
: ExampleFrameListener(win, cam)
{
mDebugText = debugText;
}
bool frameRenderingQueued(const FrameEvent& evt)
{
if( ExampleFrameListener::frameRenderingQueued(evt) == false )
return false;
for (int i = 0; i < NUM_JAIQUAS; ++i)
{
Real inc = evt.timeSinceLastFrame * mAnimationSpeed[i];
//AtlTrace("real o:%f pos:%f", inc, mAnimState[i]->getTimePosition());
if ((mAnimState[i]->getTimePosition() + inc) >= mAnimChop)
{
mAnimState[i]->setTimePosition(0);
}
else
{
Quaternionrot(mAnimationRotation,Vector3::UNIT_Y);
Vector3 startoffset = mSceneNode[i]->getOrientation() * -mSneakStartOffset;
Vector3 endoffset = mSneakEndOffset;
Vector3 offset = rot * startoffset;
Vector3 currEnd = mSceneNode[i]->getOrientation() * endoffset + mSceneNode[i]->getPosition();
//mSceneNode[i]->rotate(mSceneNode[ i]->getOrientation()+15);
offset = Vector3(0, 0, -0.02);
mSceneNode[i]->setPosition(mSceneNo de[i]->getPosition() + offset);
Vector3 rotAxis = Vector3(0,1,0);
mSceneNode[i]->rotate(rotAxis, Radian(10.f));
mAnimState[i]->addTime(inc);
}
}
return true;
}
};
class SkeletalApplication : public ExampleApplication
{
public:
SkeletalApplication() {}
protected:
std::string mDebugText;
// Just override the mandatory create scene method
void createScene(void)
{
mSceneMgr->setShadowTechnique(SHADO WTYPE_TEXTURE_MODULATIVE);
mSceneMgr->setShadowTextureSize(512 );
mSceneMgr->setShadowColour(ColourValue(0.6, 0.6, 0.6));
// Setup animation default
Animation::setDefaultInterpolationMode(Animation::IM_LINEAR);
Animation::setDefaultRotationInterpolationMode(Animation::RIM_LINEAR);
// Set ambient light
mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
// The jaiqua sneak animation doesn't loop properly, so lets hack it so it does
// We want to copy the initialkeyframes of all bones, but alter the Spineroot
// to give it an offset of where the animation ends
SkeletonPtr skel = SkeletonManager::getSingleton().load( "jaiqua.skeleton", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Animation* anim = skel->getAnimation("Run");
Animation::NodeTrackIterator trackIter =anim->getNodeTrackIterator();
Entity *ent;
Real rotInc = Math::TWO_PI / (float)NUM_JAIQUAS;
Real rot = 0.0f;
for (int i = 0; i < NUM_JAIQUAS; ++i)
{
Quaternion q;q.FromAngleAxis(Radian(rot), Vector3::UNIT_Y);
mOrientations[i] = q;
mBasePositions[i] = q * Vector3(0,0,-20);
ent = mSceneMgr->createEntity("jaiqua" + StringConverter::toString(i), "jaiqua.mesh");
// Add entity to the scenenode
mSceneNode[i] = mSceneMgr->getRootSceneNode()->createChildSceneNode();
mSceneNode[i]->attachObject(ent);
mSceneNode[i]->rotate(q);
mSceneNode[i]->translate(mBasePositions[i]);
mAnimState[i] = ent->getAnimationState("Walk");
mAnimState[i]->setEnabled(true);
mAnimState[i]->setLoop(false);
// manual loop since translationinvolved
Math::RangeRandom(0.5, 1.5);
rot += rotInc;
}
// Give it a little ambience with lights
Light* l;
l = mSceneMgr->createLight("BlueLight");
l->setType(Light::LT_SPOTLIGHT);
l->setPosition(-200,150,-100);
Vector3 dir(-l->getPosition());
dir.normalise();
l->setDirection(dir);
l->setDiffuseColour(0.5, 0.5, 1.0);
l = mSceneMgr->createLight("GreenLight");
l->setType(Light::LT_SPOTLIGHT);
l->setPosition(0,150,-100);
dir = -l->getPosition();
dir.normalise();
l->setDirection(dir);
l->setDiffuseColour(0.5, 1.0, 0.5);
// Position the camera
mCamera->setPosition(100,20,0);
mCamera->lookAt(0,10,0);
// Report whether hardware skinning is enabled or not
Technique* t = ent->getSubEntity(0)->getMaterial()-> getBestTechnique();
Pass* p = t->getPass(0);
if (p->hasVertexProgram() && p->getVertexProgram()->isSkeletalAnimationIncluded())
mDebugText = "Hardwareskinning is enabled";
else
mDebugText = "Software skinning is enabled";
Plane plane;
plane.normal = Vector3::UNIT_Y;
plane.d = 100;
MeshManager::getSingleton().createPlane("Myplane", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane, 1500,1500,20,20,true,1,60,60,Vector3::UNIT_Z);
Entity* pPlaneEnt = mSceneMgr->createEntity( "plane", "Myplane" );
pPlaneEnt->setMaterialName("Examples/Rockwall");
pPlaneEnt->setCastShadows(false);
mSceneMgr->getRootSceneNode()->createChildSceneNode(Vector3(0,99,0))->attachObject(pPlaneEnt);
}
// Create new frame listener
void createFrameListener(void)
{
mFrameListener= new SkeletalAnimationFrameListener(mWindo w, mCamera, mDebugText);
mRoot->addFrameListener(mFrameListene r);
}
};
代码实现了一个能持绕圈续走动的小人,蒙皮骨骼算法是有 ogre 引擎实现的, 人体模型是由 3类数据来描述的,蒙皮(代 表肌肉),骨骼,皮肤。在运动的时候,骨骼是不会发生变形,只是做刚体运动,相对 于相连关节的旋转运动,确定了骨骼位置 后,所有的骨骼之间都有依赖关系,根据这 些约束条件,是能够找到从一个动作到另一 个动作的中间骨骼位置变化,这样关键帧需 求大大下降,进一步,相应覆盖在它们上面 的肌肉,皮肤就可以得出了,当然需要细心 的进行一些调整,关节上的网格会发生挤压 或拉伸。事实上得到一个好的人物模型并不 容易,一般的做法是选用先进的输入设备或 者专业的美工通过商业 3d 绘图软件获得, 之后转换为程序能够读取的数据文件。骨骼 动画引擎只是简单的把这些数据读入,根据 程序附加的一些与触发条件,时间相关的控 制指令就可以移动了。