Reverse Reprojection に関するメモ

概要

  • 前のフレームのレンダリング結果を再利用する Temporal Reprojection の手法の 1 つである Reverse Reprojection に関するメモです.
  • 主にプログラマ向けの内容です.

参考文献

Temporal Reprojection とは ?

  • Temporal Reprojection とは, 今のフレームのレンダリングをする際に前のフレームのレンダリング結果(キャッシュ)をうまく再利用する方法です.
  • 具体的にここでの再利用とは, 今のフレーム上の点に該当する点が「過去のフレームのキャッシュ」に存在した場合にだけ行うことができます.
  • また以前の結果を再利用できた場合にはキャッシュヒット, 再利用できなかった場合にはキャッシュミスと言います.
  • 下図は左からパルテノン神殿, アニメーションしている女性キャラクター, アニメーションしている忍者のキャラクターです.
  • 図の緑の領域は前のフレームでも見えていた領域でキャッシュヒットが発生しているものです.
  • 一方で赤の領域は前のフレームで見えていなかったけど, 今のフレームで見えている領域でキャッシュミスが発生したものです.

  • キャッシュミスはカメラが動いたときや, モデルがアニメーションして見える表面上の点が変化した際に起きます.
  • 下図は上の 3 つの場合(パルテノン神殿, 女性キャラクター, 忍者のキャラクター)において, 前のフレームと今のフレームで見えていた表面上の点の割合の%を示したグラフです.
  • 横軸がアニメーションのフレームカウントで, 縦軸かキャッシュヒットの割合です. 下の例だと大体のフレームでキャッシュヒット率が 80 % を超えているので, 計算結果がかなり再利用できることがわかります.


Temporal Reprojection の種類

  • 今のフレームのレンダリングをする際に前のフレームのレンダリング結果(キャッシュ)をうまく再利用する Temporal Reprojection については以下の 2 種類ありますが, 今回は Reverse Reprojection のみ扱います.
    • Reverse Reprojection
      • 今のフレームのスクリーン座標を変換して, 前のフレームでのスクリーン座標を求めてキャッシュを参照する方法
    • Forward Reprojection
      • 今のフレームのキャッシュをモーションベクトルなどで変換して, 次のフレーム用のキャッシュに変換していきます.

Reverse Reprojection のアルゴリズム

  • 下図の左側は前フレーム(t-1)の結果で, 右側が今のフレーム(t)の結果です.
  • 今のフレーム(t)の点 p1 と p2 について, 前フレームの該当する点へと変換するための行列を乗算する処理(リバースリプロジェクション)である π(p)をそれぞれに適用した結果, 前フレーム(t-1)の該当する点がそれぞれ π_t-1(p1) と π_t-1(p2)であるとします.
  • その際にそれらの点の z 座標と前フレーム(t-1)のデプスの値がほぼ同じ場合, 前フレーム(t-1)の結果が再利用できるとみなして再利用します.
  • 今回の場合だと前フレームでは隠れていた p1 ではキャッシュミスが発生し, 前フレームでも見えていた p2 ではキャッシュヒットが発生することになります.


  • もう少し記号的に説明すると, 以下のようになります.
// スクリーン座標
(x,y)

// 時間 t の結果バッファ
f_t(x, y) 

 // 時間 t のデプスバッファ
d_t(x, y)

