OpenGLプログラミング/Depth of Field

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動
Normal.
Depth-of-field, focus on glass.
Depth-of-field, focus on pool.
Depth-of-field, focus at infinity.

はじめに[編集]

実生活では、レンズを使うカメラは、原理的に、見ているすべてのもの一度に焦点を合わせることはできません。 カメラ(焦点距離)から一定の距離の近くのオブジェクトだけがシャープに表示されます。 この領域は、 depth of fieldと呼ばれています。 よりカメラの近くか遠くにあるオブジェクトは、シャープでなく表示されます。 焦点距離にないオブジェクトがどのくらいアンシャープになるかは、カメラの絞りの形状や大きさに依存します。 通常、GPUはすべてを限りなくシャープにレンダリングしますが、しかし被写界深度の効果をシミュレートするための様々なテクニックがあります。 最も正確なのは、アキュムレーションバッファーを使用することで、また実装も非常に容易です。 基本的には、わずかに異なるMVP行列でシーンを複数回レンダリングすることで、レンズ絞りの異なる部分を通過した光線をシミュレートします。 レンズ diaphragmによって形成される開口部の形状は、オブジェクトが焦点から外れたときのぼやけ方に影響を与えます。 これは bokehと呼ばれています。

Simulating depth of field using the accumulation buffer[編集]

次のコードを使用して model-view-projection 行列を設定するところから始め、その後フレームをレンダリングしていこうと思います:

glm::mat4 modelview = glm::lookAt(eye, object, up);
glm::mat4 projection = glm::perspective(...);
glm::mat4 mvp = projection * modelview;
glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, glm::value_ptr(mvp));
draw_scene();
glSwapBuffers();

ここで eyeは目やカメラの位置を含むベクトルで、 objectは私たちが中心とフォーカスにしたいオブジェクトの位置で、 upはどの方向が上かを記述するベクトルです。 円形の開口部をシミュレートするために、見ている方向に垂直な平面の円のなかでカメラを移動させます。 平面を記述する2つのベクトルは、クロス積を使用することで簡単に得ることができます。

カメラをあちこちに動かすだけで、見ている方向を変更しない場合には、シーンのほとんどがぼやけることになります。 しかし、それに対するトリックがあり、私たちが中心にしてフォーカスさせたいオブジェクトを直接見ておくというものです。 これは簡単にでき、そのオブジェクトの座標を glm::lookAt()の2番目のパラメータとして渡す必要があります。 見ている物体がいつでもきっかり画面の中央になり、あちこちにに動かなくなるので、それがぼやけることがなくなります。 残りは私たちのセンターのオブジェクトとの相対的な深さによってぼかされます。

int n = 10; // number of light rays
float aperture = 0.05;
glm::mat4 projection = glm::perspective(...);

glm::vec3 right = glm::normalize(glm::cross(object - eye, up));
glm::vec3 p_up = glm::normalize(glm::cross(object - eye, right));

for(int i = 0; i < n; i++) {
  glm::vec3 bokeh = glm::vec3(right * cosf(i * 2 * M_PI / n), p_up * sinf(i * 2 * M_PI / n), 0);
  glm::mat4 modelview = glm::lookAt(eye + aperture * bokeh, object, p_up);
  glm::mat4 mvp = projection * modelview;
  glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, glm::value_ptr(mvp));
  draw_scene();
  glAccum(i ? GL_ACCUM : GL_LOAD, 1.0 / n);
}

glAccum(GL_RETURN, 1);
glSwapBuffers();

エクササイズ[編集]

  • 前のいずれかの glm::lookAt()を使用するチュートリアルに、この手法を適用してみましょう。
  • napertureの値を変更してみましょう。
  • ほとんどのカメラ diaphragmsは本当は円形ではなく、多角形です。 正方形または六角形のdiaphraghmをシミュレートしてみてください。
  • この手法をアンチエイリアシングおよび/またはモーションブラーと効率的に組み合わせることはできるでしょうか?