glTF 2.0 skinning データ構造と評価のメモ

## 背景

glTF 2.0 で skinning(キャラクターアニメーションとか)したい.

## 仕様

https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins

skinning = vertex skinning = Linear Blending Skinning(LBS) = Maya でいう skeleton, bone, skinCluster あたりです.
dual quaternion とか, delta mash などの advanced な skinning は含まれていません. 

しかし初見すると glTF の skinning の仕組みはまったくよくわかりませんね :cry: :cry:
私自身 skinning の理解をするにはなかなか時間がかかりました(今でも 25% くらいしか理解していませんが... :cry:).


## (まず注意事項) skinning つき glTF は DCC のフォーマットとして使うのを考えてはいけない

現在の glTF 2.0 の skinning の定義は再生に特化しており, また厳密な skinning 定義もなく, DCC の skinning(ボーン)データ構造とも異なります.

したがって, Skinning を含んだデータ(特にキャラクターデータ)に対して, DCC(blender, maya など)から glTF に export したり, glTF から DCC(blender, maya)に import したりは考えないほうがいいです.
(複雑なキャラクターになるとだいたいうまくいきません)

glTF はデータ交換のエコシステムと謳っていますが, こと **skinning においてはそれは全くの幻想です**.

**現状の glTF 2.0 の一番の問題が skinning 周りの定義であると言えます**
(glTF 全体の方向性とか仕様以外の issue で最も伸びているのが 1665 skinning です)

Stricter Skinning Requirements #1665
https://github.com/KhronosGroup/glTF/issues/1665


一応後述する KHR_skin_strict などで, skinning の仕様を厳密にしたり, 計算方法を詳細に記述するなど提案されていますが, glTF 2.0 の仕様の範囲では運用は難しいと考えられます(glTF 3.0(glTF Next)へ持ち越しになると思われます)

skinning つきデータについては, 大人しく USD(Universal Scene Description)などを使ったほうがよいと思います.
(Blender 2.82 から USD export 対応が始まりました. いずれは USD importer もできるはず?)

## (さらに注意事項) skinning 情報からシーングラフのデータは十分に復元できない.

再生に特化しているため, skinning 情報からシーングラフ構造を十分に復元することはできません

`inverseBindMatrix` は, 実際には任意の行列を定義できるため, かならずしも bind matrix(Maya でいう bind pose matrix でしょうか?)を逆行列にしたものとは限らないからです. bind matrix の情報定義自体は glTF の仕様にありません.

https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md

たとえばこのあたりのチュートリアルでは, `inverseBindMatrix` の逆行列をとっても bind matrix にはなりません.

これは, たとえば自前の描画エンジンとかでちょっと skinning を弄りたいときとかに問題になります.

## skeleton の階層とか

[Clarification] Skeleton root and joints hierarchy #1270
https://github.com/KhronosGroup/glTF/issues/1270

Add implementation notes about which transforms apply to skinned meshes #1195
https://github.com/KhronosGroup/glTF/pull/1195

How to bind bones with vertices in skeleton animation? #1320
https://github.com/KhronosGroup/glTF/issues/1320

## `skeleton` プロパティ

ノード階層(joint 階層)のどこを起点とするかの `skeleton` .
いずれ不要になるかも?

## joint 

joint 自体は node の行列で表現します. joint(node)は階層構造になっているのが要求されます(glTF のデータ的には階層構造になっていなかったり, 循環参照もできてしまったりする. validator でははそのあたり検出してチェックしてくれるはず)

## skinning 行列の計算

https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md

https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md

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

* `globalTransformOfNodeThatTheMeshIsAttachedTo` : `skin` が含まれている(つまり `mesh` も含まれている) node の world 行列. ` globalTransformOfNodeThatTheMeshIsAttachedTo^-1` はその逆行列.
* `globalTransformOfJointNode(j)` :  `joint` の parent node の world 行列 と, `joint` 自身の node の行列をかけたもの. 
* `inverseBindMatrixForJoint` は glTF Buffer に別途含まれている joint ごとの行列(Exporter or glTF 生成側で計算して出力されたもの). `inverseBindMatrices` ではアクセッサのインデックスを指定(基本的には `joint` の個数ぶんの行列データ配列への buffer(view) の番号なので, 行列の値を直接指定しているわけではなく, わかりずらい). 

`inverseBindMatrices` が定義されていない場合は, bind matrix の逆行列は単位行列とする.

この場合, bind matrix 相当の変換がすでに頂点データもしくは joint node の行列に含まれているとする.

コード例としては, Sascha 先生の Vulkan-glTF-PBR を参考ください.

https://github.com/SaschaWillems/Vulkan-glTF-PBR

## 頂点単位の skinning 処理

jointMatrix を animation などで変更があったときに再計算(通常 CPU)したのち, vertex shader で頂点単位で skinning 計算をします.
(頂点数など少なければ CPU で計算するという手も).

計算自体は行列計算するだけなので, 特に複雑なことはありません.

glTF 上では, 4 個づつ joint/weight 情報が頂点アトリビュートとしてアサインされています(JOINTS_0/WEIGHTS_0)
この 4 個のかたまりを set と読んだりするようです(UV set とおなじような感じでしょうか).
4 個以上の場合は, JOINTS_1/WEIGHTS_1, JOINTS_2/WEIGHTS_2, ... として定義します.

ただ, 既存の glTF ビューアなどでは, 多くの場合 4 個まで(JOINTS_0/WEIGHTS_0)の対応になっています.

weight は, 仕様上は非負かつ正規化(全ての weight の和をとると 1.0)されているとしますが, 実際には 1.0 にならなくともエラーにはなりません(glTF viewer などでは warning を出してくれたりはします).

Are skin weights normalized? #1213
https://github.com/KhronosGroup/glTF/issues/1213
 
## joint 行列をダンプする

RenderDoc などのデバッガでシェーダに渡ってくる jointMatrix の値などが確認できて便利です.

Linux or Android 向け Vulkan アプリのデバッガーのメモ
https://qiita.com/syoyo/items/4f90a015eace55280c90


## 提案中の skinning 拡張など

### 厳密な skin 定義をする KHR_skin_strict

提案中です.

https://github.com/KhronosGroup/glTF/pull/1747

### skeleton 定義

skeleton の定義をきちんとしようが提案中です.

Extension Proposal regarding Skeleton Definitions in glTF
https://github.com/KhronosGroup/glTF/pull/1695


## その他

`inverseBindMatrices` がバッファーでバイナリなので, 内容の確認や, ノード階層を変えたときなどの修正がやりずらくぴえん :cry: :cry:

animation 関連の値も同様.

glTF のプロパティのいくつかを ASCII で定義する拡張が求められます.