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 でモデルのスケルトンを定義するものが何であるかは、メッシュノードがスキンを持っているか持っていないかで異なる。 これはなかなか気付かなかった。
- スキンがない場合:メッシュには、そのメッシュノードのノード変換が適用される。
- スキンがある場合:メッシュには、スキンのジョイントノード のノード変換が適用され、メッシュノードのノード変換は無視される。
これは、同じ glTF モデルであっても、メッシュノードにスキンがあるかないか次第で、スケルトンの作り方がまったく違う ということを意味している。
ここでは仮に、1. を「メッシュスケルトン」、2. を「スキンスケルトン」とでも称して分類することにする。
glTF 2.0 の仕様だけだとこの 2 パターンのスケルトンを混在させることもできてしまうが、ここでは言及しないでおく。
なお、この仕様の副作用として、スキンスケルトンを使うモデルの場合、メッシュを持つノードの変換が無視されるため、メッシュノードを glTF アセットの Nodes
内のどこに置いても構わなくなる。
実際、スキンスケルトンを使う Alicia Solid の公式 VRM ファイルでは、以下のように、メッシュノードを配置するための専用のルートノードを作って整理しているようだ。
; メッシュノード
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
の中にある。そのため自分は、「スケルトンの定義にはスキンも含まれる?」という 誤解 を抱いてしまった。
便宜上 スキンスケルトン などと称してしまったが、スキンスケルトンはスキンの定義による影響を一切受けない。 スキンからスケルトン(またはそのジョイントノード)への一方向の参照があるのみである。
例えば Alicia Solid の公式 VRM ファイルでは、以下のように、骨格として繋がりようがない左右のジョイントノードが交互に並んでいたりする。ここからも、ジョイント配列がスケルトンを形作っているわけではないということが分かる。
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
の設定は無視することが望ましいかと考える。