トランスフォーム(姿勢の変換)について理解する

今回は座学です。

前回のglTFで全てのプリミティブを表示しようのコード実装で、glTFのポリゴンを全て表示できるようになりました。

しかし、これだけでは世にあるglTFの3Dモデルを正確に表示することはできません。

ほとんどの3Dモデルデータは複数のポリゴンメッシュの組み合わせで成り立っており、それぞれのポリゴンメッシュにはトランスフォーム(Transform)という姿勢情報が設定されています。これらの情報はBlenderやAutodesk Mayaなどの3D制作ソフトで3Dモデルを作っていく際、ソフトの内部処理で設定されるものです。

3Dライブラリはこれらのトランスフォーム情報をglTFなどの3Dモデルファイルから取り出し、各対応するポリゴンメッシュに正しく適用する必要があります。このトランスフォーム処理を経ることで、ポリゴンメッシュは制作時と同じ位置、向き、大きさで3Dシーンに配置され、画面に正しく表示されるようになります。

コーディングに取り掛かる前に、まずはこのトランスフォーム(姿勢の変換)の考え方について学んでいきましょう。

トランスフォーム情報とは何か

トランスフォーム = 平行移動+回転+拡大縮小

3Dソフトを使ったことのある人は、ソフトの基本機能として「平行移動」、「回転」、「拡大縮小」の3つがあることをご存知だと思います。

実際、3D空間中の物体の「姿勢」と「大きさのバリエーション」はこの3つでほぼ表現することができます。

物体の姿勢は「平行移動」と「回転」で表現できる

物体の「姿勢」については「平行移動」と「回転」だけで表現できます。

机の上に置いたマグカップを想像してください。コーヒーのお代わりを入れようと空のマグカップを掴んだ際、誤ってマグカップを机の下に落としてしまいました。この時、マグカップの落下は3D処理的には「下方向への平行移動」操作と考えることができます。落下して床にぶつかった後、マグカップは跳ねて床を転がっていくことでしょう。この転がりは3D処理的に「回転」操作と考えることができます。

つまり、形が変形しない物体であれば、「平行移動」と「回転」の2つの操作だけで3D空間中の「姿勢の変化(変換)」を表現できることになります。

image.png
Blenderで平行移動を行ったところ

image.png
さらに回転を行ったところ

大きさのバリエーションは「拡大縮小」で表現できる

3つ目の「拡大縮小」については、CGにおいては利便性のために存在している意味合いがあります。

物体には(特に人工物では)、形は同じだけど大きさが違う(幾何学的には「相似」と言います)バリエーションがたくさんあります。ネット販売サイトで品物を見ても、一つの商品でも大きさのバリエーションがあったりしますね。

CGの世界では、この大きさのバリエーションを作るために、同じ形のポリゴンデータを重複して保つ必要はありません。「拡大縮小」の変換操作によって同じポリゴンデータから複数の大きさの派生物をいくらでも表現することができます。

image.png
さらに拡大縮小(ここでは拡大)を行ったところ

平行移動+回転+拡大縮小はトランスフォーム情報というまとまりで一度に扱うことができる

CGの世界では、これら「平行移動」、「回転」、「拡大縮小」について、いくつかの表現形式を持っています。

代表的なものはベクトルと行列です。高校数学でもⅢC課程を選択した方は習っていると思います。数学の分野では線形代数と呼ばれる領域です。

数学と聞くとつい苦手意識が出てきますが、CGの初歩で必要となる知識は限定されており、ベクトルと行列を少しだけ知っておけばしばらくは困りません。

そして、「平行移動」、「回転」、「拡大縮小」の3つの変換操作はベクトル、行列どちらでも表現することができますが、「行列」の形式を取ることで、この3つの変換を合成することができます。

具体的にその合成を式として表すと次の通りです。

変換行列(トランスフォーム行列)= 平行移動行列 ✕ 回転行列 ✕ 拡大縮小(スケール)行列

ここで、記号は行列同士の掛け算です。この計算の結果である変換行列(トランスフォーム行列)には、「平行移動」、「回転」、「拡大縮小」の内容が全て含まれており、この変換行列をポリゴンメッシュの各頂点に作用させることで、ポリゴンメッシュの頂点を3Dソフトで作った時と同じ姿勢に狙い通りに変換することができます。

想定通りの頂点位置=変換行列(トランスフォーム行列) ✕ 頂点座標

ベクトル

ベクトルは複数の値を並べた複合的な数です。普通の数(ベクトルと対比してスカラーと呼ばれます)と同様に同じベクトル間で足し算や引き算などの演算が可能です。

CGの世界では、1つ目の数値が3D空間上のX軸、2つ目がY軸、3つ目がZ軸の値を意味します。

ベクトルによる操作

ベクトルを使って物体の姿勢操作を表すことができます。

例えば「y方向へ10移動させる平行移動」をベクトルで表現すると次のようになります。

(0,10,0)( 0, 10, 0)

例えば「x軸回りに30度回転させる回転」をベクトルで表現すると次のようになります。

(30,0,0)( 30, 0, 0)

例えば「x,y,z軸それぞれに等しく2倍に拡大する拡大縮小」をベクトルで表現すると次のようになります。

(2,2,2)( 2, 2, 2)

Blenderの画面でも、平行移動、回転、拡大縮小のベクトルの値を確認することができます。

608e4221b21906004904f12e.png
(Lotationが平行移動のベクトル値、Rotationが回転のベクトル値、Scaleが拡大縮小のベクトル値)

ベクトルによるデータ表現

ベクトルを使って、3D空間上のデータを表現することができます。

例えば、ポリゴンメッシュを構成する各頂点は3D空間上の場所である座標情報(位置座標)を持っています。この位置座標はベクトルで表現できます。

