home /
uni /
gl /
top
行列の積と投影
行列の積・平行投影
- カラムメジャーであることに注意する
Mat4.prod
は複数の行列の積を一度に計算し、新しい配列を返す
- ただし、引数がひとつの場合は引数として渡された配列をそのまま返す
- ポインターが押下された時点で行列とポインタ座標を保存しておき、ポインターが移動したときに移動量から回転量を決めて、押下時の行列に回転行列を乗じて新たなモデル行列とする
- 軸キューブ(たったいま命名)は、原点からX・Y・Zのプラス方向に辺の長さが1の正方形を3枚組み合わせており、(0, 0, 0) に置かれていて原点を中心に回る
- 投影変換は平行投影(正射図法)で、
Mat4.scale
で高さを基準としてアスペクト比を補正した上で、X・Y座標には表示倍率を乗じる
- 今回は深度バッファを使っていないのでZは計算せず、強制的に0にする。
- X軸・Y軸まわりに同時に回すと不自然さがあるが、これは次で直す
- なお、
Mat4.scale
は前回HTML中に書いたコードから少し仕様が変わっている
x
・y
・z
すべてnull
ならば全軸1倍
x
が非null
、y
・z
がnull
ならば全軸x
倍
x
・y
が非null
、z
がnull
ならばZ軸は1倍
視点移動・透視投影
- 上半分は上から見た平行投影で、Z方向への移動と、Z軸まわりの回転ができる
- 上から見るためには座標軸をX軸まわりに-90度まわす
- この例ではさらに回転前のZのマイナス方向、回転後の軸だとYのプラス方向に2だけ視点を移動させている
- 座標系は固定して物体を見ると、Yのマイナス方向に-1だけ動かして、X軸まわりに90度まわせばよい
- 実用的なビュー行列では、これに視野の回転(視野の上方向の指定)が入る
- 下半分は正面から見た透視投影で、X軸・Y軸まわりの回転ができる
- 透視投影のキモはふたつ
- 視野角(画角)から拡大率を決定
- 視点と物体との距離が変わらなければ、視野角で変わるのは拡大率だけ
- ズームレンズで広角から望遠へズームした時に、像が拡大されるだけで見え方が変わるわけではないのと同じ
- Wにabs(Z)を設定
- 頂点シェーダのあと、ラスタライズ前にX・Y・ZをWで割る処理が入り、ラスタライズにはX・Yのみが使われる
- Wにabs(Z)(GLの場合は-Z)を入れておけば距離に反比例した大きさで描画される
- Z軸方向の距離\(z\)にある物体が、スクリーンのちょうど上端に見えている場合、その物体のY座標\(y\)は、(Y方向の)視野角を\(\theta\)とすると、\(y = z \tan \theta/2 \)になる。
- このときに頂点シェーダーの出力Y座標が1になればいいので、Y座標を\(z \tan \theta/2\)で割ればいいことになる
- 実際にはあとで\(z\)で割られてしまうので、あらかじめ\(z\)をかけておく必要があり、結局Y座標を \(\tan\theta/2\)で割ればよい
- X座標はさらにアスペクト比で調整(\(y/x\)を乗じる)
- プログラム上では
Mat4.scale
で拡大率とアスペクト比を指定して、\(M_{43}\)に-1を、\(M_{44}\)に0を入れることで\(w=-z\)になるように仕向け、投影行列としている
Mat4.index
は指定された行列の成分を示す、配列のインデックスを返す
- カラムメジャーなので
Mat4.index(0, 1)
は4になり、Mat4.index(1, 0)
は1になる
- 下半分の回転は単純に
rotX
・rotY
するのではなく、ポインタの移動方向に転がすことで自然な動きにする
- つまり、ポインタの移動方向と垂直な直線を軸にする
- 一般的に座標軸以外を軸とした回転は簡単には表せないので、一度Z軸を中心として回転軸をX軸に合わせ、X軸のまわりに所望の量だけ回転させ、Z軸を中心として逆に回して元に戻す
- 回転中心を原点に保ちたいため、Z方向の移動と回転行列は別に保持
- そうしないとぐるぐる回すのではなく、ブンブン振り回すような動きになってしまう
- 現状、深度テストをしていないので、Z座標はいいかげん
球面座標
- 半径1の球に、緯線・経線を10度ごと
- 緯線は-80度から+80度まで17本ある
- 経線は極で10度進んだ経線に乗り換える形で一筆書き
- 軸キューブの大きさは0.707にしてあり、原点と反対側の頂点が球面とほぼ一致する
- モデルの中心位置は (0, 0, -1.5) 、つまり視点から少し奥に離れたところに置いてある
- 投影法は透視投影で、関数
persY0
にまとめてある
w
はビューポート幅、h
はビューポート高さで比率が重要
fovy
はY方向の視野角(Field of View)でラジアン単位
- Y方向の視野角での指定で、Zは0に設定されるので、
persY0
と名付けてみた
- ボールをちょっと離れたところから眺めた場合に相当
- 透視投影なので手前ほど大きく見え、極から同じ角度だけ離れた緯線(同心円)は手前に見える緯線の方が大きくなる
- もしこの球が不透明ならば、初期状態で赤道は見えない
- プロジェクション行列計算・ポインターイベント登録は関数にした
- プロジェクション行列はY方向の視野角版で、near・farは指定できない
- ポインターイベントのドラッグコールバックの引数に渡しているオブジェクトには、オリジナルのイベント、タップ位置からのX差分、Y差分、移動距離、移動方向が含まれる
- 座標は要素の
clientHeight
を1としてY軸は下がプラス、角度は右が0度で時計回りに増加するので注意
- X軸から右下30度に転がったとき、転がし軸はY軸から右上に30度の位置になり、この角度でそのまま
rotZ
するとGLでは反時計回りに30度回転し、転がし軸がGLのY軸に一致するので、rotY
してrotZ
で元に戻せばよい
平射図法
- もろもろの関数を外部ファイルに追い出してある
- モデルを少しこちらに近づけて、視点が球面上になった場合が平射図法(ステレオ図法)
- 実際には視点を横切る線がチラチラと表示されないように、ほんの少し球の内側に入った位置を視点にしている
- 視点を極に持ってきたのがポーラーステレオ図法で、天気図などに使われる
- 局所的な形状が正しく投影(正角)され、球面上の円は投影後も円になる
- 一般的には球面上の大円も円に投影されるので、地平線は画面中央を通らない限り円弧となって描画される
- 球面上で180度を超える範囲を描画することも可能
- この例ではY方向の視野角が90度に設定してあるが、円周角の原理から球面上ではその倍の180度が映っている
- 対角の視野角は90度より広いため、極を対角方向に持っていくと、反対側の極も同一画面に入る
- 球面上180度が地平線を円として投影されているので、上下いっぱいの円内に全天が投影された状態になっている
心射図法
- さらにモデルを近づけて、視点が球の中心になった場合が心射図法(そのまんま)
- 実際には軸キューブがきちんと表示されるように、ほんの少しだけ視点を手前にしてある
- カメラで普通に空を撮った場合に相当する
- 球面上の大円が直線になる
- 経線は常に直線
- どんな角度でも地平線が直線になる
- 球面上の最短距離が直線として投影される
- 球面上で180度に達する角度は描画できない
- 軸キューブが見づらい
- 本来は視点から3方向へ軸が生えているため、どんなにモデルを回しても軸は原点にある点としてしか描画されない
- ほんの少しだけ視点をずらせば見えるようになるが、目の前に折り紙の角を持ってきた状態なので非常に見づらい