Aqoole_Hateenaの技術日記

vulkan+raytraceで色々描いてます

colladaファイルからanimationを動かす方法

colladaファイルの特徴まとめ

  1. 行列はrow majorで格納されている => VulkanやOpenGLで用いる場合にはtransposeなどを行い、column majorへの変換が必要
  2. (実装方法によるが) > 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ファイルにおいては > matrix_output に記述されている。
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);
    }
}

といったイメージとなる。
ここで伝えたいことは、

  1. 親から渡されたjointTransformの逆行列は、一度右から自分のJOINT MATRIXを掛けた後に逆行列変換をするとよい。
  2. jointは階層構造なので、再帰関数にして子に伝えていくのがおそらく一番楽。

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);
    }
}

ここで伝えたいことは

  1. finalTransformの順番(Vulkan, OpenGLの場合)
  2. ここでは詳しく書いてはいないが、各点には影響を受けるjointの重さがあるので、それを加味してnewPositionの計算を行っている

ここでは座標変換を見るためにCPUで計算する関数を簡単に書いているが、実際にはshaderを用いて計算すると思うので、同様に展開していただけばと思う。

この変換を行えば、オブジェクトはanimationで定義されている位置に変換される。