graphics.hatenablog.com

技術系テクニカルアーティストのあれこれ

The Reyes Image Rendering Architecture

The Reyes Image Rendering Architecture
Robert L. Cook, Loren Carpenter, Edwin Catmull
http://dl.acm.org/citation.cfm?id=37414

 すべてのジオメトリを「マイクロポリゴン」に分割してシェーディングするシステムで、プログラマブルシェーダとかZ Bufferとか、わりと最近のゲーム系に近いかんじ。こないだSIGGRAPHいってきて、教養として一度ぐらいは真面目に論文読んでおくべきかなと思って。。
 ダイシング周りの話が結構おもしろかった。

 このブログではおそらくもう取り上げないけど、この論文以降の REYES についてはこちら


1. Introduction

 Reyes が目指すのは、細かくはいろいろあるけど、基本的には「映画とかのモーションピクチャーと区別が付かないこと」と「現実世界と同程度の複雑な見た目を扱うこと」のふたつ。既存の技術(統計的サンプリング、分散レイトレ、shade trees、アンチエイリアス付きデプスシャドウなど)を組み合わせて、ひとつのシステムに仕立て上げる。

ゴールは以下の7つ。

  • Model complexity
    • 十分にリッチなシェイプとテクスチャ
  • Model diversity
    • プロシージャルモデル、フラクタル、graphtal、パーティクルなどのプリミティブ
  • Shading complexity
  • Minimal ray tracing
    • GIは基本的にはテクスチャマップで表現するが、レイトレやラジオシティでも実現できるように
  • Speed
    • 「1年で2時間の映画」
  • Image Quality
    • ジャギーやモアレの軽減
  • Flexibility


2. Design Principles
f:id:hal1932:20140824181623j:plain
 これを実現するためのREYESの設計の原則は以下の8つ。

  • Natural coordinates
    • 無駄な射影変換をなくして効率をよくする
    • テクスチャはサーフェスローカルの、ビジビリティはスクリーンの、それぞれ「自然な」座標系で計算する
  • Vectorization
    • 並列計算やCPUのパイプラインを活かす
    • 似たような計算は一箇所にまとめる
  • Common representation
    • すべてのジオメトリは「マイクロポリゴン」で表現される
    • シェーディングやビジビリティも全部マイクロポリゴン単位
  • Locality
    • 普通のレイトレだとレイを飛ばす度にモデルやテクスチャを読み込むから、ページングやスラッシングのコストが馬鹿にならない
    • 一度に読み込むジオメトリを最低個数に抑える
    • テクスチャはそれぞれ1度だけ読み込む
  • Linearity
  • Large models
    • 扱えるプリミティブの個数を無制限に
  • Back door
    • 外部プログラムをロードして一部特殊なレンダリングをできるように
    • 必ずしも効率は求めない
  • Texture maps
    • 全体の計算量を減らす
    • GIの代わりに環境マップを使ったり、複雑なモデルの代わりにディスプレースメントマップを使ったり

サンプリング
 サーフェスをサンプリングするときは、ポイントサンプリングだとジャギーが出るからモンテカルロ法を利用する統計的サンプリングを使う。ノイズはでるけどジャギーよりマシ。サンプル点はジッタリングで決める。

Zバッファ
 オーバードローを減らすためにZバッファは重要。back door の結果をマージするときもZバッファを使う。サンプリングしてあったデプスと back door(レイトレやラジオシティ)の結果から返ってきたを比較。

マイクロポリゴン
 サンプリング定理を考慮して、マイクロポリゴンは 0.5px サイズの、フラットシェーディングされる四角形とする。プリミティブをマイクロポリゴンに分割する操作をダイシング dicing と呼び、ダイシングは uv 座標系で行われる。ダイシング後のマイクロポリゴンの2次元配列をグリッド grid と呼ぶ。
 グリッド内のマイクロポリゴンは、ビジビリティ判定よりも先に、まとめてシェーディングされる。カメラ方向を向いてるポリゴンはすべてシェーディングされるが、これには以下の理由がある。

  • Vectorizable shading
    • グリッドごとにまとめて並列計算できる
  • Texture locality
    • ジオメトリが「ぶつ切り」にならないからロードするテクスチャを切り替えなくて済む
  • Texture filtering
    • 各種マップのピクセルがマイクロポリゴンに紐付いてるから扱いやすい(後述)
  • Subdivision coherence
    • すべてのサーフェスについて分割は一度だけ行われるから、前方差分法とかの効率がいい方法でパッチ分割ができる
  • Clipping
  • Displacement maps
    • ディスプレースメントするとマイクロポリゴンの位置が変わるから、ビジビリティ計算の結果も変わる
  • No perspective
    • スクリーン補間前にシェーディングすれば perspective distortion を気にしないで済む

Texture Locality

  • CAT, Coherent Access Textures: (サーフェスローカルの)UV座標と(テクスチャローカルの)SV座標が1対1対応する
  • RAT, Random Access Textures: UVとSVが1対1対応しない

 ページングやスラッシングのことを考えると、テクスチャを如何にCATアクセスにするかがパフォーマンスの鍵になる。そのために、マイクロポリゴンの頂点には 1/2 の階乗単位でUVを割り付けておいて、テクスチャのほうは1マイクロポリゴン1テクセルになるような resolution pyramid(ミップマップ的なもの)を構成しておく。こうするとランタイムでのテクスチャフィルタリングも必要ないし、余計な座標変換もいらないから効率がいい
