的笔记

随时随地编辑

Ogre骨骼动画分析

http://3dlearn.googlecode.com/files/ogre skeleton animation.pdf

欢迎指出文中错误

1  前言

骨骼蒙皮动画分两步骤进行:根据时间插值更新骨骼、然后根据骨骼更新每骨骼上的顶点。为了好玩,暂且这样看:在每一个时间点,对每一个骨骼,我们创建一个骨骼魔法,并将骨骼魔法施放到每一个骨头上;有个这些骨骼然后我们开始蒙皮,我们找出每一寸皮肤(一个顶点),并从骨堆里找出这块皮需要依附的骨头,当然骨头的数量都是有限的,一般就十几或翻倍的数量级,所以骨头还是比价好找的。我们将皮贴到骨头上,贴完所有的皮,我们就得到了一个骨头人鸟。看起来很形象:
 

下页示意图少儿不宜.

 

 

 

 

2  类图

 <只能看pdf>

 

3  逻辑切片

不解释.

 

渲染

Root::renderOneFrame()

->Root::_updateAllRenderTargets

->RenderSystem::_updateAllRenderTargets()

->RenderWindow::update()

->D3D9RenderWindow::update(bool swap)

->RenderTarget::update()

->Viewport::update()

->Camera::_renderScene()

->SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)

 

4  更新骨骼

只考虑线性插值更新骨骼的情况.

4.1  创建一个骨骼魔法

创建一根骨头需要的魔法。言归正传,其实就是创建一个TransformKeyFrame对象,看做一个全变换,一个变换只能应用到一个骨骼上。当前动作有24根骨头,在每帧里,你需要对着24根骨头施加24次骨骼魔法,如果美术认为男人应该是23根骨头少画了一根,可以不用纠结,知道这不是bug就行。

一个骨骼文件看起来像这样:

 

 

左边定义的是骨头,右边定义的是动作。恩,这里只有18根骨头,可以认为这个不是人类骨骼数据。在程序实现上,事实上考虑的术语叫joint,看起来像只是一个质点,可以这样理解,一个joint是一个空间射线,它表示了一个空间变化,也即一次旋转缩放平移。当然,它是一个矩阵,可以分解成一个平移和一个四元素变换。这时候似乎没有骨骼的长度,可以认为这个joint表示的只是骨头的关节处,骨头的长度隐藏在2个关节之间了。

右边描述的是动作,一个动作是所有的joint在时间轴上的一个个切片组成的。恩,为了便于组织数据,ogrejoint分类关键时间。其实也可以用关键时间来分类joint。恩,这其实也是一个很好的优化方式,如果关键帧分类下省略了joint,就表示这个joint不需要变换,其对应的顶点都不需要进行重新蒙皮计算了。例如一个人在挥手,假设全身只有手在挥舞,当然这动作应看起来像个僵尸。按ogre现在的实现,这个wave下的所有24(为了男女平等考虑男人和女人都是24根骨头)joint都必须有关键时间,就算关键时间少几处,也会将所有的joint进行插值。这个时候避开某个joint被蒙皮,只有在这个动作下删掉某个joint了。这彻头彻尾就是个机器人鸟。如果用改进的分类方式,在某个关键字里,可以省略一些joint,这样一个人边挥手边轻边摆头还是可以实现的。

       创建一个骨头魔法分两步,第一部是取到当前时间点在关键帧中的插值系数,第二部是根据这个插值系数对这个骨头进行插值。

t=(i-k1)/(k2-k1)

 

 

可以看到移动和缩放非常好理解,都是进行的一次线性插值。只有旋转使用了四元素的归一化线性插值。两个旋转的插值似乎也只能用四元素插值,矩阵插值听说有这样那样的问题。这个插值有误差,并且不是恒速插值。

 

核心算法也是基本的线性插值公式,灰常神奇

q1+(q2-q1)*k

 

 

4.2  更新动画时间

AnalyzeAnimation.exe!AnalyzeAnimation::frameRenderingQueued

OgreMain_d.dll!Ogre::Root::_fireFrameRenderingQueued

OgreMain_d.dll!Ogre::Root::_fireFrameRenderingQueued

OgreMain_d.dll!Ogre::Root::_updateAllRenderTargets

OgreMain_d.dll!Ogre::Root::renderOneFrame

OgreMain_d.dll!Ogre::Root::startRendering

AnalyzeAnimation.exe!BaseApplication::go

AnalyzeAnimation.exe!WinMain

 

4.3  骨骼动画的核心玩法(更新骨骼)

OgreMain_d.dll!Ogre::NodeAnimationTrack::applyToNode

OgreMain_d.dll!Ogre::Animation::apply

OgreMain_d.dll!Ogre::Skeleton::setAnimationState

OgreMain_d.dll!Ogre::Entity::cacheBoneMatrices

OgreMain_d.dll!Ogre::Entity::updateAnimation

OgreMain_d.dll!Ogre::Entity::_updateRenderQueue

