TT Lab
はじめる
学ぶ 学習パス コース

3D数学とソフトウェアラスタライザ

なぜ3次元なのに4x4行列なのか

TT Labで続きを見る

一言でいうと

移動は乗算ではなく加算なので、3x3の行列には入りません。座標を1つ増やして(x, y, z, 1)にすれば、移動まで乗算1回で表現でき、そのため、グラフィックスの変換行列は4x4です。

なぜ必要なのか

物体1つを画面に置くには、いくつものことを続けて行わなければなりません。大きさを縮め、回し、所定の位置に移し、カメラを基準に移し直し、投影します。これを関数呼び出しでつなげると、頂点1つごとに、5回の異なる計算が付きます。頂点が10万個なら、50万回です。

これらの計算がすべて行列の乗算1つで表現できれば、状況が変わります。行列の積は結合法則が成り立つので、5つを先に掛けて1つにしておくことができ、頂点ごとには、4x4の乗算を1回だけすれば済みます。GPUが、ユニフォームとして、MVP行列を1つだけ受け取る理由が、これです。問題は、移動が乗算ではないということです。3x3の行列では、回転と拡大縮小はできますが、平行移動は表現できません。

どう動くのか

解決法は、次元を1つ増やすことです。点(x, y, z)を(x, y, z, 1)と書いて、4x4の行列を掛けると、最後の列が、その1と掛け合わされて、結果に足されます。その列に移動量を入れれば、平行移動が乗算の中に入ります。

| 1 0 0 tx |   | x |   | x + tx |
| 0 1 0 ty | * | y | = | y + ty |
| 0 0 1 tz |   | z |   | z + tz |
| 0 0 0  1 |   | 1 |   |   1    |

この表現を同次座標(homogeneous coordinates)と呼びます。4番目の成分wは、単なる穴埋めではなく、意味があります。wが1なら点で、0なら方向です。方向には移動を適用してはいけませんが(北は、どこへ移しても北です)、w=0にしておくと、移動の列が0と掛け合わされて、自然に無視されます。ノーマルベクトルと光源の方向を、(x, y, z, 0)として扱う理由です。

掛ける順序が結果を変えます。行列の積は、交換法則が成り立ちません。T * Rは「先に回して、そのあと移す」で、R * Tは「先に移して、そのあと回す」です。列ベクトルを右に掛ける慣習では、右にある行列が先に適用されます。点(1,0,0)に、z軸90度の回転と、x方向に2だけの移動を適用してみると、前者は(2,1,0)で、後者は(0,3,0)です。物体がその場で回るのか、軌道を回るのかが、ここで分かれます。

回転行列には、向きという落とし穴がもう1つあります。右手座標系で、z軸の回転は、x軸をy軸のほうへ回します。

rotate_z(t) = | cos t  -sin t  0  0 |
              | sin t   cos t  0  0 |
              |   0       0    1  0 |
              |   0       0    0  1 |

符号を1つ間違えて入れると、物体が逆向きに回ります。エラーメッセージは出ません。

行優先と列優先という慣習の違いもあります。このラボは、行列を「長さ4のリスト4つ」としておき、点を右に掛けます。逆に、DirectX系のドキュメントやコードは、しばしば点を左に置いて、行ベクトルとして掛けますが、そうすると、同じ変換の行列が転置された姿になります。2つの慣習の間では、掛ける順序も反転します。他人のコードを持ってきたときに結果がおかしければ、まずこれを疑う必要があります。メモリにどんな順序で入れるかは、また別の軸なので、慣習を2つではなく4つと数えることもできます。

回転を表現する方法も、行列だけではありません。クォータニオンは、4つの値で回転を持ち、2つの回転の間を滑らかに混ぜること(補間)が、行列よりもずっとうまくいきます。行列2つを成分ごとに混ぜると、回転行列ではないものが出てくるからです。ただし、GPUに渡すときは、結局、行列に変えなければならないので、状態はクォータニオンで持ち、描くときに行列を作るのが、よくある構成です。

現場での姿

シェーダーでノーマルベクトルを変換するとき、モデル行列をそのまま使うと、照明が狂います。不均等な縮小(たとえば、xだけを半分に縮めること)が入ると、表面に垂直だったベクトルが、もう垂直ではなくなるからです。正しい答えは、モデル行列の逆行列の転置を使うことで、この事実は、行列を自分で扱ってみないと、覚えられません。

もう1つは、浮動小数点の蓄積です。回転行列を、毎フレーム少しずつ掛けて蓄積すると、数百フレーム後には直交性が崩れて、物体がだんだん歪みます。そのため、エンジンは、角度やクォータニオンを状態として持っておき、毎フレーム行列を作り直します。行列そのものを、状態にはしません。

もう1つ、よく見落とされるのが、逆行列のコストです。一般的な4x4の逆行列は、計算が重いですが、グラフィックスで扱う変換の大部分は、回転と移動だけでできているので、ずっと安く反転できます。回転の部分は直交行列なので、転置がそのまま逆行列で、移動は、符号を反転させて、回転の逆を適用すればよいのです。カメラ行列を作るとき、実際にこの性質を使います。前に見たビュー行列が、カメラの3つの軸を行として立て、最後の列に負の内積を入れる形をしているのが、まさにその計算です。

最後に、ベクトル演算のうち、何を使うかを選ぶ感覚も必要です。内積は角度と投影に、外積は垂直な方向と面積に使われます。2つのベクトルが同じ方向を向いているかを知りたければ、内積の符号を、どちら側へ回るかを知りたければ、外積の符号を見ます。この2つの文が、グラフィックスのコードの相当な部分を説明します。

次のラボですること

3次元のベクトル演算と、4x4の行列の積を自分で作り、移動・拡大縮小・回転の行列を組み立てます。掛ける順序を変えて、結果がどう変わるかを数字で確認したあと、変換した四角形3つを、1枚のPNGに描いて、目で見ます。最後に、外積で、三角形の法線と面積を求めます。