colladaファイルからanimationを動かす方法
colladaファイルの特徴まとめ
- 行列はrow majorで格納されている => VulkanやOpenGLで用いる場合にはtransposeなどを行い、column majorへの変換が必要
- (実装方法によるが)
> INV_BIND_MATRIXは使わなくてもよい
collada ファイルのanimation
プログラムを実装してみた後の、個人的な理解をまとめる。
間違いや改善点などがあれば、適宜修正していきたい。
まず、言葉の定義として以下を用いる。
同じ意味でも別の用語が使われる場合が多々あるので、ここでは以下の用語で統一する。
https://community.khronos.org/t/need-help-with-skeletal-animation/68668/3
の、偉大な先人の記述を参考にさせていただいている。
OBJECT SPACE(BIND POSE) : poseのこと。最終的な描画に用いられる座標
BONE SPACE(LOCAL SPACE や JOINT SPACEとも呼ばれる) : 各jointのoriginalの位置を指す座標
JOINT MATRIX : 各jointについて定義されている、BONE SPACE -> OBJECT SPACEに変換する行列
ANIMATION MATRIX : 各jointについて定義されている、BONE SPACE -> OBJECT SPACEにanimationの位置に変換する行列
まず、library_geometriesを読み込んだだけのmeshは、OBJECT SPACEにある。
初期状態のmeshのJOINT MATRIXは colladaファイルにおいてはlibrary_visual_scene > node > matrix に格納されている。①
最終的なanimation poseはOBJECT SPACEにあり、その変換に必要なANIMATION MATRIXはcolladaファイルにおいては
ANIMATION MATRIXは BONE SPACE -> OBJECT MATRIXに変換する行列なので、まとめると
OBJECT SPACE | -> | BONE SPACE | -> | OBJECT SPACE |
mesh 情報 | ①の逆行列 | original bone | animation行列 | animation pose |
という経路での変換が必要。
なのでまずはmesh情報をBONE SPACEに戻す必要があるので①の行列の逆行列を使ってBONE SPACEに変換していく。
OBJECT SPACE -> BONE SPACE
ここで考慮が必要なのは、jointは階層構造になっており、行列は親の影響を受ける。
以下はサンプルとして、CPUでポジションを計算するプログラムで実装する場合には、再帰関数で実装すると都合がよさそう。
void objectToBone(Joint joint, glm::mat4 parentJointTransform){ glm::mat4 jointTransform = parentJointTransform * joint.jointMatrix; glm::mat4 invJointTransform = glm::inverse(jointTransform); for(auto p : joint.jointPositions){ bonePosition = invJointTransform * p; } for(auto &child : joint.children){ objectToBone(child, jointTransform); } }
といったイメージとなる。
ここで伝えたいことは、
BONE SPACE -> JOINT SPACE
先の関数を修正する。
void objectToBone(Joint joint, glm::mat4 parentJointTransform, glm::mat4 parentAnimationTransform){ parentJointTransform = parentJointTransform * joint.jointMatrix; glm::mat4 invJointTransform = glm::inverse(parentJointTransform); parentAnimationTransform = parentAnimationTransform * joint.animationMatrix; glm::mat4 finalTransform = parentAnimationTransform * invJointTransform; for(auto p : joint.jointPositions){ bonePosition = invJointTransform * p; newPosition += p.weight * finalTransform * bonePosition; } for(auto &child : joint.children){ objectToBone(child, jointTransform); } }
ここで伝えたいことは
- finalTransformの順番(Vulkan, OpenGLの場合)
- ここでは詳しく書いてはいないが、各点には影響を受けるjointの重さがあるので、それを加味してnewPositionの計算を行っている
ここでは座標変換を見るためにCPUで計算する関数を簡単に書いているが、実際にはshaderを用いて計算すると思うので、同様に展開していただけばと思う。
この変換を行えば、オブジェクトはanimationで定義されている位置に変換される。