f:id:hal1932:20140824181317j:plain
 環境マップやデカールなんかは、仕方ないから RAT アクセスする。


3. Description of the Algorithm

ダイシングとスプリット

  • 「十分少ない数」のマイクロポリゴンをに分割できるときにダイシングする
    • そうでないとき(プリミティブが大きすぎるとき)はスプリットする
  • プリミティブをスプリットすると、小さな複数のプリミティブに分割される
    • ダイシングできる大きさになるまでスプリットし続ける

f:id:hal1932:20140824185025j:plain

ε-plane

  • カメラ平面に十分近い位置に ε-plane を設置する
    • ε-plane をまたぐプリミティブは、スプリット可能であればスプリットする

 最終的に、ε-plane とニアプレーンを同時にまたぐプリミティブが存在しない状態にしたい。この状態で「ニアプレーンより手前にあるジオメトリ」をカリングすると、クリッピングが不要になる。
 というか、ニアプレーンに沿ってクリッピングすると、ニアプレーンをまたぐジオメトリのサンプリング結果が破綻する。とはいえ、できるだけ余計なシェーディングは最小限に抑えたい。これらを両立するために、「ニアプレーンをまたぐ最小サイズのグリッド」のぶんだけカメラ側にはみだしてカリングされるようにする。(グリッド単位でクリッピングしているとも言える)

f:id:hal1932:20140824185041j:plain

render_target.initialize()

foreach( backet in render_target ) {
  z_buffer.initialize()
  
  foreach( primitive in model ) {
    primitive.load_from(model_file)
    if( primitive.is_boundable() ) {
      prim_eye = eye_space.bound(primitive)
      prim_near = prim_eye.z
      prim_far = prim_eye.z + prim_eye .depth
      if( prim_far < z_near || z_far < prim_near ) {
        continue
      }
      if( (e_plane <= prim_near && prim_far <= e_plane) && primitive.is_splittable() ) {
        primitive.set_dicable(false)
      } else {
        prim_screen = screen_space.bound(primitive)
        if( !prim_screen.intersect(view_frustum) ) {
          continue
        }
      }
    }// if( primitive.is_boundable() ) {
  
    if( primitive.is_dicable() ) {
      grid = primitive.dice()
      foreach( micropolygon in grid ) {
        micropolygon.calc_normal_and_tangent()
      }
  
      grid.shade(shader)
  
      foreach( micropolygon in grid ) {
        mpoly_eye = eye_space.bound(micropolygon)
        if( mpoly_eye.z< z_near || z_far < (mpoly_eye.z + mpoly_eye.depth)) {
          continue
        }
  
        mpoly_screen = screen_space.bound(micropolygon)
        foreach( sample in generate_samples() ) {
          sampled = sample.intersect(mpoly_screen)
          if( !sampled  ) {
            continue
          }
          z = sampled.interpolate().z
          if( z < z_buffer.get(sampled.pos) ) {
            z_buffer.set(sampled.pos, z)
            backet.accumulate(sampled)
          }
        }
      }
    } else { // if( primitive.is_dicable() ) {
      model.push_back(primitive.split())
    }
  
  }// foreach( primitive in model ) {
  
}// foreach( backet in render_target ) {

back_door.execute(render_target)

render_target.produce_pixels()
render_target.write(output_file)


4. Extensions

 モーションブラー、DoF、CSG(ブーリアン)、シャドウとかは、back door のところで Z バッファを参照しながら行う。例えば、モーションブラーは時間軸上でマイクロポリゴンをジッタリングする。DoF はレンズに応じてマイクロポリゴンの XY をずらす。


5. Implementation

 スクリーン全体を一気にレンダリングしようとすると、ジオメトリとZバッファ用にメモリが大量に必要になる。それを防ぐために、スクリーンをバケット(タイル)に分割して、バケット単位でレンダリングするようにした。そうすると、マイクロポリゴンの格納領域とZバッファは、バケットサイズの分だけで済む。


6. Discussion

 サンプリング前にシェーディングしてしまうと、どうしても正しいモーションブラーができない。サンプリング後にシェーディングする方法については将来の課題としたい。
 ダイシングはスキャンコンバータによって行われるが、プリミティブの種類(blobとかパーティクルとか)によっては綺麗にダイシングできないものがある。任意形状のポリゴンも扱いが難しいけど、そもそも普段はほとんどバイキュービックパッチしか使ってないから別にそんな困ってない。
 ただ、REYESのこの方式だと一切の逆計算 inverse calculations が必要ないのがいいところ。ピクセルの角をパッチの座標系に射影して法線がおかしくなったりとかしないし、クリッピングも考えなくて済む。並列化もしやすいし、テクスチャ使うときにスラッシングを起こしにくい。ジオメトリは全部マイクロポリゴンになるからその後の計算もシンプルになる。「複雑なシーンを素早くレンダリングする」という目的においては、おおよそのトレードオフは綺麗にとれてるはずだ。