http://3dlearn.googlecode.com/files/ogre skeleton animation.pdf
歡迎指出文中錯(cuò)誤
1 前言
骨骼蒙皮動(dòng)畫分兩步驟進(jìn)行:根據(jù)時(shí)間插值更新骨骼、然后根據(jù)骨骼更新每骨骼上的頂點(diǎn)。為了好玩,暫且這樣看:在每一個(gè)時(shí)間點(diǎn),對(duì)每一個(gè)骨骼,我們創(chuàng)建一個(gè)骨骼魔法,并將骨骼魔法施放到每一個(gè)骨頭上;有個(gè)這些骨骼然后我們開(kāi)始蒙皮,我們找出每一寸皮膚(一個(gè)頂點(diǎn)),并從骨堆里找出這塊皮需要依附的骨頭,當(dāng)然骨頭的數(shù)量都是有限的,一般就十幾或翻倍的數(shù)量級(jí),所以骨頭還是比價(jià)好找的。我們將皮貼到骨頭上,貼完所有的皮,我們就得到了一個(gè)骨頭人鳥。看起來(lái)很形象:
下頁(yè)示意圖少兒不宜.
芬奇骨骼2.jpg)
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 創(chuàng)建一個(gè)骨骼魔法
創(chuàng)建一根骨頭需要的魔法。言歸正傳,其實(shí)就是創(chuàng)建一個(gè)TransformKeyFrame對(duì)象,看做一個(gè)全變換,一個(gè)變換只能應(yīng)用到一個(gè)骨骼上。當(dāng)前動(dòng)作有24根骨頭,在每幀里,你需要對(duì)著24根骨頭施加24次骨骼魔法,如果美術(shù)認(rèn)為男人應(yīng)該是23根骨頭少畫了一根,可以不用糾結(jié),知道這不是bug就行。
一個(gè)骨骼文件看起來(lái)像這樣:

左邊定義的是骨頭,右邊定義的是動(dòng)作。恩,這里只有18根骨頭,可以認(rèn)為這個(gè)不是人類骨骼數(shù)據(jù)。在程序?qū)崿F(xiàn)上,事實(shí)上考慮的術(shù)語(yǔ)叫joint,看起來(lái)像只是一個(gè)質(zhì)點(diǎn),可以這樣理解,一個(gè)joint是一個(gè)空間射線,它表示了一個(gè)空間變化,也即一次旋轉(zhuǎn)縮放平移。當(dāng)然,它是一個(gè)矩陣,可以分解成一個(gè)平移和一個(gè)四元素變換。這時(shí)候似乎沒(méi)有骨骼的長(zhǎng)度,可以認(rèn)為這個(gè)joint表示的只是骨頭的關(guān)節(jié)處,骨頭的長(zhǎng)度隱藏在2個(gè)關(guān)節(jié)之間了。
右邊描述的是動(dòng)作,一個(gè)動(dòng)作是所有的joint在時(shí)間軸上的一個(gè)個(gè)切片組成的。恩,為了便于組織數(shù)據(jù),ogre用joint分類關(guān)鍵時(shí)間。其實(shí)也可以用關(guān)鍵時(shí)間來(lái)分類joint。恩,這其實(shí)也是一個(gè)很好的優(yōu)化方式,如果關(guān)鍵幀分類下省略了joint,就表示這個(gè)joint不需要變換,其對(duì)應(yīng)的頂點(diǎn)都不需要進(jìn)行重新蒙皮計(jì)算了。例如一個(gè)人在揮手,假設(shè)全身只有手在揮舞,當(dāng)然這動(dòng)作應(yīng)看起來(lái)像個(gè)僵尸。按ogre現(xiàn)在的實(shí)現(xiàn),這個(gè)wave下的所有24(為了男女平等考慮男人和女人都是24根骨頭)個(gè)joint都必須有關(guān)鍵時(shí)間,就算關(guān)鍵時(shí)間少幾處,也會(huì)將所有的joint進(jìn)行插值。這個(gè)時(shí)候避開(kāi)某個(gè)joint被蒙皮,只有在這個(gè)動(dòng)作下刪掉某個(gè)joint了。這徹頭徹尾就是個(gè)機(jī)器人鳥。如果用改進(jìn)的分類方式,在某個(gè)關(guān)鍵字里,可以省略一些joint,這樣一個(gè)人邊揮手邊輕邊擺頭還是可以實(shí)現(xiàn)的。
創(chuàng)建一個(gè)骨頭魔法分兩步,第一部是取到當(dāng)前時(shí)間點(diǎn)在關(guān)鍵幀中的插值系數(shù),第二部是根據(jù)這個(gè)插值系數(shù)對(duì)這個(gè)骨頭進(jìn)行插值。
t=(i-k1)/(k2-k1)


可以看到移動(dòng)和縮放非常好理解,都是進(jìn)行的一次線性插值。只有旋轉(zhuǎn)使用了四元素的歸一化線性插值。兩個(gè)旋轉(zhuǎn)的插值似乎也只能用四元素插值,矩陣插值聽(tīng)說(shuō)有這樣那樣的問(wèn)題。這個(gè)插值有誤差,并且不是恒速插值。

