9. 透視投影

【計算機序論2・実習(2012年度)】 目次, 計算機序論2, 授業科目, www.kameda-lab.org 2012/11/19a

正投影は概念としてはわかりやすいですが、描画されたものは往々にして直感と合いません。
ここでは、いよいよ透視投影を利用します。

参考リンク

OpenGL(ver.2.1)の各関数(小文字のglの2文字で始まる関数)の説明
OpenGLのProgramming Guide Book (ver1.1, 通称赤本)
GLUT - The OpenGL Utility Toolkit
The OpenGL Utility Toolkit (GLUT) Programming Interface API Version 3


09.01. 透視投影の導入(暫定)

ic2_SetUpCamera_Perspective()
ic2_SetUpCamera_Ortho()の代わりに、今度は透視投影を担当する関数を製作します。
新しい関数でかつ外部から使ってもらう予定の関数なので、プロトタイプ宣言も追加します。
ic2_SetUpCamera_Ortho()内部で注意すべき点は、glMatrixMode(GL_PROJECTION); で操作する行列をGL_PROJECTIONに変更することと、その直後に行列の初期化を行うことです。
(GL_MODELVIEW行列と同様に、放っておくと、OpenGLでは行列操作として、現在の行列に今回の行列を右から演算してしまいます。)

gluPerspective()
gluPerspective()は、透視投影行列を(比較的簡単に)定義するためのGLUライブラリ関数です。
まずは自分なりに適切な値を考えて、実装してみましょう。

【プログラム09-01】(灰色分は変更無し)

  1. ic2-CommonHeaders.h (08-01からの差分)
  2. 09-01-Projection.c (07-03からの差分)
  3. 09-01-Rendering.c (08-04からの差分)
  4. 07-03-EmbededObjects.c, 07-04-Initialization.c, 07-05-Callback.c, 07-05-MainFunction.c, 08-01-GLTools.c

「暫定」とついているところから予想がつくように、せっかく作製しても、何も描画されません。
次節で原因を考えます。
そのためにも、09-01-ex1は必ず自分で行うように。

演習
09-01-ex1: gluPerspective()関数の4引数の意味を、透視投影の概念をYZ平面上に図示してそこに記入する形で説明しなさい。


09.02. 透視投影の導入(正式)

カメラと物体との位置関係
09-01-ex1でも出てきますが、OpenGLの透視投影は、焦点がカメラ座標系の原点にあるという前提で定式化されています。
一方で、これまでのプログラムでは、描画は各軸 -1.0 から 1.0 までの範囲を想定していました。
つまり、想定している描画範囲とカメラ位置がずれてしまっています。
09.01.節の暫定プログラムでは、gluPerspective()でレンダリングされる最近距離と最遠距離との間(2.0から4.0までで2.0の幅を設けています)の範囲が想定描画範囲を含んでいません。
描画範囲とレンダリング範囲を合わせるためには、「カメラを後ろに下げる」必要があります。
ところが、OpenGLの構造上、投影の直前は常にカメラ中心は投影が実施されるカメラ座標系の原点にあることが仮定されているので、カメラ中心をカメラ座標系の中心から動かすことは認められていません。
(厳密には、自分でやりたければそういうことも可能ですが、その場合、投影行列を自分の手で与えなければいけません)
そこで、カメラを後ろに下げる代わりに、「(レンダリングする直前に)全ての対象をカメラから離す」ことを考えます。(09-02-ex1)
全ての移動・回転・スケーリング操作は相対的なので、これは実現可能です。(09-02-ex2)
実際には、OpenGLではカメラはZ軸負の方向を向いていますから、全ての対象のZ値を少しマイナス側に動かすことになります。

画角
Z軸に沿って描画範囲とレンダリング範囲が一致しても、それだけで思うようなCGになる保証はありません。
そこで、画角を調整して、X軸方向とY軸方向のみかけの大きさを調整します。
OpenGLのデフォルトでは、投影に用いられる投影面(画像平面)も、実際に表示に使われるウィンドウも正方形になります(アスペクト比1.0)。
想定描画範囲も各軸 -1.0 〜 1.0 ですから、カメラから見た想定描画範囲もほぼ正方形になります。
そのため、ここでは、垂直画角だけ考えて計算することにします。
- カメラ焦点(カメラ座標系原点)から投影面までの距離を3.0とします。
- OpenGLのデフォルトでは投影面の大きさは縦横とも -1.0 〜 1.0 です。
これらの拘束条件から、垂直画角は約36.87°になります。(09-02-ex3)

画角と焦点距離
今回のプログラムでは画角から透視投影行列に必要なパラメータを決定しましたが、通常はカメラの概念として、焦点距離(カメラ中心と投影面との距離)が使われます。
そのため、gluPerspective()を使いこなすには、焦点距離から垂直画角を求める式が必要になります。(09-02-ex5)

【プログラム09-02】(灰色分は変更無し)

  1. 09-02-Projection.c (09-01からの差分)
  2. 09-02-Rendering.c (09-01からの差分)
  3. 07-03-EmbededObjects.c, 07-04-Initialization.c, 07-05-Callback.c, 07-05-MainFunction.c, 08-01-GLTools.c, ic2-CommonHeaders.h

演習
09-02-ex1: 上記説明中、「(レンダリングする直前に)全ての対象をカメラから離す」ためには、実際の描画関数(glVertex3f())に対して、プログラムソース上どのような相対位置に移動関数を設置すべきか説明しなさい。
09-02-ex2: 移動・回転・スケーリング操作が相対的に実施可能である理由を、線形代数を用いて説明しなさい。
09-02-ex3: 上記の説明で、垂直画角が36.87°になる計算過程を説明しなさい。
09-02-ex4: 上記の説明で、カメラ中心から物体中心までの距離を10としたときの垂直画角を、計算過程を示しながら求めなさい。
09-02-ex5: 画像面が正方で縦横 -1.0 〜 1.0 の一定サイズであるときの、焦点距離fと垂直画角fovyとの関係を定式化しなさい。


09.03. 透視投影下での回転と移動

透視投影では奥行き感がでます。
実際に、様々な演習をしてそのことを実感してみましょう。
1例だけ実際に示しますので、後の演習は各自で実装してください。

Y軸回転
08-04-ex3と同様に、glRotatef()を使って、ロゴがY軸回りに(右側が最初に手前に来る形で)ゆっくり回転するプログラムを作成してみましょう。
注意すべきなのは、レンダリング範囲と想定描画範囲を合わせるための移動行列の存在です。
このために、08-04-ex2や08-04-ex3で利用したような簡易な実装は不可能になります。
回転の度合いを表す変数 roty を用意して、MODELVIEW行列の操作に臨みます。

様子がわかってきたと思うので、ついでに描画速度を更新します。

【プログラム09-02】(灰色分は変更無し)

  1. 09-03-Rendering.c (09-02からの差分)
  2. 09-03-Callback.c (07-05からの差分)
  3. 07-03-EmbededObjects.c, 07-04-Initialization.c, 07-05-MainFunction.c, 08-01-GLTools.c, 09-02-Projection.c, ic2-CommonHeaders.h

演習
09-03-ex1: Z軸方向(元の描画想定範囲で)-1.0から1.0までをロゴマークが往復し続けるようにプログラムを作製しなさい。
09-03-ex2: 09-03-ex1の往復の変位量をsin関数で表すようにプログラムを改造しなさい。
09-03-ex3: 移動と回転を伴ってロゴマークが周期運動をするようにプログラムを改造しなさい。


kameda[at]iit.tsukuba.ac.jp.