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在時間軸上的一個個切片組成的。恩,為了便于組織數據,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.一次頂點計算完成,即對下一個頂點進行同樣的計算過程.所有頂點計算完成,即完成了骨骼蒙皮.