核心算法也是基本的線性插值公式,灰常神奇
q1+(q2-q1)*k
4.2 更新動(dòng)畫時(shí)間
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 骨骼動(dòng)畫的核心玩法(更新骨骼)
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 --->先更新幀監(jiān)聽(tīng),再更新實(shí)體
OgreMain_d.dll!Ogre::Root::renderOneFrame
OgreMain_d.dll!Ogre::Root::startRendering
AnalyzeAnimation.exe!BaseApplication::go
AnalyzeAnimation.exe!WinMain
TransformKeyFrame 看做一個(gè)全變換.
對(duì)骨骼(bone/node)進(jìn)行變換的流程
輸入:節(jié)點(diǎn)、時(shí)間(省略權(quán)值和縮放)
輸出:節(jié)點(diǎn)的全變換
u 構(gòu)造出插值關(guān)鍵幀全變換buffer(TransformKeyFrame kf)
u 從關(guān)鍵幀buffer釋放一個(gè)平移buffer
u 對(duì)節(jié)點(diǎn)施加平移buffer (省略權(quán)值與縮放)
u 從關(guān)鍵幀buffer釋放一個(gè)旋轉(zhuǎn)buffer
u 對(duì)節(jié)點(diǎn)施加旋轉(zhuǎn)buffer
u 從關(guān)鍵幀buffer釋放一個(gè)縮放buffer
u 對(duì)節(jié)點(diǎn)施加一個(gè)縮放buffer

每幀對(duì)每一個(gè)骨骼(這里蛻化成node)掛4個(gè)關(guān)鍵幀buffer,正是骨骼動(dòng)畫的核心玩法。
4.3.1 釋放一個(gè)關(guān)鍵幀魔法
關(guān)鍵幀魔法需要?jiǎng)?chuàng)建一個(gè)特殊的buffer,即關(guān)鍵幀全變換buffer(TransformKeyFrame).

5 蒙皮
Ogre蒙皮算法的核心是對(duì)每頂點(diǎn)進(jìn)行對(duì)應(yīng)骨骼的全變換。
V=M4*V
分兩步進(jìn)行,第一步在Mesh::softwareVertexBlend中準(zhǔn)備好計(jì)算數(shù)據(jù)結(jié)構(gòu)的上下文,第二步在softwareVertexSkinning中進(jìn)行每頂點(diǎn)的蒙皮計(jì)算。

處理軟件索引頂點(diǎn)混合,本意是用于骨骼動(dòng)畫,但是也可用于其他用途.
|
|
|
const VertexData* |
sourceVertexData |
|
const VertexData* |
targetVertexData |
|
const Matrix4* const* |
blendMatrices |
|
size_t |
numMatrices, |
|
bool |
blendNormals |
|
sourceVertexData
頂點(diǎn),法線,混合索引,混合權(quán)重
targetVertexData
目標(biāo)的頂點(diǎn),混合版本的法線緩存.需要注意向量的歸一化.
blendMatrices
指向一個(gè)用于混合的矩陣數(shù)組,被sourceVertexData的混合指數(shù)索引.
numMatrices
blendMatrices中矩陣數(shù)組的數(shù)量
blendNormals
true表示法線也同頂點(diǎn)一起混合.
|
|
|
srcElemPos |
源頂點(diǎn) |
|
srcElemNorm |
源法線 |
|
srcElemBlendIndices |
源混合索引 |
|
srcElemBlendWeights |
源混合權(quán)重 |
|
|
|
|
|
|
|
srcPosBuf |
源頂點(diǎn)緩存 |
|
srcIdxBuf |
源索引緩存 |
|
srcWeightBuf |
源權(quán)重緩存 |
|
srcNormBuf |
源法線緩存 |
|
destElemPos |
目頂點(diǎn) |
|
destElemNorm |
目法線 |
|
|
|
|
destPosStride |
目法線跨步 |
|
|
|
|
|
|
|
5.2 蒙皮核心算法
核心算法如下


首先對(duì)頂點(diǎn)進(jìn)行計(jì)算
ü 找到當(dāng)前的混合索引值
ü 用這個(gè)值索引出混合矩陣M4
ü M4左乘以頂點(diǎn)V1(*)得到V2
ü V2進(jìn)行加權(quán)計(jì)算得到V3(=V2*weight)
ü V3歸一處理得到V4(=V3.normalized)
然后對(duì)法線進(jìn)行同樣過(guò)程的計(jì)算,只是上面流程中的(*) 處的V1換成法線.如果一個(gè)頂點(diǎn)存在多個(gè)權(quán)重值,需要對(duì)每一個(gè)權(quán)值重復(fù)上面的1到4步驟進(jìn)行累積計(jì)算到V3.一次頂點(diǎn)計(jì)算完成,即對(duì)下一個(gè)頂點(diǎn)進(jìn)行同樣的計(jì)算過(guò)程.所有頂點(diǎn)計(jì)算完成,即完成了骨骼蒙皮.