RTXDIのminimal-sampleを理解する(2)
 
    
  前提知識として、 RTXDIのminimal-sampleをSpatioTemporal Resamplingなしの場合の動作について理解する必要があります。
RISとReSTIR
minimal-sampleは、まず初めに、現在レンダリングしているフレーム内でLight SampleとBRDF SampleをMISで結合したRservoirを生成します。この時点でもReservoirの結合を行いますが、基本的には Resampled Importance Sampling: RISのアルゴリズムに基づいて最適なライトパスの選択が行われます。
加えて、“Enable Resampling"チェックボックスを有効にした場合は、現在のフレームで生成したReservoirと、前のフレームで生成されたReservoirを結合することで、さらに良質なライトパスの選択を行うことが出来ます。この処理を、Reservoir-based SpatioTemporal Importance Resampling: ReSTIRと呼びます。
ReSTIRの効果
端的にReSTIRの効果の有無を比較すると以下のようになります。ReSTIRの処理が追加されるので処理負荷は大きくなりますが、もしもReSTIRを使わずに、これと同等のレンダリングを達成するためには、ずっと多くの処理時間が必要となるでしょう。
  
  
  
 
  
  
   
  
  
  
RTXDI_SpatioTemporalResampling()の処理
RTXDI SDKのReSTIRの処理は、RTXDI_SpatioTemporalResampling()関数で行われます。minimal-sampleではRender.hlslから呼ばれています。引数には、以下の情報を渡します。返り値として、ReSTIRで結合されたReservoirが返されます。
reservoir = RTXDI_SpatioTemporalResampling(pixelPosition, primary.surface, reservoir, rng, stparams, params, temporalSamplePixelPos, lightSample);
- pixelPosition- 現在処理をしているPixelの位置
 
- surface- 現在処理をしているPixelのサーフェース情報(位置, 法線 tec..)
 
- curSample- 現在のフレームで生成したReservoir
 
- rng- 乱数生成用のステート
 
- stparams- Spatio Temporal Resamplingの処理に関するのパラメーター(後述)
 
- params- RTXDI SDKの定数パラメーター(バッファのオフセット情報など)
 
- temporalSamplePixelPos- Backprojectionに成功した場合は、そのピクセル位置が格納されます。失敗すれば(-1,-1)が格納されます。
 
- selectedLightSample- Spatio Temporal Resamplingの処理でReservoirの選択サンプルが更新された場合は、このライトサンプルの情報が更新されます。
 
RTXDI_SpatioTemporalResamplingParameters 構造体
RTXDI_SpatioTemporalResampling()関数を呼び出す際の引数にあるこの構造体には、 ReSTIRの制御に関する様々なパラメーターが格納されています。
- screenSpaceMotion- 現在処理しているピクセルのモーションベクトルです
 
- sourceBufferIndex- Reservoirバッファのフレームごとの参照オフセットを計算するためのインデックスです。
 
- maxHistoryLength- 結合されたReservoirのウエイトの上限を決めます。この値が大きいほど、過去に多数のReservoirと結合されたサンプルのウエイトが高くなります。
- また、逆を言えば、シーンの変化への追従が悪くなります。
 
- biasCorrectionMode- Reservoir結合時のBiasの補正方法です。
- RTXDI_BIAS_CORRECTION_OFF
- Biasの補正をしない結合方法を使います。処理は一番軽いですが、レンダリング結果にBiasを導入します。
 
- RTXDI_BIAS_CORRECTION_BASIC
- TargetPDFを再計算してBiasを補正しますが基本的に結合されたReservoirはすべて有効であると仮定します。異なる場合はBiasが導入されます。
 
- RTXDI_BIAS_CORRECTION_PAIRWISE
- pairwise MISという方法でBiasを補正します。基本的に結合されたReservoirはすべて有効であると仮定します。異なる場合はBiasが導入されます。今回の記事では説明しません。
 