OgreMain_d.dll!Ogre::RenderQueue::processVisibleObject

OgreMain_d.dll!Ogre::SceneNode::_findVisibleObjects

OgreMain_d.dll!Ogre::SceneNode::_findVisibleObjects

OgreMain_d.dll!Ogre::SceneManager::_findVisibleObjects

OgreMain_d.dll!Ogre::SceneManager::_renderScene

OgreMain_d.dll!Ogre::Camera::_renderScene

OgreMain_d.dll!Ogre::Viewport::update

OgreMain_d.dll!Ogre::RenderTarget::_updateViewport

RenderSystem_Direct3D9_d.dll

OgreMain_d.dll!Ogre::RenderTarget::_updateAutoUpdatedViewports

OgreMain_d.dll!Ogre::RenderTarget::updateImpl

OgreMain_d.dll!Ogre::RenderTarget::update

OgreMain_d.dll!Ogre::RenderSystem::_updateAllRenderTargets

OgreMain_d.dll!Ogre::Root::_updateAllRenderTargets              --->先更新帧监听,再更新实体

OgreMain_d.dll!Ogre::Root::renderOneFrame

OgreMain_d.dll!Ogre::Root::startRendering

AnalyzeAnimation.exe!BaseApplication::go

AnalyzeAnimation.exe!WinMain

 

TransformKeyFrame 看做一个全变换.

 

对骨骼(bone/node)进行变换的流程

输入:节点、时间(省略权值和缩放)

输出:节点的全变换

 

构造出插值关键帧全变换buffer(TransformKeyFrame kf)

从关键帧buffer释放一个平移buffer

对节点施加平移buffer (省略权值与缩放)

从关键帧buffer释放一个旋转buffer

对节点施加旋转buffer

从关键帧buffer释放一个缩放buffer

对节点施加一个缩放buffer

 

 

每帧对每一个骨骼(这里蜕化成node)4个关键帧buffer,正是骨骼动画的核心玩法。

 

4.3.1  释放一个关键帧魔法

关键帧魔法需要创建一个特殊的buffer,即关键帧全变换buffer(TransformKeyFrame).

 

 

 

5  蒙皮

Ogre蒙皮算法的核心是对每顶点进行对应骨骼的全变换。

 

V=M4*V

分两步进行,第一步在Mesh::softwareVertexBlend中准备好计算数据结构的上下文,第二步在softwareVertexSkinning中进行每顶点的蒙皮计算。

 

 

处理软件索引顶点混合,本意是用于骨骼动画,但是也可用于其他用途.

 

 

 

const VertexData*

sourceVertexData

 

const VertexData*

targetVertexData

 

const Matrix4* const*

blendMatrices

 

size_t

numMatrices,

 

bool

blendNormals

 

 

sourceVertexData

顶点,法线,混合索引,混合权重

 

targetVertexData

目标的顶点,混合版本的法线缓存.需要注意向量的归一化.

 

blendMatrices

指向一个用于混合的矩阵数组,sourceVertexData的混合指数索引.

 

numMatrices

blendMatrices中矩阵数组的数量

 

blendNormals

true表示法线也同顶点一起混合.

 

 

 

 

srcElemPos

源顶点

 

srcElemNorm

源法线

 

srcElemBlendIndices

源混合索引

 

srcElemBlendWeights

源混合权重

 

 

 

 

 

 

 

srcPosBuf      

源顶点缓存

 

srcIdxBuf      

源索引缓存

 

srcWeightBuf 

源权重缓存

 

srcNormBuf

源法线缓存

 

destElemPos

目顶点

 

destElemNorm

目法线

 

      

 

 

destPosStride

目法线跨步

 

 

 

 

 

 

 

5.2  蒙皮核心算法

核心算法如下

 

 

首先对顶点进行计算

ü 找到当前的混合索引值

ü 用这个值索引出混合矩阵M4

ü M4左乘以顶点V1(*)得到V2

ü V2进行加权计算得到V3(=V2*weight)

ü V3归一处理得到V4(=V3.normalized)

 

然后对法线进行同样过程的计算,只是上面流程中的(*) 处的V1换成法线.如果一个顶点存在多个权重值,需要对每一个权值重复上面的14步骤进行累积计算到V3.一次顶点计算完成,即对下一个顶点进行同样的计算过程.所有顶点计算完成,即完成了骨骼蒙皮.

posted on 2012-07-25 23:42 的笔记 阅读(4741) 评论(3)  编辑 收藏 引用

评论

# re: Ogre骨骼动画分析 2013-05-09 22:55 eryar

Good!
Mark...  回复  更多评论   

# re: Ogre骨骼动画分析 2013-10-28 11:08 渣浆泵

扔下太久了,看着好累  回复  更多评论   

# re: Ogre骨骼动画分析[未登录] 2014-09-02 16:58 albert

内容看的有些懂了,不过作者很有意思  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理