minMatrix.js と座標変換行列
座標変換行列の基本
基本的な 3D レンダリングを行なう際には、三つの座標変換行列を用意します。これについては当サイトの以前のテキストでも何度か触れていますね。
一つ目はモデル変換行列で、DirectX などではワールド変換行列とも呼ばれるものです。モデル変換行列はその名のとおり、描画されるモデルに対して影響を与えます。モデルの位置、モデルの回転、モデルの拡大縮小(スケーリング)に関する情報を持たせるのが普通です。
二つ目はビュー変換行列です。これはわかりやすく言うと三次元空間を撮影するカメラを定義するためのものですね。カメラの位置、カメラの注視点、カメラの上方向を定義することで、カメラがどのように振舞うのかを決めます。
三つ目はプロジェクション変換行列です。この座標変換ではスクリーンの縦横比や、クリッピング領域などを定義します。また遠近法のような効果を得るためにもこの変換行列が必要になります。
これらのことを踏まえると、行列に対してどのような操作が必要になるのかおおよそ見えてきます。minMatrix.js を用いれば行列の基本的な操作を行なうことが可能です。あくまでも基本的な部分に限定されますが、minMatrix.js で出来ることについて見ていきましょう。
minMatrix.js の基本
当サイトオリジナルの行列演算ライブラリである minMatrix.js では、行列を生成したり、あるいは行列に操作を加えたりすることができます。minMatrix.js の柱となるのは matIV
と呼ばれるオブジェクトで、この matIV
オブジェクトを介して全ての行列操作が行なわれます。minMatrix.js で行列を扱う場合には、何を差し置いてもまずはこの matIV
オブジェクトを生成するところから始まります。
matIV オブジェクトを生成するコード
var m = new matIV();
上記のようなコードが走ると、変数 m
が matIV
オブジェクトのインスタンスとなります。 m
に続けてピリオドを打ち、適宜メソッド名などを記述することで matIV
オブジェクトの持つメソッドが利用できます。
以下に、minMatrix.js で定義されている matIV
オブジェクトのメソッドを列挙します。とりあえず今すぐに意味がわからなくても大丈夫ですので、ざっと見てみましょう。
minMatrix.js : create
構文 : matIV.create()
引数 : なし
戻り値 : 行列(に見立てた配列)
4 x 4 の正方行列を生成します。内部的には 16 の要素を持つ Float32Array オブジェクトで、全ての要素は 0 で初期化されています。
minMatrix.js : identity
構文 : matIV.identity(dest)
引数 : dest > 初期化する行列
引数 dest に受け取った行列を初期化(単位行列化)して出力します。
minMatrix.js : multiply
構文 : matIV.multiply(mat1, mat2, dest)
引数 : mat1 > 掛け合わせの元となる行列
引数 : mat2 > 掛け合わせる行列
引数 : dest > 演算結果を格納する行列
mat1 を左項、mat2 を右項として掛け合わせた結果を dest に出力します。
minMatrix.js : scale
構文 : matIV.scale(mat, vec, dest)
引数 : mat > 元となる行列
引数 : vec > スケーリングベクトル
引数 : dest > 演算結果を格納する行列
モデル変換における拡大縮小(スケーリング)を行ないます。mat には元となる行列を、vec には X Y Z の各スケーリング値を表すベクトルを渡します。ベクトルは三つの要素を持つ配列として渡します。演算結果は dest に出力されます。
minMatrix.js : translate
構文 : matIV.translate(mat, vec, dest)
引数 : mat > 元となる行列
引数 : vec > 原点からの移動距離を表すベクトル
引数 : dest > 演算結果を格納する行列
モデル変換における移動(座標位置の変更)を行ないます。mat には元となる行列を、vec には X Y Z の各方向への移動量を表すベクトルを渡します。ベクトルは三つの要素を持つ配列として渡します。演算結果は dest に出力されます。
minMatrix.js : rotate
構文 : matIV.rotate(mat, angle, axis, dest)
引数 : mat > 元となる行列
引数 : angle > 回転させる量をラジアンで指定
引数 : axis > 回転させる際の軸を表すベクトル
引数 : dest > 演算結果を格納する行列
モデル変換における回転を行ないます。mat には元となる行列を、angle には回転させたい量をラジアンで、axis には回転させる軸を指定するベクトルを三つの要素を持つ配列として渡します。演算結果は dest に出力されます。
minMatrix.js : lookAt
構文 : matIV.lookAt(eye, center, up, dest)
引数 : eye > カメラの位置を表すベクトル
引数 : center > カメラの注視点を表すベクトル
引数 : up > カメラの上方向を表すベクトル
引数 : dest > 演算結果を格納する行列
ビュー変換行列を生成します。eye にカメラの三次元空間上の位置を指定し、center はそのカメラが見つめる注視点を指定します。up にはカメラの上方向を表すベクトルを与えます。各ベクトルは三つの要素を持つ配列として渡します。演算結果は dest に出力されます。
minMatrix.js : perspective
構文 : matIV.perspective(fovy, aspect, near, far, dest)
引数 : fovy > 視野角
引数 : aspect > スクリーンのアスペクト比
引数 : near > ニアクリップ
引数 : far > ファークリップ
引数 : dest > 演算結果を格納する行列
プロジェクション変換行列を生成します。尚、ここで生成されるのは一般的には[ 透視射影 ]と呼ばれる射影変換行列で、いわゆる遠近法の効果が現れます。fovy には視野角を度数法で、aspect にはスクリーンのアスペクト比(幅÷高さ)を、near には前方クリップ位置(必ず 0 より大きな数値を指定)を、far には後方クリップ位置(任意の数値)を指定します。演算結果は dest に出力されます。
minMatrix.js : transpose
構文 : matIV.transpose(mat, dest)
引数 : mat > 元となる行列
引数 : dest > 演算結果を格納する行列
行列を転置します。mat に渡された行列を転置し、dest に結果を出力します。
minMatrix.js : inverse
構文 : matIV.inverse(mat, dest)
引数 : mat > 元となる行列
引数 : dest > 演算結果を格納する行列
行列から逆行列を生成します。mat に渡された行列の逆行列を求め dest に結果を出力します。
かなり機能が限定されたシンプル設計なのがわかると思います。手抜きとも言えますけれどもね……
行列変換の流れ
さて、minMatrix.js を使えば行列を操作できることはわかっていただけたと思います。具体的な操作の順序も、ざっくりと確認しておきます。
まずは、モデル変換行列だろうとビュー・プロジェクション変換行列だろうと、行列自体を生成しなければ何もできません。最初は matIV.create
を実行して行列を生成しましょう。生成した行列は、基本的に matIV.identity
を通して初期化します。ここまでをコードにすると、以下のようになりますね。
matIV オブジェクトを使った行列の初期化
// matIVオブジェクトを生成
var m = new matIV();
// 行列の生成と初期化
var Matrix = m.identity(m.create());
これで変数 Matrix
は使える状態の行列になりました。
上記のコードでは初期化と生成を一度に行なっていますが、次のように複数列に分けて記述しても、もちろん問題ありません。
matIV オブジェクトを使った行列の初期化(その 2 )
var Matrix = m.create();
m.identity(Matrix);
あとはこの初期化済み行列に対して、目的の操作を行なっていきます。たとえば、モデル変換行列を用意したいとして、モデルの座標を X 方向に 1.0 移動したモデル変換行列を生成するには次のようにします。
モデル変換行列に移動成分を与える例
var Matrix = m.identity(m.create());
m.translate(Matrix, [1.0, 0.0, 0.0], Matrix);
このようなコードが実行されると、配列 Matrix
には X 方向へ 1.0 移動したモデル変換行列が入っている状態になります。同様に、回転やスケーリングなどを行うことが可能です。ただし、ここで注意点があります。それは、移動・回転・拡大縮小を行なう順序です。移動してから回転するのと、回転してから移動するのでは、結果がまるで変わってきます。これは、回転が原点(0.0, 0.0, 0.0)を中心に行なわれるためです。モデル変換を行なう順序には十分に気を配りましょう。
具体的には、拡大縮小 > 回転 > 移動、という順序で変換を行なうようにすることで、回転・拡大縮小した状態のモデルを任意の位置に移動させることができるはずですね。
ただし、これで話は終わりません。
OpenGL は行列が列オーダーになっているため、掛け合わせる順序が真逆になります。これは行列の計算方法をしっかり勉強するとわかることなのですが、列オーダーと行オーダーでは、掛ける順序が逆になってしまうのですね。つまり、先ほどのように拡大縮小 > 回転 > 移動という順序で掛け合わせると結果は意図したものとは全く違うものになってしまいます。つまり、正確には[ 移動 > 回転 > 拡大縮小 ]という順序で掛け合わせればいいことになります。順序に注意しながら、目的のモデル変換行列を生成しましょう。
ビュー変換行列を生成するには matIV.lookAt
を、プロジェクション行列を生成するには matIV.perspective
を使えばいいですね。
ここまでくると、モデル・ビュー・プロジェクションの各変換行列が生成されているはずですので、これら三つの行列を掛け合わせて最終的な座標変換行列を生成します。配列同士を掛け合わせるには matIV.multiply
を使えばいいですね。
しかしここでも注意点があります。
モデル・ビュー・プロジェクションの三つの行列を掛け合わせる順番が重要です。以前のテキスト(行列(マトリックス)の基礎知識)でも解説しましたが、行列というのは掛け合わせる順番を変えると、その結果も変わるという特性があります。いわゆる普通の四則演算の積を求める場合には、左項と右項を逆にしても結果は同じですね。しかし行列の場合には結果が変わってしまいます。
座標変換行列は、モデル・ビュー・プロジェクションの頭文字を採って mvpMatrix
などと表記されることが多いのですが、掛け合わせる順序は mvp ではなく、p > v > m の順序で掛け合わせます。minMatrix.js を用いた場合のコードの一例を示すと次のようになります。
座標変換行列を用意する際の例
// 各種行列の生成と初期化
var mMatrix = m.identity(m.create()); // モデル変換行列
var vMatrix = m.identity(m.create()); // ビュー変換行列
var pMatrix = m.identity(m.create()); // プロジェクション変換行列
var mvpMatrix = m.identity(m.create()); // 最終座標変換行列
// 各行列を掛け合わせる順序を示す一例
m.multiply(pMatrix, vMatrix, mvpMatrix); // p に v を掛ける
m.multiply(mvpMatrix, mMatrix, mvpMatrix); // さらに m を掛ける
ここまで見てきたような手順で、無事に座標変換行列が生成できたら、それを最終的に WebGL に通知します。その方法については次回以降、解説する予定です。
まとめ
今回は当サイトオリジナルの行列演算ライブラリである minMatrix.js の基本的な使い方と、座標変換行列を用意する手順を解説しました。
minMatrix.js では、 matIV
というオブジェクトを介して行列を操作します。各種メソッドの内容は、今はよくわからなくても大丈夫です。必要なときになったら適宜解説を追加していきます。座標変換行列を生成できたらポリゴンをレンダリングできるところまであと少しです。
次回は、いよいよポリゴンが画面に映し出されるところまで解説します。