我要发帖 回复

管理员

735

主题

2万

积分

30

专家分

忠于职守杰出贡献鼓励

兴趣点(最多三项):

建模技术

私信
发表时间 : 2013-11-19 14:44:02 | 浏览 : 1746    评论 : 0

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在时间轴上的一个个切片组成的。恩,为了便于组织数据,ogre用joint分类关键时间。其实也可以用关键时间来分类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)进行变换的流程
输入:节点、时间(省略权值和缩放)
输出:节点的全变换

u 构造出插值关键帧全变换buffer(TransformKeyFrame kf)
u 从关键帧buffer释放一个平移buffer
u 对节点施加平移buffer (省略权值与缩放)
u 从关键帧buffer释放一个旋转buffer
u 对节点施加旋转buffer
u 从关键帧buffer释放一个缩放buffer
u 对节点施加一个缩放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换成法线.如果一个顶点存在多个权重值,需要对每一个权值重复上面的1到4步骤进行累积计算到V3.一次顶点计算完成,即对下一个顶点进行同样的计算过程.所有顶点计算完成,即完成了骨骼蒙皮.

最近VR访客

UE4   |   虚幻引擎   |   Unity VR    |    Hololens

手机版|VR开发网 统计 津ICP备18009691号
网安备12019202000257

GMT+8, 2021-5-17 11:51 PM

返回顶部