床井研究室

レンズの画角

スクリーン上の表示領域を指定して視野錐台を設定する方法は、柔軟ですが、直感的に理解しやすいとは言えません。そこで、カメラの投影方法にならって透視投影の変換行列を決める方法が用いられます。現実のカメラではスクリーン (フィルムや撮像素子) の大きさが固定されてるので、スクリーンに投影される範囲は (near に相当する) レンズの焦点距離によって決まります。しかし、コンピュータでは決まった大きさのスクリーンを想定する必要がありませんから、スクリーンの大きさに依存しない方法としてレンズの画角 (field of view - fov ですが、ここでは fovy とします) を用います。

スクリーン上の表示領域

レンズの画角から視野錐台の left, right, bottom, top を決定します。aspect は表示領域の縦横比 (表示領域の高さに対する幅の割合) です。通常はビューポートの縦横比と一致させることが多いようです。

画角と前方面の関係

視線は表示領域の中心を通っているので、$\text{left} = -\text{right}, \text{bottom} = -\text{top}$ です。視点とスクリーン (前方面) との距離は zNear なので、top は次式で求めることができます。

\[\begin{aligned} \text{top} &= -\text{bottom} = \text{near} \tan\left(\frac{\mathit{fovy}}{2}\right) \\ \text{right} &= -\text{left} = \mathit{aspect}\,\text{top} \end{aligned}\]

これを視野錐台にもとづく透視投影変換行列に代入します。

\[\begin{aligned} \frac{2\,\text{near}}{\text{top} - \text{bottom}} &= \frac{\text{near}}{\text{top}} = \frac{1}{\tan\left(\frac{\mathit{fovy}}{2}\right)} = f \\ \frac{2\,\text{near}}{\text{right} - \text{left}} &= \frac{\text{near}}{\text{right}} = \frac{1}{\mathit{aspect}\tan\left(\frac{\mathit{fovy}}{2}\right)} = \frac{f}{\mathit{aspect}} \\ \frac{\text{right} + \text{left}}{\text{right} - \text{left}} &= \frac{\text{top} + \text{bottom}}{\text{top} - \text{bottom}} = 0 \end{aligned}\]

これにより、次のような透視投影変換行列が得られます。

\[\begin{pmatrix} \frac{f}{\mathit{aspect}} & 0 & 0 & 0 \\ 0 & f & 0 & 0 \\ 0 & 0 & -\frac{\text{far} + \text{near}}{\text{far} - \text{near}} & -\frac{2\,\text{far}\,\text{near}}{\text{far} - \text{near}} \\ 0 & 0 & -1 & 0 \end{pmatrix}\]

それでは、画角 fovy, 表示領域の縦横比 aspect, 前方面の位置 zNear, 後方面の位置 zFar を指定して透視投影変換行列を作成し、引数 matrix に与えられた配列に格納する関数 cameraMatrix() を作成してください。なお、画角が弧度法 (radian) だとわかりにくいので、fovy は度数法 (degree, 度) で指定できるようにしてください。

/*
** 画角から透視投影変換行列を求める
*/
void cameraMatrix(float fovy, float aspect,
                  float zNear, float zFar,
                  GLfloat* matrix)
{
  /* この部分を考えましょう */
}

この関数も orthogonalMatrix() と同じファイルに書いておきましょう。

画角を指定した透視投影

それでは、perspectiveMatrix() をこの cameraMatrix() に置き換えて、透視投影により図形を描いてください。まずメインプログラムの宣言部に cameraMatrix() の外部宣言を追加します。

...

/*
** 画角から透視投影変換行列を求める
*/
extern void cameraMatrix(float fovy, float aspect,
                         float zNear, float zFar,
                         GLfloat* matrix);

...

そして、初期化処理の init() において perspectiveMatrix() を呼び出していた部分を cameraMatrix() に置き換えます。fovy は 30 度、aspect は 1 としてください。また zNearzFar には以前の値 (7.0f と 11.0f) を使用してください。

...

  /* 透視投影変換行列を求める */
  GLfloat perspective[16];
  cameraMatrix(30.0f, 1.0f, 7.0f, 11.0f, perspective);

  /* ビュー変換行列を求める */
  GLfloat viewing[16];
  lookAt(4.0f, 5.0f, 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, viewing);

  /* ビュー変換行列と投影変換行列の積を projectionMatrix に入れる */
  multiplyMatrix(viewing, perspective, projectionMatrix);

  /* uniform 変数 projectionMatrix の場所を得る */
  projectionMatrixLocation = glGetUniformLocation(gl2Program, "projectionMatrix");

...

これで下のような図形が描かれれば OK です。

画角を指定して描画

時間が余ったら立方体を描いてみて。あと前回lookAt() が間違っていたから、これも修正しといてくんろ。