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を使わずに、これと同等のレンダリングを達成するためには、ずっと多くの処理時間が必要となるでしょう。

RIS+ReSTIR
RIS

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の論文を参照)

可視状態の確認は、biasCorrectionModeRTXDI_BIAS_CORRECTION_RAY_TRACEDが設定された場合に実行されます。具体的には、選択されたLight Sampleの位置と、各Reservoirの位置を、過去のフレームのBVHでレイトレースして、可視状態を確認します。(ただし、サンプル内の実際の処理では、シーンがスタティックであると仮定して、単純に現在のフレームのBVHでレイトレースするように実装されています。)

次にFinazlieの処理についてです。RTXDI_SpatioTemporalResampling()関数の正規化部分では、pipiSumという変数が、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する際の係数が大きくなるように計算されています。

また、pipiSumの初期値は、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処理します。

まとめ

最後まで読んじゃった人は「にゃ~ん」ってつぶやいてほしいです。

shikihuiku
shikihuiku

リアルタイムレンダリングが好き

Related