vertex skinning の覚え書き

はじめに

3D モデルを読み込んで OpenGL で表示させようとしていてこのあたりを調べていたのでここに書いておきます。

glTF を主に調べていましたが、他のフォーマットなどでも同じ仕組みが使われてるようです。

glTF のドキュメントには vertex skinning という名前が出てましたが色々呼び方があって紛らわしいです。

ja.wikipedia.org

スケルタルアニメーションとかスキンメッシュアニメーション、ボーンアニメーションとも呼ばれるようです。

参考にしたもの

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 プラグインがあって、これでファイル内容を見つつプレビューしたりできて便利でした。

marketplace.visualstudio.com

定義へのジャンプができるため他の要素を ID で参照している部分などを追いかけるのがやりやすいと思います。

それからこちらは DirectX のフォーマットに関する記事ですが日本語で詳しいところまで解説されていて理解しやすかったです。

marupeke296.com

marupeke296.com

仕組みなど

vertex skinning は 3D モデルに色々なポーズを取らせたり、アニメーションさせて歩かせたり踊らせたりといったことに使えます。

やったことないですが Blender でモデルを作ってボーンをいれて動かすような場合も同じ仕組みが使われてると思います。

簡単に仕組みを説明してみます。

3D モデルはたくさんの三角形から成っていて、この三角形の頂点と紐付く情報を色々持っています。

頂点と紐付く情報は頂点の位置や色、テクスチャ座標などです。

vertex skinning ではモデルにボーンを埋め込んでボーンの動きに追随して各頂点が動くようになっています。

ボーンの数は三角形頂点の数に比べて少なく、三角形頂点の情報を更新することなくボーンの変形だけでモデルを動かすことができるので扱いやすくなっています。

ボーンというと棒が繋がったようなのを想像しますが実際は階層的な座標系の構造を表すようなものだと思います。

肩の座標系の下に肘の座標系があってさらにその下に手首、その下に指という感じです。

頂点の情報にはボーンのインデックスとウェイト (影響度) が追加されます。

シェーダーには頂点の情報のほかに各ボーンの変形を表す行列が渡されてボーンによる変形を反映した座標が計算されて描画されます。

glTF

glTF の構造のうち vertex skinning と関係したところを書いておきます。

ノード

ノードは階層的な構造を表すのに使われます。ボーンの構造もノードで表現されます。

仕様だとこのあたりです。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#nodes-and-hierarchy

JSONnodes の型は配列でインデックスが各ノードの識別子として使われます。 仕様のバージョン1.0では nodes にオブジェクトを使ってキーを識別子として使っている例もあるんですがバージョン2.0では配列になっているようです。

ノードのプロパティは name, children, rotation, scale, translation, matrix, mesh, skin などがあります。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#node

children に子ノードの識別子を指定して階層的な構造を表せます。

rotation, scale, translation, matrix はノードの変形です。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#transformations

mesh はノードと紐付くメッシュの識別子です。skin はスキンの識別子です。スキンについては後で説明します。

vertex skinning の場合は一番上の階層のノードに meshskin が指定されるようです。

仕様が1.0から2.0に変わったときになくなっているプロパティがいくつかあるようです。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/1.0/README.md#node

スキン

スキンもボーンの情報を持っています。

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#skins

https://github.com/KhronosGroup/glTF/blob/1f78518ec26e805cd23580022ca49f4349b0fd65/specification/2.0/README.md#skin

joints はボーンを構成するノードの識別子の配列です。ボーンのルートのノードは相対的に動かないので joints には入ってません。

inverseBindMatricesaccessor の識別子で joints の数と同じ要素数の 4x4 行列の配列を参照します。 inverseBindMatrices はボーンの変形を計算するときに必要になります。

その27 アニメーションの根っこ:スキンメッシュアニメーション(ボーン操作)

インバースバインドマトリックスは恐らくこちらの記事で書かれていたボーンオフセット行列と同じものだと思います。

例えば肩を回転させて二の腕を動かすときに、肩の座標を原点に一致させないとおかしな変形になってしまうためこの行列が必要になるのだと思います。

ボーンの変形行列の計算方法はチュートリアルに書かれていました。

https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md#the-joint-matrices

jointMatrix(j) =
  globalTransformOfNodeThatTheMeshIsAttachedTo^-1 *
  globalTransformOfJointNode(j) *
  inverseBindMatrixForJoint(j);

まずインバースバインドマトリックス inverseBindMatrixForJoint(j) を掛けて頂点をボーンの座標系に移動させます。

それからボーンをグローバル座標系で変形させる行列を掛けます。

最後に globalTransformOfNodeThatTheMeshIsAttachedTo^-1 を掛けているのは後で使う modelViewMatrix にメッシュがアタッチされたノードの変形が含まれているからだと思います。

skeleton というプロパティが指定される場合があってこれはボーンのルートノードの指定に使われるようなんですが、ノードのプロパティで skin が指定される場合との違いがよくわかりません。

ライブラリによってはこの skeleton プロパティは無視されているようで、three.js の GLTFLoader では使ってないように見えます。