Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

ReSTIRの数理と実装 (rtcamp10)

yumcyawiz
October 12, 2024

ReSTIRの数理と実装 (rtcamp10)

レイトレ合宿10 (https://sites.google.com/view/rtcamp10/home) のセミナーで使用したスライドです。ReSTIRの数理と実装の両面について全体的に俯瞰する内容となっています。

yumcyawiz

October 12, 2024
Tweet

More Decks by yumcyawiz

Other Decks in Technology

Transcript

  1. ReSTIRとは? Resampled Importance Sampling (RIS) 好きな分布からの重点的サンプリングを行う Spatio Temporal Resamping 前フレーム

    (Temporal), 周辺ピクセル (Spatial)の情報を活用 最近のリアルタイムレイトレの流行り ReSTIR DI + ReSTIR GI + Radiance Cache + Denoiser + Upscaler
  2. ReSTIRの流れ Generate Candidate Temporal Resampling Spatial Resampling Resolve Generate Candidate:

    光源サンプリング(DI), パスト レーシング(GI)によって を生成 Temporal resampling: 前フレームと現在フレームの サンプル集合からリサンプリング Spatial resampling: 現在フレームの周辺ピクセルの サンプル集合からリサンプリング Resolve: リサンプリング後の結果を使って放射輝 度を評価 (x ​ , L ​ ) i i
  3. Resampled Importance Sampling (RIS) 提案分布から生成した 個のサンプルの中から1つを選び直し(Resampling), 目標分布 (Target function) に従うようなサンプルを得る

    アルゴリズム 1. 提案分布 から 個のサンプル を生成 2. 各サンプルの重み を計算 3. 確率 に従って を選び直してサンプル を生成 M ​ (x) p ^ p(x) M (x ​ , x ​ , ⋯ , x ​ ) 1 2 M w ​ = i ​ ​ M 1 p(x ​ ) i ​ (x ​ ) p ^ i P ​ = i ​ ​ w ​ ∑i=1 M i w ​ i x ​ i y
  4. RISの性質 ならRISによって得られるサンプルは目標分布に 分布収束 (Convergence in law) する サンプル列 に自己相関が出てくる 極端なケースだと同じサンプルが選ばれ続ける

    RISから得られたサンプルを入力とする統計的な推定において問題が起こる可能性 (後述) lim ​ V[ ​ w ​ ] → M→∞ ∑i=1 M i 0 (y ​ , y ​ , ⋯ ) 1 2
  5. Weighted Reservoir Sampling (WRS) RISを逐次的なアルゴリズムで置き換えたもの Reservoirという構造体に現在のサンプルだけを保存しておく アルゴリズム 1. 新たなサンプル とそのp.d.f

    が与えられる 2. 重み を計算 3. Reservoirのこれまでの重みの総和を更新: 4. サンプル数も更新: 5. の一様乱数 が ならReservoirのサンプルを で置き換える struct Reservoir { ReservoirSample x; // サンプル float w_sum; // 重みの総和 float c; // サンプル数 float ucw; // Unbiased Contribution Weight }; x ​ next p(x ) next w ​ = next ​ ​ M 1 p(x ​ ) next ​ (x ​ ) p ^ next w ​ + sum = w ​ next c += 1 [0, 1) u u < ​ w ​ sum w ​ next x ​ next
  6. 実装例 void updateReservoir(inout Reservoir reservoir, vec3 hit_position, vec3 hit_normal, vec3

    radiance, float weight, float rv0) { reservoir.w_sum += weight; reservoir.c += 1; if (rv0 < weight / reservoir.w_sum) { reservoir.hit_position = hit_position; reservoir.hit_normal = hit_normal; reservoir.radiance = radiance; } }
  7. Unbiased Contribution Weight (UCW) モンテカルロ積分を行うためにはRISによって得られたサンプルのp.d.fが必要 問題: どうやって計算? p.d.fは厳密に計算出来る必要はなく, ある確率変数 が以下を満たせば良い

    を Unbiased Contribution Weight (UCW) という 上の式は と等価 RISのUCWは で与えられる W ​ X E f(X)W = [ X] ​ f(x)dx ∫ Ω W ​ X E[W ​ ∣X] = X ​ p ​ (X) X 1 W ​ = X ​ ​ (X) p ^ ​ w ​ ∑i=1 M i
  8. RISの性質2 かつ, ある について を満たすなら 特に となっている場合 この式から言えること は の良い近似になった方が分散が減る

    が出来るだけ変動しないようにすることが分散を減らすために重要 lim ​ V[ ​ w ​ ] → M→∞ ∑i=1 M i 0 C ​ > f 0 0 ≤ f ≤ C ​ ​ f p ^ V[f(X)W ​ ] ≤ X V ​ + [ ​ (X) p ^ f(X) ] C ​ ​ ​ ∥ ​ ∥ + 2 ​ ​ f 2 V ​ w ​ [ i=1 ∑ M i] p ^ 2 V ​ w ​ [ i=1 ∑ M i] f(x) = C ​ ​ (x) f p ^ V[f(X)W ​ ] = X C ​ V ​ w ​ f 2 [ i=1 ∑ M i] ​ p ^ f w ​ i
  9. Reservoirのmerge Reservoir 1とReservoir 2を用いてWRSすることで, あたかもReservoir 1とReservoir 2の両方のサンプル集 合からRISしたようなサンプルを得ることが出来る ReSTIRにおいて最も重要な部分 アルゴリズム

    : Reservoir 1, Reservoir 2のサンプル : Reservoir 1, Reservoir 2のサンプル数 Reservoir 1, Reservoir 2のUCW 1. 重み を計算 2. Reservoir 1の重みの総和を更新: 3. Reservoir 1のサンプル数を更新: 4. の一様乱数 が ならReservoir 1のサンプルをReservoir 2で置き換える x ​ , x ​ 1 2 c ​ , c ​ 1 2 W , W ​ X ​ 1 X ​ 2 w ​ = 2 ​ ​ (x ​ )W ​ c ​ +c ​ 1 2 c ​ 2 p ^ 2 X ​ 2 w ​ + sum = w ​ 2 c ​ + 1 = c ​ 2 [0, 1) u u < ​ w ​ sum w ​ 2
  10. 実装例 void mergeReservoir(inout Reservoir reservoir0, Reservoir reservoir1, float weight, float

    rv0) { reservoir0.w_sum += weight; reservoir0.c += reservoir1.c; if (rv0 < weight / reservoir0.w_sum) { reservoir0.hit_position = reservoir1.hit_position; reservoir0.hit_normal = reservoir1.hit_normal; reservoir0.radiance = reservoir1.radiance; } }
  11. MIS Weight RISにおいても Multiple Importance Sampling (MIS) を考えることが出来る MIS weight

    を用いて重みの計算を と置き換える. Constant MIS: Balance Heuristics: Generalized Balance Heuristics m ​ (x) i w ​ (x ) = i i m ​ (x ​ ) ​ (x )W ​ i i p ^ i X ​ i m ​ (x ) = i i ​ ​ c ​ ∑j=1 K j c ​ i m ​ (x ) = i i ​ ​ c ​ p ​ (x ​ ) ∑j=1 K j j i c ​ p ​ (x ​ ) i i i m ​ (x ) = i i ​ ​ c ​ ​ ​ (x ​ ) ∑j=1 K j p ^j i c ​ ​ ​ (x ​ ) ip ^i i
  12. 実装例 // M-capping { previous_reservoir.c = min(previous_reservoir.c, g_ConfidenceCap * g_CandidateSamples);

    } // temporal resampling { const float p_12 = evaluateTargetFunction( origin_position, origin_normal, previous_reservoir.hit_position, previous_reservoir.hit_normal, previous_reservoir.radiance); // rejection heuristics previous_reservoir.c *= candidate_score; const float w = p_12 * previous_reservoir.ucw * previous_reservoir.c; mergeReservoir(reservoir, previous_reservoir, w, sample1D(rng)); }
  13. 実装例 // spatial resampling for (int i = 0; i

    < g_SpatialSamples; ++i) { // sample neighbor pixel vec2 gaussian = sample2DGaussian(sample2D(rng)); ivec2 neighbor_pixel = ivec2(pixel + g_SpatialRadius / 1.96 * gaussian); if (neighbor_pixel.x < 0 || neighbor_pixel.y < 0 || neighbor_pixel.x >= g_Extent.x || neighbor_pixel.y >= g_Extent.y) { // out of bounds continue; } Reservoir neighbor_reservoir = g_PreviousReservoirs[neighbor_pixel.y * g_Extent.x + neighbor_pixel.x]; const float p_12 = evaluateTargetFunction( origin_position, origin_normal, neighbor_reservoir.hit_position, neighbor_reservoir.hit_normal, neighbor_reservoir.radiance);
  14. if (g_UseRejectionHeuristics != 0) { neighbor_reservoir.c *= rejectionHeuristics( origin_position, origin_normal,

    neighbor_hit_info.position, neighbor_hit_info.normal); } if (g_UseJacobianRejectionHeuristics != 0) { neighbor_reservoir.c *= reconnectionShiftJacobian( neighbor_hit_info.position, neighbor_reservoir.hit_position, neighbor_reservoir.hit_normal, origin_position) > g_JacobianRejectionHeuristicsThreshold ? 0.0 : 1.0; } const float w = p_12 * neighbor_reservoir.ucw * neighbor_reservoir.c; mergeReservoir(reservoir, neighbor_reservoir, w, sample1D(rng)); }
  15. 実装例 float depthHeuristics(float depth, float previous_depth, float sigma) { const

    float diff = (depth - previous_depth) * (depth - previous_depth) / depth; return clamp(exp(-sigma * diff), 0.0, 1.0); } float normalHeuristics(vec3 normal, vec3 previous_normal, float sigma) { return clamp(pow(max(dot(normal, previous_normal), 0.0), sigma), 0.0, 1.0); } float rejectionHeuristics(vec3 p0, vec3 n0, vec3 p1, vec3 n1) { float weight = 1.0; weight *= depthHeuristics(distance(g_Eye, p0), distance(g_Eye, p1), g_DepthRejectionHeuristicsSigma); weight *= normalHeuristics(n0, n1, g_NormalRejectionHeuristicsSigma); return weight; }
  16. Visibility Reuse Target functionがVisibilityを含んでいないと影のノイズが取れない 一方でVisibilityを毎回評価するのはパフォーマンス的に避けたい そこでTemporal resampling後だけにVisibilityを一回評価し、Visibility = 0ならUCW =

    0とする 次のSpatial resampling, Temporal resamplingで再利用されなくなる 影のノイズ減少 (代わりに暗くなるBiasが増える) Visibility Reuse Off Visibility Reuse On
  17. 実装例 // visibility reuse if (g_UseVisibilityReuse != 0) { const

    float V = checkVisibility(origin_position, first_hit_info.geometricNormal, reservoir.hit_position); reservoir.ucw *= V; }
  18. ReSTIR DI 1. 光源サンプリングによって光源上の点サンプルを 個生成 2. RISによって1個のサンプルを選択 3. Temporal Resampling

    4. Spatial Resampling 5. Reservoirに含まれているサンプルを利用して を評価 PT ReSTIR DI M f × G × L ​ × i V
  19. ReSTIR GI 1. First hit pointの位置からBRDF samplingによってSecond hit pointを生成 2.

    Second hit pointからFirst hit pointに向かう放射輝度をパストレーシングで計算 3. Temporal Resampling 4. Spatial Resampling 5. Reservoirに含まれているサンプルを利用して を評価 PT ReSTIR GI f × G × L ​ × i V
  20. 最後に ReSTIRを正しく実装するには理論的な理解はとても重要 間違った式を使うとBiasが発生する BiasはTemporal Resampling, Spatial Resamplingで容易に周辺ピクセルに伝播してしまう Visibility reuse, その他近似によって発生するBiasを適切にコントロールする必要

    常にパストレーシングによるリファレンスとの比較を行うようにする 設定出来る項目が多いので, GUIで簡単に比較出来るようにしたほうが良い 例: 初期サンプルの数, Spatial resamplingのサンプル数, 半径, Rejection heuristicなど まだまだ色々と研究の余地はありそう