3D画像ファイルのカメラデータとは?
glTFファイルのカメラデータを解きほぐす
はじめに
3D画像ファイルは、3次元コンピュータグラフィックス(以下3DCG)技術と密接に関連しており、3D物体の表面形状(ジオメトリ)を表すデータが主体ですが、画像データ(明るさや色情報)が付加される場合もあります。
前回ので、glTFファイルなどの3D画像ファイルでは画像データをどのように扱って記録しているのかを調べました。
3D画像ファイルのデータによって3D物体が形成されることになるので、一般的には3D画像ファイルを読み取る3Dソフトウェアが3D物体を2D画像に変換して表示しています。
この2D画像に変換する操作方法はそれぞれの3Dソフトウェアにより異なり、好きな位置の画像を好きな視点から観察するようにそれぞれ工夫されています。
これは被写体をカメラで撮影するのと同じ動作を3DCGの世界でも実行できるようになっていると言えるでしょう。
3Dソフトウェアで自在に2D画像に変換できるのは便利な機能ですが、指定した2D画像を表示させるという機能も考えられます。
被写体をカメラで撮影するのと同じ動作をさせるデータを3D画像ファイル自体に組み込んでおくことによって、指定した2D画像を表示させるという機能があります。
前回取り上げたglTFファイルにもそういうカメラデータが格納できる機能が備わっています。
そのカメラデータとはどういうデータであって、その役割はどういうものなのでしょうか?
3D画像ファイルを用いて画像を形成する仕組みについては、3DCGに関する複雑な数式を駆使した解説記事が沢山見つかりますが、理解するのは骨が折れる作業になります。
今回は、glTFファイルのカメラデータだけに絞って、出来るだけ数式を使わないでそのデータの概念を解きほぐしてみることにします。
glTF 2.0ファイルフォーマットのカメラデータ
glTF 2.0ファイルフォーマットの仕様については、The glTF 2.0 Specificationに書かれています。
カメラデータは、視点データとプロジェクション変換データがあり(筆者が適当に分類)、
視点データとして、
・translation(視点移動)
・rotation(視点回転)
プロジェクション変換データとして、
・aspectRatio(アスペクト比)
・yfov(y画角)
・znear(z近点)
・zfar(z遠点)
のデータで構成されています。
後述する平行投影の場合は、aspectRatioとyfovの代わりにxmagとymagが用いられます。
こういう3DCG関連の用語の日本語訳は、異なる用語が用いられている場合が多くて混乱します。ここでは筆者の独断で書いていますのでご了解ください。
仕様書では、視点データは仕様書ではnodeデータのcameraプロパティに格納され、プロジェクション変換データはcameraアセットに格納されています。
それらカメラデータの意味や役割を以下に説明していきます。
視点データ
カメラ(視点)がどの位置にあるかをtranslation(視点移動)で設定し、カメラ(視点)がどの方向を見ているかをrotation(視点回転)で設定します。
translation(視点移動)は、被写体(3D物体)の原点からどれくらい離れた位置に視点があるかをxyz座標値で表します。この視点位置がカメラ機能で用いられるカメラ座標系の原点になります。
glTFファイルフォーマットでは、右手座標系で
The camera is defined such that the local +X axis is to the right, the “lens” looks towards the local -Z
axis, and the top of the camera is aligned with the local +Y axis.
と定義されているので、カメラ座標系の-Zc(混乱するのでカメラ座標系はXcYcZcと呼ぶことにする)方向が視方向になります。
被写体をカメラで撮影する場合、ある場所にカメラを置いた後、ある向きになるように、被写体を回転させるかカメラを回転するかの操作をして撮影します。
この回転操作は、被写体の座標系をカメラの座標系に対してどの程度回転させるかという操作になります。
この異なる座標系の回転量をrotation(視点回転)で表します。
3DCGの表現では、被写体の座標系をワールド座標系と呼び、カメラ座標系に変換することをビュー変換と呼んだりしています。
ワールド座標系をtranslation(視点移動)の分だけ移動させ、rotation(視点回転)の分だけ回転させればカメラ座標系になるということです。
視点と被写体原点を結んだ線上にそのカメラ座標系のZ軸を配置すれば、カメラは被写体の原点に向かって視方向が設定されることになります。
下記具体例1では、カメラの視方向が被写体原点に向かっています。注視点が被写体原点(ワールド座標系の原点)になっていると言い換えることができます。
具体例1
今回も著名なオープンソースの3DCGソフトであるBlenderから出力された、単純な立方体モデルのglbファイル(バイナリ)を拝借します。
Blenderのデフォルト状態(筆者の環境でBlenderインストールした直後の状態なのでデフォルトとしておく)で、の具体例3と同様に、立方体の向きが分かるように、メッシュモードの頂点カラー+ノーマルあり、BaseColorを(1,1,1)にして頂点ペイントで赤色1個の頂点だけに塗った状態を、カメラ設定にチェックを入れてglTF2.0(.glb)でエクスポートしました。
そのglbファイルの中の視点データを示すカメラデータのみを取り出すと、
"nodes": [
{
"camera": 0,
"name": "Camera",
"rotation": [
-0.209972992539406,
0.385779947042465,
0.090628445148468,
0.89379620552063
],
"translation": [
7.35889148712158,
4.95830917358398,
6.92579078674316
]
}
],
となっています。
具体例1のBlenderのカメラ設定は【図2】、レンズ設定は【図3】、カメラ画面は【図4】のようになっています。
このglbファイルを、ビューアソフトであるglTF Viewerを使って表示させた例を【図5】に示します。
このglTF Viewerは、右上にあるcamera設定を[default]からcameraに変更すると、glTFファイルのカメラ設定が反映されて表示されるようになっています。
translation(視点移動)について
具体例1のglTFファイルのtranslation値(Tx、Ty、Tzとします)は、
Tx=7.35889148712158,
Ty=4.95830917358398,
Tz=6.92579078674316
になっていました。
一方、このglTFファイルをエクスポートしたBlenderのカメラ設定は【図2】に示すように、
位置x=7.3589m
y=-6.9258m
z=4.9583m
となっていて値が一致しません。
同じ数値でありますが、yzの座標や符号が違っています。
これは、Blenderの座標系が【図6】に示すように、上方向+Z、右方向+X、奥方向+Yになっていて、glTFファイルの上方向+Y、右方向+X、奥方向-Zと異なっているせいです。
Blenderの座標系も右手座標系であり、X軸周りに-90゜回転させればglTFファイルと一致するのですが、Blenderではそのような座標回転変換はしないで、あたかも上方向+Y、右方向+X、奥方向-ZとみなしてglTFファイルをエクスポートしているようです。
この結果、【図5】に示したようにBlenderのカメラ画面と同じ形状の画像がglTFファイルの表示画面に現れています。頂点カラーの位置で同じであることが分かります。
すなわち、glTFファイルをエクスポートする際に、Y位置とZ位置を入れ替えて、入れ替えた後のZ位置の符号を反転していると思われます。
rotation(視点回転)について
具体例1のglTFファイルのrotation値はクォータニオン(四元数)で表されていて(qx、qy、qz、qwとします)、
qx=-0.209972992539406,
qy=0.385779947042465,
qz=0.090628445148468,
qw=0.89379620552063
になっていました。
クォータニオンは4個のqx、qy、qz、qw値だけで回転変換を表すことができます。
qx = λx・sinθ/2
qy = λy・sinθ/2
qz = λz・sinθ/2
qw = cosθ/2
であり、λx、λy、λzはそれぞれXYZ回転軸の方向ベクトル、θは回転角です。
glTFファイルフォーマットには、クォータニオンはqx、qy、qz、qwの順で記載するとされています。
一方、このglTFファイルをエクスポートしたBlenderのカメラ設定は【図2】に示すように、
回転w=0.780
x=0.484
y=0.209
z=0.337
となっていて値が一致しません。
これも、上記translationと同様に、Blenderの座標系が上方向+Z、右方向+X、奥方向+Yになっていて、glTFファイルの上方向+Y、右方向+X、奥方向-Zと異なっているせいです。
これを確かめるため、クォータニオンを回転行列に変換する式
にBlenderのクォータニオン
qw=0.780
qx=0.484
qy=0.209
qz=0.337
を代入して回転行列を得て、これにY成分とZ成分を入れ替え、入れ替えた後のZ成分の符号を反転する下記行列
を掛けると、得られる回転行列(M)は、
になります。
この回転行列(M)の値を回転行列クォータニオンに戻す式(M00+M11+M22>0の場合)
に代入すると、
qw=0.894
qx=-0.209
qy=0.386
qz=0.091
となって、上記glTFファイルのクォータニオンが得られます。
すなわち、rotation(視点回転)のデータも、glTFファイルをエクスポートする際に、Y位置とZ位置を入れ替えて、入れ替えた後のZ位置の符号を反転した値に変換されていることになります。
プロジェクション変換データ
プロジェクション変換データは、カメラ(視点)が3D物体のどの範囲を2D画像に変換するかを設定します。
glTFファイルフォーマットでは、
・Infinite perspective projection
・Finite perspective projection
・Orthographic projection
の3種類のプロジェクション変換が定められています。
perspective projectionは透視投影、Orthographic projectionは平行投影という日本語が用いられる場合が多いようなので、
それら3種類のプロジェクション変換は、無限透視投影、有限透視投影、平行投影とでも訳すのでしょうか?
これらのプロジェクション変換の式は、
・aspectRatio(アスペクト比)
・yfov(y画角)
・znear(z近点)
・zfar(z遠点)
のデータで構成されていて、
Infinite perspective projection(無限透視投影)の場合は、zfar(z遠点)を用いない仕様で、
Orthographic projection(平行投影)の場合は、aspectRatio(アスペクト比)とyfov(y画角)の代わりに"xmag"と"ymag"を用いる仕様になっています。
【図7】は通常のカメラの画角を示しています。
レンズが広角であれば広い画角範囲の被写体を撮影することができ、望遠であれば狭い画角範囲の被写体を撮影できます。
画角はカメラのセンサの大きさによっても変化します。
【図8】は典型的なプロジェクション変換を表す図です。
通常のカメラと同様に、ある画角範囲の3D物体のデータを取り込んで、2D画像を生成します。
その画角範囲は、カメラのセンサに相当する画面サイズを設定することで決定されます。
下記aspectRatio(アスペクト比)の項で説明します。
通常カメラと異なるのは、nearZと呼ばれる近点(glTFではznearという名になっている)からfarZと呼ばれる遠点(glTFではzfarという名になっている)までの範囲に制限されている点です。
またこの【図8】はglTFファイルの仕様上のFinite perspective projection(有限透視投影)を示しており、farZが無限遠になった場合にInfinite perspective projection(無限透視投影)の状態になります。
【図9】はOrthographic projection(平行投影)を表した図で、視点から3D物体が無限に離れた状態になっており、これは距離にかかわらず大きさが変化しないので、遠近感が不要な機械図面などで使われる投影方法です。
具体例1ではFinite perspective projection(有限透視投影)の形式になっていて、
そのglbファイルの中のプロジェクション変換データを示すカメラデータのみを取り出すと、
"cameras": [
{
"name": "Camera",
"perspective": {
"aspectRatio":
1.77777777777778,
"yfov": 0.399596520463049,
"zfar": 100,
"znear": 0.100000001490116
},
"type": "perspective"
}
],
となっています。
具体例2
具体例1の状態のままレンズデータだけを平行投影に変更して、glTF2.0(.glb)でエクスポートしました。
そのglbファイルの中のプロジェクション変換データを示すカメラデータのみを取り出すと、"nodes"の部分は具体例1と同じで、"cameras"の部分が下記のように変わっています。
"cameras": [
{
"name": "Camera",
"orthographic": {
"xmag": 3.65714287757874,
"ymag": 2.05714286863804,
"zfar": 100,
"znear": 0.100000001490116
},
"type": "orthographic"
}
],
具体例2のBlenderのカメラ設定は【図10】のようになっていて、カメラ画面は【図11】のようになっています。
このglbファイルを【図5】と同様に、ビューアソフトであるglTF Viewerを使い、camera設定を[camera]にして表示させた例を【図12】に示します。
【図5】と比べて頂点を結ぶ線が平行になっていることが分かります。
aspectRatio(アスペクト比)について
aspectRatio(アスペクト比)は視野の縦横比であり、画面のwidth/heightで表します。
プロジェクション変換によって、ある画角範囲の3D物体のデータを取り込んで2D画像が生成されるのですが、その画角範囲はカメラのセンサに相当する画像サイズを定めねばなりません。
通常カメラは、センサとレンズによって被写体を「切り取る」ことによって2D画像を生成していますが、3DCGでは3D物体をプロジェクション変換によって2D画像を生成するので、画角を自在に設定して2D画像を取得できることになります。
このセンサと画角の関係は【図13】を見ると分かり易いと思います。
【図13】ではセンサ(横をw、縦をhで示す)が焦点距離(fで示す)の位置に置かれています。
無限遠を撮影しているような状態です。
実際にセンサが置かれているのではなく、そのセンサが置かれていると見なして画角を定めるということです。
具体例1では、glTFファイルで
"aspectRatio": 1.77777777777778
となっていました。
【図4】に示すBlenderのカメラ画面では16:9のアスペクト比になっています(Blenderのセンサーフィットでも確認できる)。
16/9=1.77777777777778であるので、そのカメラ画面のアスペクト比が反映されていることが分かります。
yfov(y画角)について
yfov(y画角)は縦方向の画角(単位ラジアン)を表します。
具体例1では、glTFファイルで
"yfov": 0.399596520463049
となっていました。
【図3】や【図10】に示すBlenderのレンズ設定ではサイズ36mmで焦点距離50mmになっています。
これは横36mmのセンササイズであることを示しています。
上記のようにアスペクト比16:9であると、縦サイズは36×9/16=20.25mmになります。
縦サイズの1/2と焦点距離のなす角の2倍がyfovになるので、
2・arctan(20.25/2/50)
=22.8952゜=0.3995965rad
となり、plTFファイルのyfov値と一致します。
znear(z近点)とzfar(z遠点)について
znear(z近点)はプロジェクション変換を行う3D物体の最も近い距離、zfar(z遠点)最も遠い距離を表します。
具体例1では、glTFファイルで
"znear": 0.100000001490116
"zfar": 100
となっていました。
【図3】や【図10】に示すBlenderのレンズ設定の範囲の開始0.1m、範囲の終了100mがそのznearとzfarに対応しています。
xmagとymagについて
平行投影の際に使用されるxmagは横方向の視野サイズの1/2、ymagは縦方向の視野サイズの1/2とされています。
具体例2では、glTFファイルで
"xmag": 3.65714287757874
"ymag": 2.05714286863804
となっていました。
【図10】に示すBlenderのレンズ設定では平行投影のスケールは7.314になっています。
その7.314の1/2が
7.314/2=3.657
となりこれが"xmag"になっています。
縦方向はアスペクト比16:9であるので、
3.657・9/16=2.057
となりこれが"ymag"になっています。
Blenderの平行投影のスケールが7.314になっている理由は不明でしたが、【図4】と【図11】を比較すると、平行投影の場合の大きさが透視投影の場合の大きさにほぼ合致しているので、透視投影の際のznearなどの距離におけるサイズに合うように計算されていると思われます。
おわりに
translationとrotation
の2つがある。
有限透視投影の場合に、
aspectRatio、yfov、znear、zfar
の4つがあり、
無限透視投影の場合に、
aspectRatio、yfov、znear
の3つがあり、
平行投影の場合に、
xmag、ymag、znear、zfar
の4つがある。
などを学びました。
このため、Blenderのカメラ設定データとエクスポートされたglTFファイルのデータとの不一致が起こる。
などを知ることができました。
今後も調べていくことにします。