panda's tech note

WebGLの基礎(座標変換の復習)

WebGL はブラウザ上で3Dグラフィックスを表示するための標準仕様で,多くのブラウザでサポートされています。WebGL は HTML5 の Canvas に描画します。また,WebGL は組み込み機器向けの OpenGL 規格である OpenGL ES から派生しているため,OpenGL ES Shading Language (GLSL) と呼ばれるプログラミング言語によりプログラミングします。

オブジェクトの移動・拡大縮小・回転と投影

実際に WebGL を扱う前に,描画するオブジェクト(物体)の移動・回転および投影について簡単に説明します。なお,今回は導出については省略します。OpenGL では,このあたりの変換関数が準備されているのですが,WebGL では(2021年2月現在では)自分で実装するか,外部ライブラリを使用する必要があります。

まず,直交する\(x\)軸,\(y\)軸,\(z\)軸で表現される3次元のユークリッド空間上の座標 \(v_x, v_y, v_z\) の移動と回転について考えます。3次元空間の頂点ですが,移動を扱う際に4次元ベクトルとすると便利であるため,頂点の座標を \( \boldsymbol{v} = (v_x, v_y, v_z, 1)^T \) とします。以下では頂点の変換を取り扱いますが,線形変換なので,オブジェクトに対する操作はオブジェクトの各頂点を変換することで実現できます。

移動

この頂点を \(\delta_x, \delta_y, \delta_z \) だけ移動する操作,つまり \( \boldsymbol{v}' = (v_x + \delta_x, v_y + \delta_y, v_z + \delta_z, 1)^T \) を得る操作は,以下のように表すことができます。

\begin{align*} \boldsymbol{v}' &= \begin{bmatrix} v_x + \delta_x \\ v_y + \delta_y \\ v_z + \delta_z \\ 1 \end{bmatrix} \\ &= \begin{bmatrix} 1 & 0 & 0 & \delta_x \\ 0 & 1 & 0 & \delta_y \\ 0 & 0 & 1 & \delta_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \boldsymbol{v} \end{align*}

つまり, \begin{align*} M = \begin{bmatrix} 1 & 0 & 0 & \delta_x \\ 0 & 1 & 0 & \delta_y \\ 0 & 0 & 1 & \delta_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \end{align*} と置くと, \begin{align*} \boldsymbol{v}' &= M \boldsymbol{v} \end{align*} という頂点ベクトルに対して左から行列\(M\)をかけることで頂点の移動を表すことができます。

拡大縮小

移動と同様に拡大縮小も行列演算により表すことができます。

\begin{align*} S = \begin{bmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \end{align*} とおくと,

\begin{align*} S\boldsymbol{v} &= \begin{bmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} v_x \\ v_y \\ v_z \\ 1 \end{bmatrix} \\ &= \begin{bmatrix} s_x v_x & 0 & 0 & 0 \\ 0 & s_y v_y & 0 & 0 \\ 0 & 0 & s_z v_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \end{align*} と \(x\)軸,\(y\)軸,\(z\)軸の方向に \(s_x, s_y, s_z\) 倍拡大(縮小)する方向に頂点を変換することができます。

回転

回転の説明をする前に,3次元ユークリッド空間の座標系においては右手系と左手系があります。OpenGL は右手系を採用しており,\(x\)軸の正方向から見た場合,\(y\)軸を\(90^\circ\)回転させた場合に\(z\)軸,\(y\)軸の正方向から見た場合,\(z\)軸を\(90^\circ\)回転させた場合に\(x\)軸,\(z\)軸の正方向から見た場合,\(x\)軸を\(90^\circ\)回転させた場合に\(y\)軸となります。

これを踏まえて,回転も行列演算により表すことができます。\(x\)軸,\(y\)軸,\(z\)軸,それぞれを中心として \(\theta \) 回転するには,

\begin{align} R_x &= \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta & 0 \\ 0 & \sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \\ R_y &= \begin{bmatrix} \cos\theta & 0 & \sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \\ R_z &= \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \end{align}

とおくと,\( R_x \boldsymbol{v} \),\( R_y \boldsymbol{v} \),\( R_z \boldsymbol{v} \) と表すことができる。

投影

頂点の移動は比較的単純ですが,投影は少々複雑です。投影には平行投影と透視投影があります。透視投影は任意の視点から物体を見たときの物体の見え方を表すため,3次元オブジェクトの描画に用いられます。一方,平行投影は,ひとつの点からの見え方ではなく,平行な面(無限遠の視点など)からオブジェクトを観察した場合の物体の見え方を表すため,物体の奥行きに関しては無視されます。物体に無限遠とみなせる光(太陽光など)を当てたときの影は平行投影です。平行投影は特に頂点の変換は必要ないため,ここでは透視投影について説明します。

透視投影は視点の座標ベクトルと向き(視線)の単位ベクトルから4x4の変換行列を得る方法が最も汎用性が高いですが,変換式が複雑になるため,今回は簡単のために視点を原点、視線を\(z\)軸の負の方向に固定した場合について説明します。視点から\(x\)-\(y\)平面と平行な長方形領域を指定すると,四角錐が視野として定義できます。透視投影では,任意の\(x\)-\(y\)平面と平行な面によるこの四角錐の断面が同じ大きさに見えるため,この四角錐に対して視点からの異なる2点の距離から得られる四角錐台を立方体に変換する操作により変換できます。

ここで,視点から近い点,つまり四角錐台の上面までの距離を \( d_n \),視点から遠い点,つまり四角錐台の底面までの距離を \( d_f \),上面の幅および高さをそれぞれ \( w \),\( h \) とすると,四角錐台を立方体に変換する操作は以下の行列で表すことができる。

\begin{align*} P = \begin{bmatrix} \frac{2 d_n}{w} & 0 & 0 & 0 \\ 0 & \frac{2 d_n}{h} & 0 & 0 \\ 0 & 0 & -\frac{d_f + d_n}{d_f - d_n} & -\frac{2 d_f d_n}{d_f - d_n} \\ 0 & 0 & -1 & 0 \end{bmatrix} \end{align*}

つまり,ある頂点 \( \boldsymbol{v} \) をこの視点から投影した点への変換(射影)は \(P \boldsymbol{v} \) により得られます。