Ogre骨骼动画

一、基本框架

先看一下OGRE动画的基本框架:

http://blog.csdn.net/leonwei/article/details/5819248

二、动画控制

OGRE的基本动画控制是很简单的,设置一个动画的操作是这样:

// Set idle animation

mAnimationState = ent->getAnimationState( "Idle" );

mAnimationState->setLoop( true );

mAnimationState->setEnabled( true );

(上面这段代码来自Intermediate Tutorial1 – Ogre Wiki)从一个Entity对象中得到AnimationState指针,然后设置一些属性,在每帧需要调用(FrameListener中):

mAnimationState->addTime( evt.timeSinceLastFrame );

传入每帧的时间,这样动画就开始动起来了。

三、动画资源加载

1.Skeleton加载

可以通过SkeletonManager来加载骨骼文件。

SkeletonPtr skel = SkeletonManager::getSingleton().load("jaiqua.skeleton", 

ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

这个load是SkeletonManager所继承的ResourceManager的虚函数load。

 ResourcePtr ResourceManager::load(const String& name, 
const String& group, bool isManual, ManualResourceLoader* loader,
const NameValuePairList* loadParams)
{
ResourcePtr r = createOrRetrieve(name,group,isManual,loader,loadParams).first;
// ensure loaded
r->load();
return r;
}

里边创建一个Resource,这边就是Skeleton了,并调用他的load函数。

这个load函数也是继承来的,它是继承于Resource类的,Ogre里的资源都是继承这个类的,如Material,Mesh等。

 void Resource::load(bool background)
{
...
    if (old==LOADSTATE_UNLOADED)
prepareImpl();

preLoadImpl();

old = LOADSTATE_PREPARED;

if (mGroup == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME)
{
// Derive resource group
changeGroupOwnership(
ResourceGroupManager::getSingleton()
.findGroupContainingResource(mName));
}

loadImpl();

postLoadImpl();
...
}

这里边的主要加载资源逻辑就是在loadImpl(),这是个纯虚函数,具体由子类来实现,我们现在读取的是skeleton,因此就是使用skeleton类中的loadImpl().

void Skeleton::loadImpl(void)
{
SkeletonSerializer serializer;
LogManager::getSingleton().stream()
<< "Skeleton: Loading " << mName;

DataStreamPtr stream =
ResourceGroupManager::getSingleton().openResource(
mName, mGroup, true, this);

serializer.importSkeleton(stream, this);

// Load any linked skeletons
LinkedSkeletonAnimSourceList::iterator i;
for (i = mLinkedSkeletonAnimSourceList.begin();
i != mLinkedSkeletonAnimSourceList.end(); ++i)
{
i->pSkeleton = SkeletonManager::getSingleton().load(
i->skeletonName, mGroup);
}


}

此函数主要进行了两件事:1、用SkeletonSerializer来解析文件数据流,保存到当前的skeleton实例中;2、载入所有相关连的skeleton(在.skeleton文件的<animationlinks>标签中定义)。

动画信息存储在Skeleton的“AnimationList mAnimationsList”成员变量中,可以根据动画的名字来获取相应的动画信息

SkeletonPtr skel = SkeletonManager::getSingleton().load("Jaiqua.skeleton",
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

Animation* anim = skel->getAnimation("Sneak");


这边说一下Skeleton和SkeletonInstance的区别,SkeletonInstance继承了Skeleton,内部保存了一个指向Skeleton的指针。可以打开SkeletonInstance的头文件,可以看到一段简短的英文描述。

/** A SkeletonInstance is a single instance of a Skeleton used by a world object.
@remarks
The difference between a Skeleton and a SkeletonInstance is that the
Skeleton is the 'master' version much like Mesh is a 'master' version of
Entity. Many SkeletonInstance objects can be based on a single Skeleton,
and are copies of it when created. Any changes made to this are not
reflected in the master copy. The exception is animations; these are
shared on the Skeleton itself and may not be modified here.
*/

简单翻译如下:

一个SkeletonInstance类是一个单一的Skeleton的一个实例,Skeleton是一个宿主,就像一个Mesh可以有多个Entity一样。一个Skeleton可以有许多个SkeletonInstance,每个SkeletonInstance拷贝了一份Skeleton信息,对SkeletonInstance里的一些信息的更改不会影响到Skeleton里的信息,但是对动画的修改就会影响到Skeleton里的动画,因为动画信息是共享的。

这边最典型的好处就是可以动态的为骨骼添加一些东西,如武器,装备等。

2.Mesh加载
skeleton单独加载进来了,那Mesh怎么知道哪个skeleton是他的呢?如果用XML转换器将.mesh文件转为XML,打开可以看到在文件末尾有 <skeletonlink name="jaiqua.skeleton" />

所以他会在加载Mesh的时候加载进来。

现在来看Mesh的加载步骤。。这个加载真是能把人绕晕掉。。

先从调用的地方开始看:

Entity* ent = mSceneMgr->createEntity("jaiQua", "jaiqua.mesh");

跳转到:

Entity* SceneManager::createEntity(
const String& entityName,
const String& meshName )
{
// delegate to factory implementation
NameValuePairList params;
params["mesh"] = meshName;
return static_cast<Entity*>(
createMovableObject(entityName, EntityFactory::FACTORY_TYPE_NAME,
&params));

}

代理给工产方法:

MovableObject* SceneManager::createMovableObject(const String& name, 
const String& typeName, const NameValuePairList* params)
{
...
MovableObjectFactory* factory =
Root::getSingleton().getMovableObjectFactory(typeName);

MovableObject* newObj = factory->createInstance(name, this, params);
...
}


调用工厂方法:

MovableObject* MovableObjectFactory::createInstance(
const String& name, SceneManager* manager,
const NameValuePairList* params)
{
MovableObject* m = createInstanceImpl(name, params);
m->_notifyCreator(this);
m->_notifyManager(manager);
return m;
}
createInstanceImpl()是一个纯虚函数,由不同的子类各自实现,这边就是调用Entity的方法了。
MovableObject* EntityFactory::createInstanceImpl( const String& name,
const NameValuePairList* params)
{
// must have mesh parameter
MeshPtr pMesh;
if (params != 0)
{
NameValuePairList::const_iterator ni = params->find("mesh");
if (ni != params->end())
{
// Get mesh (load if required)
pMesh = MeshManager::getSingleton().load(
ni->second,
// autodetect group location
ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME );
}

}
if (pMesh.isNull())
{
OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
"'mesh' parameter required when constructing an Entity.",
"EntityFactory::createInstance");
}

return OGRE_NEW Entity(name, pMesh);

}
这边也开始调用load函数了,这才开始进入mesh资源的加载。
加载的过程跟skeleton类似,因此只挑重点的说一下:
经过若干次跳转后。。调用到MeshSerializerImpl::importMesh
void MeshSerializerImpl::importMesh(DataStreamPtr& stream, Mesh* pMesh, MeshSerializerListener *listener)
{
// Determine endianness (must be the first thing we do!)
determineEndianness(stream);

// Check header
readFileHeader(stream);

unsigned short streamID;
while(!stream->eof())
{
streamID = readChunk(stream);
switch (streamID)
{
case M_MESH:
readMesh(stream, pMesh, listener);
break;
}

}
}
跟踪下去:
MeshSerializerImpl::readMesh()――case M_MESH_SKELETON_LINK:

=》MeshSerializerImpl::readSkeletonLink();
=》Mesh::setSkeletonName();
=》mSkeleton = SkeletonManager::getSingleton().load(skelName, mGroup);――得到骨骼指针

mSkeleton为Mesh里保存的骨骼的指针。
这样就从Entity中可以得到对应的骨骼了。
SkeletonInstance* skelIns = ent->getSkeleton();

3.动画集合

Animation类的对象就是“An animation sequence”,各种类型的动画序列都由这个类来管理。它管理了三种类型的Track list,分别是:NodeAnimationTrack、NumericAnimationTrack、VertexAnimationTrack。

通俗的讲就是一个动画是由它的各个部分的动画轨迹组成的。比如人的动画是由他的头和四肢的轨迹组成的。

AnimationState是一个Animation的状态管理类,可以在里边设置动画的权重,设置动画是否开启,是否循环等状态。

AnimationStateSet就是所有的AnimationState的集合。


在Entity的构造函数中,如果Mesh含有骨骼动画或者顶点动画,则会new一个AnimationStateSet对象,并调用“mesh->_initAnimationState(mAnimationState);”。

void Entity::_initialise(bool forceReinitialise)
{
...
// Is mesh skeletally animated?
if (mMesh->hasSkeleton() && !mMesh->getSkeleton().isNull())
{
mSkeletonInstance = OGRE_NEW SkeletonInstance(mMesh->getSkeleton());
mSkeletonInstance->load();
}

// Initialise the AnimationState, if Mesh has animation
if (hasSkeleton())
{
mFrameBonesLastUpdated = OGRE_NEW_T(unsigned long, MEMCATEGORY_ANIMATION)(std::numeric_limits<unsigned long>::max());
mNumBoneMatrices = mSkeletonInstance->getNumBones();
mBoneMatrices = static_cast<Matrix4*>(OGRE_MALLOC_SIMD(sizeof(Matrix4) * mNumBoneMatrices, MEMCATEGORY_ANIMATION));
}
if (hasSkeleton() || hasVertexAnimation())
{
mAnimationState = OGRE_NEW AnimationStateSet();
mMesh->_initAnimationState(mAnimationState);
prepareTempBlendBuffers();
}
...
}
可以看到Entity中用的是SkeletonInstance,而不是直接用mesh的Skeleton。

4.动画的更新

主要运算就在“void Entity::updateAnimation(void)”函数中。

=》 Entity::cacheBoneMatrices
=》 Skeleton::setAnimationState
此函数先是调用“Skeleton::reset”,然后针对每个enabled animation state,找到其对应的Animation,然后调用Animation::apply()来计算每个Bone的状态。



几个小技巧:

1.复制动画

Animation* copyAnim = skel->createAnimation("OldSneak", anim->getLength());
*copyAnim = *(anim->clone("OldSneak"));
ent->refreshAvailableAnimationState();

记得要refresh才能用,不然找不到。

2.添加动画:

SkeletonInstance* skelIns = ent->getSkeleton();
skelIns->addLinkedSkeletonAnimationSource("XX.skeleton");




原文地址:https://www.cnblogs.com/gameprogram/p/2259127.html