vertex skinning の覚え書き
はじめに
3D モデルを読み込んで OpenGL で表示させようとしていてこのあたりを調べていたのでここに書いておきます。
glTF を主に調べていましたが、他のフォーマットなどでも同じ仕組みが使われてるようです。
glTF のドキュメントには vertex skinning という名前が出てましたが色々呼び方があって紛らわしいです。
スケルタルアニメーションとかスキンメッシュアニメーション、ボーンアニメーションとも呼ばれるようです。
参考にしたもの
glTF のチュートリアルがわかりやすいと思います。
glTF-Tutorials/gltfTutorial_019_SimpleSkin.md at master · KhronosGroup/glTF-Tutorials · GitHub
glTF-Tutorials/gltfTutorial_020_Skins.md at master · KhronosGroup/glTF-Tutorials · GitHub
それから VSCode の glTF プラグインがあって、これでファイル内容を見つつプレビューしたりできて便利でした。
定義へのジャンプができるため他の要素を ID で参照している部分などを追いかけるのがやりやすいと思います。
それからこちらは DirectX のフォーマットに関する記事ですが日本語で詳しいところまで解説されていて理解しやすかったです。
仕組みなど
vertex skinning は 3D モデルに色々なポーズを取らせたり、アニメーションさせて歩かせたり踊らせたりといったことに使えます。
やったことないですが Blender でモデルを作ってボーンをいれて動かすような場合も同じ仕組みが使われてると思います。
簡単に仕組みを説明してみます。
3D モデルはたくさんの三角形から成っていて、この三角形の頂点と紐付く情報を色々持っています。
頂点と紐付く情報は頂点の位置や色、テクスチャ座標などです。
vertex skinning ではモデルにボーンを埋め込んでボーンの動きに追随して各頂点が動くようになっています。
ボーンの数は三角形頂点の数に比べて少なく、三角形頂点の情報を更新することなくボーンの変形だけでモデルを動かすことができるので扱いやすくなっています。
ボーンというと棒が繋がったようなのを想像しますが実際は階層的な座標系の構造を表すようなものだと思います。
肩の座標系の下に肘の座標系があってさらにその下に手首、その下に指という感じです。
頂点の情報にはボーンのインデックスとウェイト (影響度) が追加されます。
シェーダーには頂点の情報のほかに各ボーンの変形を表す行列が渡されてボーンによる変形を反映した座標が計算されて描画されます。
glTF
glTF の構造のうち vertex skinning と関係したところを書いておきます。
ノード
ノードは階層的な構造を表すのに使われます。ボーンの構造もノードで表現されます。
仕様だとこのあたりです。
JSON の nodes
の型は配列でインデックスが各ノードの識別子として使われます。
仕様のバージョン1.0では nodes
にオブジェクトを使ってキーを識別子として使っている例もあるんですがバージョン2.0では配列になっているようです。
ノードのプロパティは name
, children
, rotation
, scale
, translation
, matrix
, mesh
, skin
などがあります。
children
に子ノードの識別子を指定して階層的な構造を表せます。
rotation
, scale
, translation
, matrix
はノードの変形です。
mesh
はノードと紐付くメッシュの識別子です。skin
はスキンの識別子です。スキンについては後で説明します。
vertex skinning の場合は一番上の階層のノードに mesh
と skin
が指定されるようです。
仕様が1.0から2.0に変わったときになくなっているプロパティがいくつかあるようです。
スキン
スキンもボーンの情報を持っています。
joints
はボーンを構成するノードの識別子の配列です。ボーンのルートのノードは相対的に動かないので joints
には入ってません。
inverseBindMatrices
は accessor
の識別子で joints
の数と同じ要素数の 4x4 行列の配列を参照します。
inverseBindMatrices
はボーンの変形を計算するときに必要になります。
その27 アニメーションの根っこ:スキンメッシュアニメーション(ボーン操作)
インバースバインドマトリックスは恐らくこちらの記事で書かれていたボーンオフセット行列と同じものだと思います。
例えば肩を回転させて二の腕を動かすときに、肩の座標を原点に一致させないとおかしな変形になってしまうためこの行列が必要になるのだと思います。
ボーンの変形行列の計算方法はチュートリアルに書かれていました。
jointMatrix(j) = globalTransformOfNodeThatTheMeshIsAttachedTo^-1 * globalTransformOfJointNode(j) * inverseBindMatrixForJoint(j);
まずインバースバインドマトリックス inverseBindMatrixForJoint(j)
を掛けて頂点をボーンの座標系に移動させます。
それからボーンをグローバル座標系で変形させる行列を掛けます。
最後に globalTransformOfNodeThatTheMeshIsAttachedTo^-1
を掛けているのは後で使う modelViewMatrix
にメッシュがアタッチされたノードの変形が含まれているからだと思います。
skeleton
というプロパティが指定される場合があってこれはボーンのルートノードの指定に使われるようなんですが、ノードのプロパティで skin
が指定される場合との違いがよくわかりません。
ライブラリによってはこの skeleton
プロパティは無視されているようで、three.js の GLTFLoader では使ってないように見えます。