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処理します。
まとめ
最後まで読んじゃった人は「にゃ~ん」ってつぶやいてほしいです。