- RTXDI_BIAS_CORRECTION_RAY_TRACED
- TargetPDFを再計算してBiasを補正したうえで、レイトレースを行い結合されたReservoirが有効かどうかをチェックします。基本的にBiasを導入しない方法です。
 
 
- depthThreshold, normalThreshold- Backprojectionをしたときに、法線とデプスの相似度をチェックする際の閾値です。
 
- numSamples- 結合を試みるReservoirの数です。最低1必要で、最初の一つは、TemporalResamplingとなります。
 
- numDisocclusionBoostSamples- Backprojectionに失敗した場合に、SpartialSampleの数を増やす場合のサンプル数です
 
- samplingRadius- SpartialSampleのサンプリング半径(ピクセル単位)です。
 
- enableVisibilityShortcut- RTXDI_BIAS_CORRECTION_RAY_TRACEDの時のみ有効です。
- Reservoir結合後に、Visibilityテストを行う際にTemporalSampleが選択された場合はVisibilityテストをスキップします。
- (ここのIfの判定は不明。おそらくだが、選択されたサンプルのReservoirのVisibilityテストをスキップするのが正しいと思う。(なぜならそれは前フレームで行ったから。))
 
 
- enablePermutationSampling- BackprojectionとSpartialSamplingの位置にに小さいオフセットを適用します。
 
Backprojectionの処理
まず、前のフレームのReservoirと結合するためには、Backprojectionの処理を行う必要があります。この処理自体は、通常我々が行っているものと違いはありません。モーションベクトルを基に、過去フレームのサンプル位置を算出し、その近傍で、法線やDepthの類似性が高いサンプルを探します。
Temporal Sampleの読み出し
Backprojectionが成功したら、Reservoirバッファより、前フレームのPixel位置に対応するReservoirを読み出してprevSampleに格納します。読み出されるのは前のフレームに保存されたReservoirの情報になります。読み出したReservoirに対して以下の処理を行います。
- MをhistoryLimitでクランプ
- spartialDistanceにピクセルオフセットを加算
- ageをインクリメント
- lightIDを現在のフレームのライトバッファに対応するIDに変換- lightIDの変換では、もし該当するライトが、現在のフレームになければ読み出したReservoirを破棄します。
 
