ボーンアニメーションのオフセット行列とは?
glTFファイルのInverseBindMatricesを解きほぐす
はじめに
前回の3D画像ファイルのアニメーションとは?で、3Dデータフォーマットの決定版とされるglTFファイルのアニメーションデータを調べて、ボーンアニメーションの基本構造についても触れました。
ボーンアニメーションは、骨格となるボーン(Bone)を設定してそのBoneに付随する3D物体の動きを制御するアニメーションですが、Boneに付随する3D物体の動きを制御するのにForward Kinematics(順運動学)やInverse Kinematics(逆運動学)と呼ばれる手法があります。
これらの手法は複数のBoneの動きに合わせて3D物体の頂点の位置を計算して設定していきます。
そのような動き制御の計算を簡略化するために、Boneの状態を初期位置に戻す行列データが用いられています。
glTFファイルでは、ボーンアニメーションのことをスキンメッシュアニメーションと呼んでいて(前報3D画像ファイルのアニメーションとは?参照)、そのBoneの状態を初期位置に戻す行列データのことをInverseBindMatricesと呼んでいます。この行列は日本ではボーンオフセット行列と呼ばれているようです。
InverseBindMatrices(以下ボーンオフセット行列と呼ぶことにします)はどういう行列でどのような役割を果たすのでしょうか?
今回は、glTFファイルのInverseBindMatricesデータの構造を解きほぐすことによってボーンアニメーションの具体的な技術を学ぶことにします。
glTF 2.0ファイルフォーマットのInverseBindMatricesデータ
glTF 2.0ファイルフォーマットの仕様については、The glTF 2.0 Specificationに書かれています。
InverseBindMatricesデータについては、
引用:vertex skinning の覚え書き
という記事があります。
そのらのデータの意味や役割を以下に具体的に説明していきます。
行列演算の問題
3DCG技術では行列演算が多用されるのですが、行列の扱い方に異なる2つの形式があります。
「列優先表記による平行移動行列は、
行優先表記による平行移動行列は、
のようになる。」
引用:3Dプログラミング入門§3-8 行列 2
という行優先/列優先の表記とか、
引用:3Dプログラミング入DirectXとOpenGLのベクトル/行列演算の違い
という横ベクトル/縦ベクトルの表記として紹介されています。
行優先表記と横ベクトル表記は同じで、列優先表記と縦ベクトル表記は同じ意味になります。ここでは行優先/列優先の用語を採用しておきます。
列優先行列と行優先行列は転置の関係になっており、行優先行列の積は左から右へ順に掛けていくのに対し、列優先行列の積は右から左へ順に掛けていくという掛け算方向の違いがあります。
ここで取り上げるinverseBindMatrices は 4x4 行列で、glTF仕様書にはその格納順についての記載はないのですが、
「An accessor referenced by inverseBindMatrices MUST have floating-point components of "MAT4" type. The number of elements of the accessor referenced by inverseBindMatrices MUST greater than or equal to the number of joints elements.The fourth row of each matrix MUST be set to [0.0, 0.0, 0.0, 1.0].」
と書かれています。
第4行が[0.0, 0.0, 0.0, 1.0]であることというのは、列優先の行列を意味しているので(上記引用参照)、以下列優先の表記で説明することにします。
ボーンアニメーションの行列演算
ボーンアニメーションの行列演算については。
引用:床井研究室バーテックスブレンディング
という記事が正確に詳しく説明されています。
また、
引用:スキンメッシュアニメーションの解説
という記事が行優先表記ではありますが、具体例を使って分かり易く説明されています。
ボーンアニメーションの行列演算には、いろいろな異なる用語が用いられていて混乱するので、ここでは以下の用語を用いて分かり易く表記することにします。
最初に、ボーンを配置する工程では、
モデル空間に配置されたボーン座標=ボーン姿勢行列・ボーン空間でのボーン座標
で表される。
という行列演算が用いられます。
ここで、
を表します。
この動作は、ボーンが存在する座標系(ローカル空間とかボーン空間とかジョイント空間などいろいろな用語が使われる)をボーンが配置される座標系(ワールド空間とかモデル空間などどいろいろな用語が使われる)に変換する処理でありますが、Blenderでは単にボーンの座標を設定しているだけです。
次に、ボーンの座標を変換する工程では、
モデル空間での変換後のボーン座標=
ボーン姿勢行列・変換行列・ボーンオフセット行列・モデル空間での変換前のボーン座標
で表される。
という行列演算が用いられます。
ここで、
を表します。
ボーンの座標値が変化するアニメーションでは変換行列が使用されますが、特に回転変換の場合には、回転中心が原点でないと、よく知られた回転行列を用いることができないため、ボーンを一旦ボーン空間に戻し、そのボーン空間の原点を回転中心として回転行列を施して回転変換を行い、その後再びモデル空間に戻すという処理が行われています。
ボーンアニメーションにおけるボーンオフセット行列
ここではボーンアニメーションに用いられるglTFファイルのinverseBindMatricesの働きをできるだけ具体的に説明します。
具体例1(ボーンの配置)
具体例1は1個のボーン(Bone)を配置するだけの単純な例です。
前回の3D画像ファイルのアニメーションとは?と同様に、glTFファイルの生成には、著名なオープンソースの3DCGソフトであるBlenderから出力されたファイルを用います。
前回と同様に、「オブジェクトの追加」機能で1個のアーマチュア(BlenderではBoneのこと)を設定します。
立方体(Cube)は位置関係を示すために削除しないでワイヤフレームにして、Boneの形状はデフォルトの8角形にしておきます。
位置をx +1m、y +1m、z +1m、回転は0、スケールは1に設定します。
この配置状態を【図1】に示します。
【図1】は、Blenderでは単にボーンの座標を設定しているだけですが、これを【図2】に示すように、ボーンをモデル空間とは別のボーン空間にあると考えて、そのボーンを移動させて配置すると考えると行列演算が分かりやすくなると思います。
この状態でBlenderからエクスポートされたglbファイルのJSONデータを調べると、ジオメトリを示す"skins"情報は、
"skins": [
{
"inverseBindMatrices": 2,
"joints": [
],
"name": "30a230fc30de30c130e530a2"
}
],
となっています。
"accessors"情報は、
"accessors": [
カラム0省略
カラム1省略
カラム2は、
{
"bufferView": 2,
"componentType": 5126,
"count": 1,
"type": "MAT4"
},
となっています。
カラム2の"inverseBindMatrices"のデータは、"componentType": 5126により4Byteの浮動小数点(単精度float)で、"type": "MAT4"により4x4 行列が"bufferView": 2のバッファに格納されています。
その4x4 行列の値は10進表記で、(1,0,0,0),(0,1,0,0),(0,0,1,0),(-1,1,1,1)となっています。括弧は分かり易いように付与しています。
前述したように。これを普通に読めば、(1行目)、(2行目)、(3行目)、(4行目)の行列になると思えるのですが、glTF仕様書により、(1列目)、(2列目)、(3列目)、(4列目)の行列とみなす(あるいは転置行列にする)必要があるので、
となります。
このinverseBindMatrices(ボーンオフセット行列)の逆行列がボーン姿勢行列になるので、inverseBindMatricesの逆行列を求めると、
となります。
ボーンを【図1】の位置に配置する際、ボーンの座標、例えば(x=0,y=0,z=0)の点が(x=1,y=1,z=1)に平行移動(オフセット)されたことになり、
モデル空間に配置されたボーン座標=ボーン姿勢行列・ボーン空間でのボーン座標
の式で配置後のボーン座標が設定されます。
配置後座標の確認(ボーンの始点の場合):
このボーン配置のオフセット処理は、ボーンの座標系全体がオフセットされるので、ボーンの始点とかの特定の点に限定されるのではありません。
例えばボーン空間でのボーンの座標の(x=0,y=0,z=0)の点に対しては、座標変換のために同次座標(x=0,y=0,z=0,w=1)を使って、
\end{bmatrix}・\begin{bmatrix} 0 \\ 0 \\ 0 \\ 1\end{bmatrix}=\begin{bmatrix} 1 \\ 1 \\ -1 \\ 1\end{bmatrix}$$
が計算されて、(x=1,y=1,z=-1)の配置後座標が得られます。
この配置後座標(x=1,y=1,z=-1)はBlenderで【図1】の位置に配置した(x=1,y=1,z=1)とは符号が異なっています。
これは、3D画像ファイルのカメラデータとは?で述べたように、Blenderの座標系が上方向+Z、右方向+X、奥方向+Yになっていて、glTFファイルの座標系の上方向+Y、右方向+X、奥方向-Zと異なっていることが原因で、Blenderが、あたかも上方向+Y、右方向+X、奥方向-ZとみなしてglTFファイルをエクスポートしているためです。
配置後座標の確認(ボーンの先端の場合):
ボーン空間でのボーンの先端に当たる座標の(x=0,y=1,z=0)の点に対しては、座標変換のために同次座標(x=0,y=1,z=0,w=1)を使って、
\end{bmatrix}・\begin{bmatrix} 0 \\ 1 \\ 0 \\ 1\end{bmatrix}=\begin{bmatrix} 1 \\ 2 \\ -1 \\ 1\end{bmatrix}$$
が計算されて、(x=1,y=2,z=-1)の配置後座標が得られます。
【図1】のボーン先端が上に突き出ている位置を表しています。
具体例2(ボーンの変換)
具体例2は1個のボーン(Bone)だけがX軸周りに90゜回転するだけの単純なアニメーションで、まだBoneに付随する3D物体との関連付けは行っていません。
前報3D画像ファイルのアニメーションとは?の具体例2に相当します。
Blenderで、animationモードでアーマチュアの上記初期状態を0フレームを登録し、アーマチュアのx回転角を90゜にして120フレームに登録します。終了は140フレームに設定しました。
Blenderでのアニメーションの様子を見ていただくために、Blenderの出力機能で動画であるaviファイルを出力し、aviファイルはデータ容量が大きいので、ブラウザで表示させるためaviファイルを適当な外部ツールでデータ圧縮されたwebmファイルに変換した動画を【図3】に表示します。
立方体は位置確認用のためワイヤーフレームにしています。
ボーンの回転制御が一旦ボーン空間に戻した状態を、Blenderでボーンのオフセットを省いて作成した動画を用いて【図4】に表示します。
【図4】はモデル空間の原点を中心に回転しているので紛らわしいのですが、ボーン空間にあるとみなしてオフセットの様子を見ていただければと思います。
【図2】のようにボーンがアニメーションされると、モデル空間で座標変換されたボーンの座標値は、
モデル空間での変換後のボーン座標=
ボーン姿勢行列・変換行列・ボーンオフセット行列・モデル空間での変換前のボーン座標
の式で変換後のボーン座標が設定されます。
ボーンオフセット行列はInverseBindMatricesで、ボーン姿勢行列はボーンオフセット行列の逆行列であり、これらの行列は、アニメーションで刻々変化する変換行列と違って不変ですので、ボーンの座標を変換する行列演算が簡略化されます。
しかしながら、この状態でBlenderからエクスポートされたglbファイルのinverseBindMatricesデータを調べると、
となっていました。
inverseBindMatricesの逆行列であるボーン姿勢行列を求めると、
となります。
これら具体例2のinverseBindMatricesとボーン姿勢行列は、具体例1とBoneを配置した位置は同じであるのに値が異なっています。
配置後座標の確認(ボーンの始点の場合):
具体例1と同様の確認を行ってみると、
ボーン空間でのボーンの座標の(x=0,y=0,Z=0)の点に対しては、座標変換のために同次座標(x=0,y=0,Z=0,w=1)を使って、
\end{bmatrix}・\begin{bmatrix} 0 \\ 0 \\ 0 \\ 1\end{bmatrix}=\begin{bmatrix} 1 \\ 1 \\ -1 \\ 1\end{bmatrix}$$
が計算されて、(x=1,y=1,z=-1)の配置後座標が得られて、具体例1と同じ結果です。
配置後座標の確認(ボーンの先端の場合):
しかし、ボーン空間でのボーンの先端に当たる座標の(x=0,y=1,z=0)の点に対しては、座標変換のために同次座標(x=0,y=1,z=0,w=1)を使って、
\end{bmatrix}・\begin{bmatrix} 0 \\ 1 \\ 0 \\ 1\end{bmatrix}=\begin{bmatrix} 1 \\ 1 \\ 0 \\ 1\end{bmatrix}$$
が計算されて、(x=1,y=1,z=0)の配置後座標が得られて、具体例1と異なった結果です。
この配置後座標(x=1,y=1,z=0)は、【図5】に示すボーンアニメーションの最終位置のボーン先端座標になっています。
すなわち、具体例2のBlenderからエクスポートされたglTFファイルのinverseBindMatricesデータは、アニメーションの最終位置のボーンに対するオフセット行列が格納されていることになります。
ボーン始点だけを制御する場合はこれでも良いかも知れませんが、注意が必要と思われます。
おわりに
調べたInverseBindMatricesデータはBlenderが作成したことになります。
ボーンアニメーションに設定した場合に、その設定前と後でInverseBindMatricesデータが異なっていることを知りました。
glTF仕様書にInverseBindMatricesデータ設定の厳密な規定はなく、Blenderの意図も分からなかったので、そのように設定されている理由は不明でした。
BlenderがエクスポートしたglTFファイルのInverseBindMatricesデータがどのように利用されるかについては、更なる調査が必要だろうと考えています。
ボーンを一旦モデル空間からボーン空間に戻す処理を行う理由については、「原点でなく任意の点を中心に回転させる回転変換行列が複雑になって処理が重たくなるのを回避するため」であろうと考えています。