glTF 2.0 のスケルトンの分類

# glTF 2.0 でのスケルトンて？

glTF に限らず、**ヒューマノイド（人間）** 型の 3D モデルは、三角形から成る目に見える **メッシュ** の他に、モデルの **骨格** を表す **スケルトン** も持っている。

スケルトンがあれば、モデルは「動く」ことができる。
モデルが動くとき、スケルトンの **骨（ボーン）** または **関節（ジョイント）** の動きに応じて **頂点スキニング** を行うことで、曲がった関節のメッシュも破綻することなく滑らかに繋げて描画することができる。

ここで、個人的に混乱したこと。

- glTF でスケルトンを定義するのはメッシュノードのツリー？ それともスキンが持つジョイントノードのツリー？
- スキンが `skeleton` プロパティを持ってるみたいだけどこれは何？ ……え？ 無視すべきなの？

# 考察

先に、文章中で使用する glTF 用語（？）の意識合わせを簡単に。

- 「メッシュノード」…… `mesh` プロパティを持つ `node` 。
- 「スキン」……`node` の `skin` プロパティ。`skin` プロパティを持つ `node` は、必ず `mesh` プロパティも持っていなければならない。
- 「ノード変換」……`node` の TRS プロパティ（`translation`, `rotation`, `scale`）または `matrix` プロパティで定義される「変換（transform）」。
- 「ジョイントノード」…… `skin.joints` が参照する `node` 。「スケルトンノード」とも言う。

## 二種類のスケルトンがある

glTF 2.0 でモデルのスケルトンを定義するものが何であるかは、メッシュノードが**スキンを持っているか持っていないかで異なる。** これはなかなか気付かなかった。

1. スキンがない場合：メッシュには、**そのメッシュノード**のノード変換が適用される。
2. スキンがある場合：メッシュには、**スキンのジョイントノード** のノード変換が適用され、メッシュノードのノード変換は**無視**される。

これは、同じ glTF モデルであっても、**メッシュノードにスキンがあるかないか次第で、スケルトンの作り方がまったく違う** ということを意味している。

ここでは仮に、1. を「**メッシュスケルトン**」、2. を「**スキンスケルトン**」とでも称して分類することにする。

:::note warn
glTF 2.0 の仕様だけだとこの 2 パターンのスケルトンを混在させることもできてしまうが、ここでは言及しないでおく。
:::

なお、この仕様の副作用として、スキンスケルトンを使うモデルの場合、メッシュを持つノードの変換が無視されるため、メッシュノードを glTF アセットの `Nodes` 内の**どこに置いても構わなくなる。**

:::note info
実際、スキンスケルトンを使う [Alicia Solid](https://3d.nicovideo.jp/alicia/) の公式 VRM ファイルでは、以下のように、メッシュノードを配置するための専用のルートノードを作って整理しているようだ。

```sh
; メッシュノード
nodes[0], Name='mesh'
 nodes[1], Name='body_top'. Meshes[0], Skins[0]
 nodes[2], Name='body_under'. Meshes[1], Skins[1]
 nodes[3], Name='cloth'. Meshes[2], Skins[2]
 nodes[4], Name='cloth1'. Meshes[3], Skins[3]
 nodes[5], Name='cloth2'. Meshes[4], Skins[4]
 nodes[6], Name='cloth_ribbon'. Meshes[5], Skins[5]
 nodes[7], Name='eye'. Meshes[6], Skins[6]
 nodes[8], Name='face'. Meshes[7], Skins[7]
 nodes[9], Name='flonthair'. Meshes[8], Skins[8]
 nodes[10], Name='neck'. Meshes[9], Skins[9]
 nodes[11], Name='other'. Meshes[10], Skins[10]
 nodes[12], Name='other02'. Meshes[11], Skins[11]
; 以下、スキンスケルトン用のノード
nodes[13], Name='Root'
 nodes[14], Name='Hips'
  nodes[15], Name='LeftUpLeg'
   nodes[16], Name='LeftLeg'
    :
    :
```

この例では、`nodes[0～12]` のノード変換は完全に無視されるので、TRS プロパティや `matrix` プロパティを記述する必要はない。
:::

## スキンの `joints` と `skeleton` て？

glTF 2.0 では、「スケルトン」や「ジョイント（関節）」といった用語は、大抵「スキン」に関する文脈で登場する。かつ、これらの情報は `skin` の中にある。そのため自分は、「**スケルトンの定義にはスキンも含まれる？**」という **誤解** を抱いてしまった。

便宜上 **スキンスケルトン** などと称してしまったが、**スキンスケルトンはスキンの定義による影響を一切受けない。** スキンからスケルトン（またはそのジョイントノード）への一方向の参照があるのみである。

:::note info
例えば [Alicia Solid](https://3d.nicovideo.jp/alicia/) の公式 VRM ファイルでは、以下のように、骨格として繋がりようがない左右のジョイントノードが交互に並んでいたりする。ここからも、ジョイント配列がスケルトンを形作っているわけではないということが分かる。

```sh
skins[0], Skeleton=Nodes[14]
  joint[0] = Nodes[48] ('Spine3')
  joint[1] = Nodes[49] ('LeftShoulder')
  joint[2] = Nodes[115] ('RightShoulder')
  joint[3] = Nodes[50] ('LeftArm')
  joint[4] = Nodes[116] ('RightArm')
  joint[5] = Nodes[51] ('LeftForeArm')
  joint[6] = Nodes[117] ('RightForeArm')
    :
```
:::

スキンスケルトンを使う場合、メッシュの頂点は、スキンスケルトンの一部を表しているとある `skin` の `joints` 配列の中から、最大 4 つのジョイントノードを正しい配列インデックスで選ぶ。そのインデックスさえ正しければ、`skin.joints` 配列に格納される順番には特に決まりがないようだ。

（ただし、一緒に使われそうなジョイントは近くに集めていた方が、実装次第では、シェーダーリソースの節約になるかも知れない。また、`skin.joints` と `skin.inverseBindMatrix` との数と順番は一致させる必要がある。）

### `skin.skeleton` の存在意義

そして、glTF 2.0 仕様によれば、`skin.skeleton` プロパティが示す情報（ジョイントノードの共通の親となるノードのインデックス）は、**スキニングには使用されず**、ただスキンの「ピボットポイント」として使うこともできる、と記されているのみである。

個人的に思うに、1 つの glTF ファイルには複数のモデルを含めることができるので、あるスキンがどのモデル（のスキンスケルトン）に属するのかを示すのが `skin.skeleton` の由来だったのかなと思う。

しかし、将来的には廃止されそうだという情報もあるため、**`skin.skeleton` の設定は無視することが望ましい**かと考える。