(5,10,20,1)( 5, -10, 20, 1)

位置座標(X軸が5, Y軸が-10、Z軸で20の場合)の例

本来はX,Y,Zの3要素のベクトルで位置座標の表現が十分できるのですが、CGでは後述する「行列」で4x4行列というものがよく用いられるため、それらと演算(計算)ができるように4つ目の要素(w)も加えて位置座標が表現されることがあります。

(5,10,20,1)( 5, -10, 20, 1)

位置座標(X軸が5, Y軸が-10、Z軸で20の場合)の例

なぜw(4つ目)が1なのかについては、別の機会に説明します。今は4つ目はほとんどの場合1でよい、と一種のおまじないくらいに思ってください。

また、ベクトル自体は「各軸に対応する数字が並んだもの」にすぎないため、それが具体的に何を意味するのか(平行移動、回転、拡大縮小などの「操作」なのか、位置座標などの「データ」なのか)は外の文脈から判断する必要があります。

ベクトルには行ベクトルと列ベクトルがある

ベクトルには数字を並べる方向によって行ベクトル(横に並べる)と列ベクトル(縦に並べる)があります。どちらも数字の並びには違いありませんが、行列と演算する際など、この違いが重要になることがあります。

WebGLでは慣習として列ベクトルが使われます。

(510201)\left( \begin{array}{c} 5 \\ -10 \\ 20 \\ 1 \\ \end{array} \right)

列ベクトル

しかし表記上、紙面スペースの効率としてはあまり良くないため、説明の文脈上問題ない場合は行ベクトルで表記するケースも多々あります。

(5,10,20,1)( 5, -10, 20, 1)

行ベクトル

行ベクトルと列ベクトルの2つの違いがどのようなケースで差が出るのかは別の機会で説明します。今はこうしたバリエーションがあるのだと頭の片隅に留めておいてください。

行列

ベクトルは横または縦どちらかの方向に数字を並べたものでした。他に「行列」というものもあり、これは横方向縦方向どちらにも数字を並べたものです。

CGの世界では3D物体の座標変換によく使われます。3x3行列(横方向・縦方向それぞれ数字を3つ並べた行列)または4x4行列(横方向・縦方向それぞれ数字を4つ並べた行列)がよく使われます。

(1000 01010 0010 0001)\begin{pmatrix} 1 & 0 & 0 & 0 \\\ 0 & 1 & 0 & 10 \\\ 0 & 0 & 1 & 0 \\\ 0 & 0 & 0 & 1 \end{pmatrix}

y方向へ10移動させる平行移動を行列で表現したもの

(1000 0cosθsinθ0 0sinθcosθ0 0001)\begin{pmatrix} 1 & 0 & 0 & 0 \\\ 0 & cos \theta & -sin \theta & 0 \\\ 0 & sin \theta & cos \theta & 0 \\\ 0 & 0 & 0 & 1 \end{pmatrix}

x軸回りに角度$\theta$の回転を行列で表現したもの

(x000 0y00 00z0 0001)\begin{pmatrix} x & 0 & 0 & 0 \\\ 0 & y & 0 & 0 \\\ 0 & 0 & z & 0 \\\ 0 & 0 & 0 & 1 \end{pmatrix}

拡大縮小を行列で表現したもの
(x=y=zの場合は大きさだけ変わり、x,y,zが異なる数値の場合は各軸で伸び縮みの量が異なるため形状として変形することになります)

そして前述の通り、これらの行列は掛け算によって1つの行列(トランスフォーム行列)に合成することができます。

変換行列(トランスフォーム行列)= 平行移動行列 ✕ 回転行列 ✕ 拡大縮小(スケール)行列

ベクトルと同様に行列についても、その値が具体的に何の意味で使われているかについては外の文脈から判断する必要があります。

ベクトルや行列の要素数について

3次元のCGにおいて、回転と拡大縮小は3x3行列で表現できますが、平行移動については4x4行列でなければ表現することができません。そのため、これらを合成してトランスフォーム行列とする都合上、回転行列や拡大縮小(スケール)行列も4x4行列に拡張して使われることが多くあります。

同様に、ポリゴンの各頂点座標のベクトルがWを含む要素数4のベクトルで表現されることが多いのも、これらの行列との演算を行う都合からです。数学上の規則で、行列同士や行列とベクトルの間で演算をするには、要素数が一致している必要があります。

こうしたベクトル・行列の情報は膨大なのでうんざりするかもしれません。とりあえず今は細かい部分がわからなくてもかまいません。少しずつ覚えていけばいいので、こうしたものをCGライブラリの計算では扱うのだな、くらいに今は受け止めてください。

線形代数のコードを自作するか、既存のライブラリを使うか

CGライブラリを実装するには本来、ベクトルや行列などの線形代数の知識を習得した上で、これらの機能を持ったクラスを作成することになります。しかし、自分でこれらすべてを自作するのは大変です。問題なく動作するまでデバッグしてバグを取り切るまでに全てのモチベーションを使い切ってしまいそうです。

ありがたいことに、世の中にはこれらの面倒を見てくれるライブラリが既にたくさんあります。JavaScriptの世界で有名なのはgl-matrix でしょう。

こうした計算ライブラリはよく使われている定番のものほど多くの検証が行われているためバグもほぼ取り除かれており、また計算も十分に最適化されています。実際、世にある知られたWebGLライブラリでもgl-matrix に依存しているものは少なくありません。

しかし、本連載では勉強も兼ねて、あえて自作することにしましょう。コードを振り返ってみると、実はすでにVector3とVector4クラスを作っています。この調子で次回から行列系のクラスも作ってみましょう。

それが終わったら、これらを駆使してトランスフォーム処理のプログラムに移ります。

10日目:glTFで全てのプリミティブを表示しよう

12日目:リファクタリングしよう