// スクリーン座標(x,y)を π_t-1でリバースリプロジェクションした結果,
// 該当する点が (x',y',z') になったとします.
(x',y',z') = π_t-1(x, y)

// リバースリプロジェクションで (x,y)に該当するデプスです
d_t-1(x',y') 

// リバースリプロジェクションで (x,y)に該当する結果バッファです
f_t-1(x',y')

// キャッシュヒットする場合
if ( z' == d_t-1(x',y') ) 
   f_t-1(x',y') を再利用
  • また今のフレームの座標から, カメラ行列や射影行列を使って 1 フレーム前の座標を求める場合(リバースリプロジェクション)は以下のような数式になります.
  • 今のクリップ座標から視点座標に変換してから, 1 フレーム前のカメラ行列と射影行列をかけて 1 フレーム前のクリップ座標を求めます.


Reverse Reprojection のパス

  • Reverse Reprojection の基本的な実装方法としては以下の 3 種類の方法があります.
1 パスの場合


    • 長所
      • 実装が単純です.
    • 短所
      • 動的分岐の効率が悪いです. (キャッシュヒット・ミスの領域に一貫性がないときや, 計算量のバランスが悪いとき)
      • MRT が必要です.
// 1 パス目

// キャッシュヒットした場合
if ( is_cache_hit )
{
   cache = getCache( cache_texture, cache_texcoord );
}
// キャッシュミスした場合
else
{
   cache = calcCache();
}

color = calcShade( cache );

// 結果を出力
gl_FragColor[ 0 ] = color;
// キャッシュを出力
gl_FragColor[ 1 ] = cache;
2 パスの場合


    • 長所
      • キャッシュを再計算する処理(calcCache())を動的分岐から 2 パス目に移動したので, 動的分岐部分のバランスが少し良くなりました.
      • 2 パス目での計算は Early-Z を利用して, キャッシュの再計算が必要なピクセルに対してだけ処理できます.
    • 短所
      • 1 パス目の動的分岐の効率が悪いです. (キャッシュの再計算の処理やシェーディングが重い場合)
      • MRT が必要です.
// 1 パス目

// キャッシュヒットした場合
if (  is_cache_hit )
{
   cache = getCache( cache_texture, cache_texcoord );
   color = calcShade( cache );

   // 結果を出力
   gl_FragColor[ 0 ] = color;
   // キャッシュを出力
   gl_FragColor[ 1 ] = cache;
}
// キャッシュミスした場合
else
{
   discard;
}
// 2 パス目
// 上のパスで discard されたピクセルだけ処理するように Early-Z を設定

cache = calcCache();
color = calcShade( cache );

// 結果を出力
gl_FragColor[ 0 ] = color;
// キャッシュを出力
gl_FragColor[ 1 ] = cache;
3 パスの場合

    • 長所
      • 1 パス目の動的分岐の処理が軽くなっていて, バランスがとれています.
      • MRT が不要です.
    • 短所
      • パスが増えます.
      • テクスチャの読み書きが増えています.
// 1 パス目

// キャッシュヒットした場合
if ( is_cache_hit )
{
   cache             = getCache( cache_texture, cache_texcoord );
   gl_FragColor[ 0 ] = cache;
}
// キャッシュミスした場合
else
{
   discard;
}

color = calcShade( cache );
// 2 パス目
// 上のパスで discard されたピクセルだけ処理するように Early-Z を設定

cache             = calcCache();
color             = calcShade( cache );
gl_FragColor[ 0 ] = cache;
// 3 パス目
cache             = getCache( cache_texture, cache_texcoord );
color             = calcShade( cache );
gl_FragColor[ 0 ] = color

キャッシュのリフレッシュの方法

  • キャッシュは複数のフレームに渡って再利用できますが, シェーディングやサンプリング時の誤差が蓄積するのですぐに古くなってきます.
  • 従って, キャッシュを定期的に更新することが提案されています.
  • 具体的にはグループを delta_n 個のグループに分けて, 時間 t においてグループ i に属するピクセルラウンドロビン(下の条件式を満たす)で更新します.

  • 各タイルをどうやってグループ i に分類するか ? については以下の 3 つの方式が紹介されていました.
    • スクリーンをタイルに分割する方式
    • スクリーンのピクセルに対してランダムにグループ id を割り当てる方式(但し, 各グループ id に属するピクセル数は同じ数にしておきます)
    • インターリーブ方式

キャッシュの更新方法

  • キャッシュについては下の数式(2)のように, 今回のサンプリング結果と以前のキャッシュ結果をαでブレンドして更新します.

  • f_t(p) : 今回のサンプリング結果と, キャッシュの両方を考慮した結果
  • α: ブレンド
  • s_t(p) : 今回の 1 回分のサンプリング結果
  • πt-1(p) : 今のフレームをリバースリプロジェクションしたクリップ座標
  • f_t-1(πt-1(p)): キャッシュした結果