[Work/Class/OpenGL/1_OpenGL1x]

3Dグラフィックの座標系と変換・視点の移動

3Dグラフィックの座標系

OpenGLに限らず,基本的に3Dグラフィックは以下の5つの座標系を扱う.

  1. 一つのモデル・オブジェクトの中で定められた「原点」を基準とした「ローカル座標系」
  2. 世界全体を絶対的な座標で表現する「ワールド座標系」
  3. 世界やオブジェクトをカメラを原点とした相対座標で表現する「視点座標系」
  4. カメラで見た座標系(3次元)をスクリーン(2次元)に落とし込んだ「投影面座標系」
  5. 一つのウィンドウの中での位置をマラワス「ウィンドウ座標系」(ビューポート)

我々が2Dグラフィックで意識しているのは,ローカル座標系とワールド座標系のみであり,ワールド座標系がそのままウィンドウの座標系と一致しているが,3Dグラフィックの場合,次元を削減する(投影する,わかりやすく言うとパースの図面を書く)ことにより,2Dグラフィックに落とし込む.その際の変換が以下の4段階を経て行われる.

  1. モデリング変換(ローカル座標系→ワールド座標系)
  2. 視野変換・視界変換・ビューイング変換(ワールド座標系→視点座標系)
  3. 投影変換(視点座標系→投影面座標系)
  4. ビューポート変換(投影面座標系→ウィンドウ座標系)
モデル座標系→ワールド座標系→視点座標系→投影面座標系 投影面座標系→Viewport

モデリング変換

一つ前の項でやった通り,glTranslate()glRotate()を前に書かない状態でglutSolidTeapod()を出現させると,原点を中心にして現れる.この状態が「オブジェクトのローカル座標系」と「世界を絶対的な座標で表現するワールド座標系」が一致している状態である.

glTranslate()やglRotate()を使いオブジェクトを任意の場所に配置するが,2Dグラフィックスや前項で述べた通り,アフィン変換によって,「オブジェクトのローカル座標系」を「移動・回転・拡大縮小」させるために「ワールド座標系」に変換した状態である.

視野変換

モデリング変換が行われた段階では,世界(ワールド座標系)の中心を基準としてモデルが描かれるが,これを投影する前に,カメラを原点としてレンズの傾きなどカメラから見える状態の座標系に変換する.これが視野変換である.

本来なら視野変換もアフィン変換同様行列演算で行うが,OpenGL 1.x系ではこの部分はgluLookAt()を使いカメラの位置を指定することで,自動的に行われる.

投影変換

視野変換で得られた「カメラを原点とした座標系で表現された世界(これはまだ3次元)」を2次元に落とし込む変換である.いわゆる「パース図を描く」作業に相当する

OpenGL 1.x系では,この作業はglOrtho()(平行投影)もしくはgluPerspective()(透視投影)により,自動的に行われる.(透視投影はglFrustum()でも行えるが,gluPerspective()の方が扱いが簡単なので,こちらを用いる)

この投影変換も,本質的には行列演算で行われる.

ビューポート変換

「投影変換で作られた2次元画像のどこからどこまでを切り抜いて,ウィンドウのどこからどこまでに表示するか」がビューポート変換である.

投影変換がされた段階では,まだカメラから見た中心を原点として2次元座標(x,y)で表現されているが,コンピュータの画面に表示されるウィンドウは左上もしくは左下を原点としたピクセル数高さ幅の範囲無いの座標で表現される.この部分(2次元から2次元へ)がビューポート変換である.

OpenGL 1.x系では,glViewport()が自動的に行ってくれるし,何もしなくても勝手にウィンドウいっぱいに表示してくれるので,気にする必要はない.

OpenGL 1.x系の基本

glMatrixMode()関数に,モデリング変換と視野変換を行うことを指定するGL_MODELVIEW定数タグか,投影変換を行うことを指定するGL_PROJECTION定数タグを与えて,「今はどのモードの行列演算をしているかを指定する.

glMatrixMode(GL_MODELVIEW);の様に指定する.

OpenGL 1.x系ではこのレンダリング過程は固定されている(固定パイプラインと呼ばれる)ため,行列演算は自動化されており,関数を幾つか実行するのみで自動的に移動や透視変換の行列演算がなされる.

OpenGL 1.x系での手順

行列演算は以下のように指定する.

  1. glMatrixMode(GL_MODELVIEW もしくは GL_PROJECTION定数タグを指定);を実行.
  2. glLoadIdentity();を実行して,そのモードの最初の行列を単位行列に初期化する.
  3. 変換行列を生成する関数を実行する前にglPushMatrix();を実行し,変換行列を保存し始める.
  4. 変換行列を生成し掛け算をしていく関数(モデルビューの時はglRotated()glTranslatef())を実行する.
  5. 最後にglPopMatrix();で保存した変換行列を取り出す(つまりひとまとまりの行列演算を終了する).