パスワードを忘れた? アカウント作成
418613 journal

j3259の日記: ボケの表現

日記 by j3259

コンピュータグラフィックスがどうしても嘘っぽく見える理由の一つにすごく手前のものも遠くのものもハッキリとシャープに見えるっていうのがあると思う。実際に人の目やカメラで撮った写真の場合は近すぎたり遠すぎるものはボケて見える。

グラフィックスは普通はピンホールカメラをモデルとしてて薄レンズモデルを使わなきゃならんと師に言われた。確かにピンホールカメラモデルというのはグラフィックスで最初に習うものだけど忘れてしまっていた。ボケるっていうのが像が複数に見えてるっていうのは分かってたけど。

遠くのものがボケて見えることを depth of field(dof; 被写界深度) 効果といって、写真の世界では普通に使われてる言葉ですね。正確には被写界深度っていうのは「被写体前後の範囲の中でピントが合っている幅」のこと。「depth of field opengl」 で検索すると色々コードが出てきました。

簡単そうなのがsgi だけど、グラフィックカードに依存して無さそうなので、おそらく遅い。像が複数に見えるからって何回もレンダリングしたら負け。基本的なアイディアが分かるストレートな実装。

      min = -2;
      max = -min + 1;
      count = -2 * min + 1;
      count *= count;
 
      scale = 2.f;
 
      glClear(GL_ACCUM_BUFFER_BIT);
 
      for(j = min; j < max; j++) {
        for(i = min; i < max; i++) {
          dx = scale * i * FRUSTNEAR/focus;
          dy = scale * j * FRUSTNEAR/focus;
          glMatrixMode(GL_PROJECTION);
          glLoadIdentity();
          glFrustum(-FRUSTDIM + dx,
                    FRUSTDIM + dx,
                    -FRUSTDIM + dy,
                    FRUSTDIM + dy,
                    FRUSTNEAR,
                    FRUSTFAR);
          glMatrixMode(GL_MODELVIEW);
          glLoadIdentity();
          glTranslatef(scale * i, scale * j, 0.f);
          render();
          glAccum(GL_ACCUM, 1.f/count);
        }
      }
      glAccum(GL_RETURN, 1.f);

次に注目するのがShene のDepth of Field Implementation with OpenGL (pdf)というペーパー。肝は page 8。

1. OpenGL ルーチンを使ってフレームバッファにシーンをレンダする。バッファをスワップする前に以下の手順を加える。
2. 2D のラスタ座標で作業するために、2D 正射影(orthogonal projection)に切り替える。
3. OpenGL の glReadPixels を使って色バッファをローカルバッファの fb1、深度バッファをローカルバッファの db に読み込む。

glReadPixel(0, 0, w, h, GL_RGB, GL_FLOAT, fbl);
glReadPixel(0, 0, w, h, GL_DEPTH_COMPONENT, GL_FLOAT, db);

4. db の各ピクセルの正規化された深度値(z)をオブジェクトスペース値(Z)に変換する。
Z := Znear * Zfar / (Zfar - z * (Zfar - Znear);
このステップは CoC計算がオブジェクトスペースを元とするため欠かすことができない。ピクセルの深度値はピクセルとレンズの単位距離となるべきだ。
5. 二次バッファ fb2 を初期化する。新たらしい画像は光をここに積み上げるによって生成される。
6. 各ピクセルに対し CoC計算を実行し一様分布関数(uniform distribution function)を掛ける。

For each scan line,
  for each pixel(x, y) on the scan line
    PixelDepth := db[x, y]
    CoC diameter := abs(Aperture - Coefficient * (ScreenPos * (1.0 / FocalLen - 1.0/PixelDepth) -1))
    UniformDistribution(CoC diameter, x, y, fb1, fb2)

7. OpenGL の glDrawPixels を使って現在のラスタを新しい fb2 にて置き換える。

glDrawPixels(w, h, GL_RGB, GL_FLOAT, fb2);

CoC というのは錯乱円(circle of confusion) 「ピントが合っていてもいなくても点光源からの光がフィルム面上で結ぶ像は円形となり、この円のこと」。

レンダリングは一回だけど、やっぱりリアルタイムに使うには遅いらしい。

リアルタイムで頑張ってるのはATI の Riguer の Real-Time Depth of Field Simulation とか、Cant の New Anti-Aliasing and Depth of Field Techniques for Gamesとか。nDIVIA のスライドも参考になる事が書いてある。
自分で計算しなくてもグラフィックカードに似た機能があるだろうと。つまり、ミップマップを使えということになる。サンプル9Linear Bi-Linear Tri-LinearWikipedia mipmap参照。
Mulder の Fast Perception-Based Depth of Field Rendering も参考になる。

テクスチャでごまかせ派、シェーダで頑張れ派、レイヤー分けしてフィルタを使え派がいるっぽい。
フィルターの欠点は nDIVIA のスライドにある通り、ピクセル漏れが起こる事。二次元の画像に後付けでフィルタを掛けるわけだから、遠くの物と近くの物の境界線において色が混ざることになる。
あらかじめボケたテクスチャを計算してそれでごまかす欠点はテクスチャはボケてもポリゴンの線そのものはボケないってことじゃないかと思う。ダンジョンなんかの室内だと直線が多いだろうし、距離も長くないから気にならないはず。
で、一番使えそうなのが、レイヤー分けして遠景をボカして、真ん中は普通にレンダーして、近景もボカして最後に組み合わせるっていう方法。さらに周辺視野もボカすと効果的。オブジェクトスペースを三分割もしくは四分割するだけだからピクセルごとに深度を読むより速いはずだし境界線でのピクセル漏れも起きないはず。これを実践してるのがMulder のこの画像

dof は三次元の話だけど、四次元でのボカしの話がモーションブラーということらしい。ここも参考になる。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

私はプログラマです。1040 formに私の職業としてそう書いています -- Ken Thompson

読み込み中...