Temporal Sampleの結合
prevSampleが有効なReservoirだった場合は、現在のフレームで生成されたReservoirと結合します。
まず、prevSampleのReservoirの情報を基に、Light Sampleを構築します。ここで構築されるLight Sampleは、読み出し時に、ligtIDを変換したので、現在のフレームにおける光源サンプルの位置になります。そして、現在処理をしているSurfaceとそのLight Sampleで、targetPDFを計算します。(つまりシェーディングの計算をします。)この値は、前のフレームのReservoirを結合する際の、ウエイトの補正に使います。
結合の計算の詳細:
まず、prevSampleはFinalizeされて格納されているので、そのメンバー変数weightSumは意味的には、(1/targetPDF * 1/M * weightSum)の値になっています。
そして、prevSampleのRIS Weightは、プログラム上では(RTXDI_CombineReservoirs()呼び出しの引数のtargetPdf) * (Reservoirのメンバー変数のweightSum) * (Reservoirのメンバー変数のM)で計算されます。
これを意味的に解釈すると(引数のtargetPDF)/(元のtargetPDF) * weightSum となります。
つまり本来の意味でのweightSumに、新旧のtargetPDFの比を乗算したものがRISWeightとして使われることになります。
結合後は、MとRISWeightはそれぞれ結合先のReservoirに加算され、乱数によるサンプルの選択が行われることで結合が完了します。
Spartial Sampleの読み出しと結合
minimal-sampleにおいて、Spartial SampleはTemporal Sampleと同様に、前フレームのGBufferとReservoirバッファから読み出されます。
テストするサンプル数は、デバッグUI上のSpartial Sampleのスライダーで調整できます。Temporal Sampleとの主な違いは、Backprojectionした位置から、さらに、NeighborOffsetBufferから取得した値でオフセットを適用するところにあります。NeighborOffsetBufferはSDK側からその内容があらかじめ提供されている静的なバッファで、Spartial Sampleのサンプリングパターンが格納されています。読み出したサンプルは、Normal, Dpethそして、GbuffのMaterialの相似度をみて、サンプルが有効かを判定します。
有効な場合は、Temporal Sampleの場合と同様にReservoirを結合行います。結合の計算は、Temporal Sampleの場合と同じです。
Biasの補正とReservoirのFinalize処理
隣接ピクセルや、過去のフレームのReservoirとの結合はBiasを発生させることがあります。例えば、異なるピクセルで生成した複数のReservoirを結合した場合、個々のピクセルの積分範囲(法線を中心とした半球)は異なるため、もし、結合後に選択したLight Sampleが、結合された、とあるReservoirの積分範囲の外であったり、不可視な状態だったなら、このReservoirからMの値を、Fianlize処理するときの分母に含めてはいけません。そうしないと、Biasが発生してしまいます。(詳しくはReSTIRの論文を参照)
可視状態の確認は、biasCorrectionModeにRTXDI_BIAS_CORRECTION_RAY_TRACEDが設定された場合に実行されます。具体的には、選択されたLight Sampleの位置と、各Reservoirの位置を、過去のフレームのBVHでレイトレースして、可視状態を確認します。(ただし、サンプル内の実際の処理では、シーンがスタティックであると仮定して、単純に現在のフレームのBVHでレイトレースするように実装されています。)
次にFinazlieの処理についてです。RTXDI_SpatioTemporalResampling()関数の正規化部分では、piとpiSumという変数が、Finalizeする際の係数の分子と分母になるように記述されています。もしも、ここがもっと単純な記述だったら、piは常に1で、piSumには、選択したLight SampleがそのReservoirの積分範囲内で、かつ可視状態の、有効なReservoirのMのみを加算することで、Finalize処理の係数を算出する形になります。(ReSTIR論文における1/Zに相当)
しかし、RTXDIでは、MISのように正規化の係数を計算しています。具体的には、選択されたLight Sampleと各Reservoirの位置で、targetPDFを計算し、可視状態ならば(targetPDF*M)という値を分母側のpiSumに蓄積しています。分子側のpiは選択されたLight Sampleを保持していたReservoirとのtargetPDFです。つまり、選択されたLight Sampleを保持していたReservoirのtargetPDF(つまりはシェーディングの輝度)が相対的に他のReservoirと計算した輝度よりも高ければ、Finalizeする際の係数が大きくなるように計算されています。
また、piとpiSumの初期値は、Temporal SampleやSpatial Sampleとの結合前の、現在のフレームで計算されたReservoirの値を設定します(curSample.M)。これのMISのウエイトとしてstate.targetPdfを使っています。これは、現在選択されているLight Sampleと、現在処理中のサーフェースで計算されたtargetPDFで、この値は、他のTemporal SampleやSpatial Sampleのために計算するtargetPdfに対応する値です。(この値は、現在のフレームのデータで計算しています。他のTemporal SampleやSpatial SampleのReservoirのtargetPdfは前フレームのデータで計算しているという点は異なります。)
ループ処理が完了すれば、結合されたすべてのReservoirのBiasの除外のチェックが完了したことになります。そして、piには選択されたサンプルのtargetPDFが格納され、piSumにはtargetPDF*Mの総和が格納されています。 pi/piSumを正規化係数としてFinalize処理を行うことで、Biasの補正をした結合ができます。
Biasの補正をしない場合のReservoirのFinalize処理
Biasの補正をしない場合は、単純に結合されたReservoirを1/Mを正規化係数として、Finalize処理します。
まとめ
最後まで読んじゃった人は「にゃ~ん」ってつぶやいてほしいです。
