2021年12月25日土曜日

Godot4 に実装されるグローバルイルミネーションについて調べてみる

Godot4 に実装されるグローバルイルミネーションについて調べてみる

このページは レイトレーシング(レイトレ) Advent Calendar 2021 の14日目の記事です。

はじめに

Godot は2D及び3Dゲームを製作できるクロスプラットフォームなゲームエンジンです。 Godot はオープンソースであり MIT ライセンスの下で開発・配布されており無料で利用することができます。 現在は Godot3 が最新のメジャーバージョンですが、既に次期メジャーバージョンである Godot4 の計画は発表されており開発も進んでいます。 Godot4 ではレンダリングバックエンドとして新しく Vulkan サポートが追加されるなど様々なアップデートが予定されており (マイルストーン)、 その中の一つにリアルタイムグローバルイルミネーション (以降 GI と表記) の追加があります。 本記事ではそのリアルタイムGIの実装である Signed Distance Field Global Illumination (以降は SDFGI と表記) について簡単に紹介します。
最新の Godot4 の実装は github の master ブランチで見ることができます。 また、この記事は commit e53e357 の時点での Godot4 を使用して書いています。

Signed Distance Field について

Signed Distance Field (以降 SDF と表記) はスカラー場で、ここでは空間上のある点からシーン内の最も近いジオメトリの表面までの距離を表します。また、 SDF が正の値の時は自分がジオメトリの外側にいる、負の値の時はジオメトリの内側に位置していることを意味します。
レイキャスト時はこの SDF を用いてスフィアトレーシングを行います。下の図はレイがカメラ (点 p0p_0) から出て表面 (点 p4p_4) にヒットするまでを表しています。始めに、レイは開始地点である点 p0p_0 の SDF の値を見ます。 SDF が示している距離の間には何も無いことが判明しているのでレイは点 p1p_1 まで進む事ができます。これを繰り返す事で表面にヒットする点 p4p_4 (点から表面までの距離が閾値以下) までレイを進めます。

SDFGI

SDFGI は名前の通り SDF を利用した GI 実装です。Godot4 の SDFGI 実装はまだ完成ではないそうですが機能としては既に利用できます。また、Godot公式サイトの記事でデモを見ることができます。以下は SDFGI を無効/有効にした時のレンダリング画像です。
SDFGI無効 SDFGI有効
このシーンでは 道路や建物がディレクショナルライトで照らされていますが SDFGI を有効にした場合は影になっている部分も間接光によって明るくなっているのがわかります。
以下の画像はもう少し単純なシーンで SDFGI を試した例です。このシーンでは箱の中を天井にあるエリアライトで照らしています。真ん中に立っている小さな箱の側面を見ると GI 効果 (Color Bleeding) が出ているのがわかります (ただし現在エリアライトからの影はうまく出ないようです) 。
CornellBox
SDFGI は RayTracing Core を使っていないので比較的古い GPU でも動きます。そして、古い GPU でもパフォーマンスが出て程良いクオリティの GI レンダリングができるように設計されています。

ソースコード中では、SDFGI 関連の実装は C++側は servers/rendering/renderer_rd/ 内の renderer_scene_render_rd.cpprenderer_scene_gi_rd.cppforward_clustered/render_forward_clustered.cpp に記述されており、Shaderコードは servers/rendering/renderer_rd/shaders/ 内に glsl で実装されています。 Godot4 でレンダリングを開始すると renderer_scene_render_rd.cpp 内の RendererSceneRenderRD::render_scene() が実行されているのでこの関数から見ていきます。
残念ながら私が実装の詳細まで理解することができなかったため、ここでは処理の概要のみ説明します

SDFGI に関して render_scene() 内の処理は大まかに以下のようになっています、

  1. SDF の構築
  2. ライトプローブの更新
  3. 各ピクセルの GI の計算

まずは SDF の構築を行い、シーン上に一定間隔毎で設置された各ライトプローブ上の放射照度を計算します。 最後に各ピクセルから見える点の GI を周囲にあるライトプローブから計算します。

SDF の構築

シーン中の空間をグリッドに区切り各セルの SDF を計算します。Godot4 のデフォルトでは 128 x 128 x 128 のグリッドに区切っています。 各セルはまず自分のセル内にジオメトリがあるか確認します。この時オリジナルのジオメトリでは処理が重たくなってしまうので、下図のように簡易化されたジオメトリを用いています。

ジオメトリが見つかった場合はその表面までの距離をセルに格納します。 ジオメトリが見つからなかった場合は、次はセルの周囲にある別のセルを参照します。周囲のセルのどれかにジオメトリがある (SDFの値がセルに格納されている) 場合は自身のセルからジオメトリの表面までの距離をセルに格納します。周囲のセルを参照する処理を何度か繰り返す事でシーン全体の SDF の構築を完成させます。

ライトプローブの更新

ここでは GI を計算するために、シーン中の各ライトプローブ上の放射照度を計算します。ライトプローブは下の画像のようにシーン中に一定間隔で配置されています。

このプローブ上からランダムな方向にレイを飛ばし当たった光源からの放射輝度を計算します。この時のレイキャストには SDF を用いたスフィアトレーシングを行います。デフォルトの設定では 1 プローブ当たり 16 本のレイを飛ばしています。また、ディレクショナルライトがある場合はその方向にもレイを飛ばし、ディレクショナルライトからの放射輝度も計算します。
次に間接照明に関しては、今度は周囲にあるライトプローブを参照し、自身のライトプローブ方向への放射輝度を計算します。

各ピクセルの GI の計算

ライトプローブの更新が終了したら、次は各ピクセルからの GI を計算します。ピクセルから見える頂点をデプスバッファから計算します。頂点を見つけたら、ライトプローブで間接照明を計算したときと同様に頂点の周囲にあるライトプローブから頂点への放射輝度を計算します。

終わりに

本記事では Godot4 に実装された SDFGI について調べたことを簡単にまとめました。 ここで紹介した内容は SDFGI の概要程度で、実際の実装では色々な高速化が施されていますが私が解説できるほどの理解ができなかったためスキップしています。また、似た手法として SDFDDGI が発表されているのでこちらを読んでみるのも良いかもしれません。 法線の八面体へのエンコード (codepaper) やRGB9995エンコード (code) など手軽に自分のレンダラーでも使えそうな圧縮手法もあったので、ここにコードへのリンクだけ貼っておきます。
大規模なソースコードから使われている技術を読み解いていくのは慣れていない事ですが、自分の知らない技術が使われているのを見つけられるので今後も他のレンダラーのコードも読んでいこうと思います。

参考文献

  1. Amazon Lumberyard, Amazon Lumberyard Bistro, Open Research Content Archive (ORCA), url=http://developer.nvidia.com/orca/amazon-lumberyard-bistro
  2. Jinkai H, Milo K. Y, Guillermo E. A, Shihao G, Xiangjun T, Xiaogang J Efficient real-time dynamic diffuse global illumination using signed distance fields (2021)

2020年12月20日日曜日

Resampled Importance Sampling & Weighted Reservoir Sampling

RIS and WRS

Resampled Importance Sampling & Weighted Reservoir Sampling

このページは レイトレ Advent Calendar 2020 の20日目の記事です。

Spatiotemporal Reservoir Resampling for Real-Time Ray Tracing with Dynamic Direct Lighting を読んでみて Resampled Importance SamplingWeighted Reservoir Sampling が面白いと思ったので少しまとめてみます。
Resampled Importance Sampling は通常の Importance Sampling では用いられない pdf に従ったサンプリングも行うことができます。また、 Resampled Importance Sampling は通常の Importance Sampling よりも計算に必要なメモリ容量が多くなるので、 Weighted Reservoir Sampling を用いて計算に必要なメモリ容量を削減します。

Importance Sampling

Direct Lighting では、ある点 yy から方向 ω\omega への反射輝度 LL は次のような積分で表されます、

L(y,ω)=Aρ(y,xyω)Le(xy)G(xy)V(xy)dAx(1) L \left( y, \omega \right) = \int_A \rho \left( y, \overrightarrow{xy} \leftrightarrow \omega \right) L_e \left( x \rightarrow y \right) G \left( x \leftrightarrow y \right) V \left( x \leftrightarrow y \right) dA_x \tag{1}

ρ\rho は BSDF 、 LeL_e は光源の放射輝度、 GG は幾何項、 VV は可視度項になります。簡単のために yyω\omega の表記を省略すると式 (1) は、

L=Af(x)dx,wheref(x)ρ(x)Le(x)G(x)V(x)(2) L = \int_A f \left( x \right) dx, \qquad where \quad f(x) \equiv \rho \left( x \right ) L_e \left( x \right) G \left( x \right) V \left( x \right) \tag{2}

と表せます。

(2) の積分は分析的に解くことは難しいため、通常 Monte Carlo 法を用いて解析的に解きます。 Monte Carlo 法は確率的手法であるため分散によるノイズが発生します。 分散を低減するために Importance Sampling (以降 IS と表記) がよく用いられます。
IS では NN 個のサンプル xix_i を pdf p(xi)p \left( x_i \right) に従ってサンプリングします。その推定量は以下のようになります、

<L>isN=1Ni=1Nf(xi)p(xi)L(3) \left< L \right>_{is}^{N} = \frac{1}{N} \sum_{i=1}^{N} \frac{f \left( x_i \right)}{p \left( x_i \right)} \approx L \tag{3}

ISf(x)0f \left( x \right) \neq 0 となる xx に対して常に p(x)>0p \left( x \right) \gt 0 となる場合に unbiased となります。 また、 p(x)p \left( x \right)f(x)f \left( x \right) に対して相関が強いほど分散を低減することができます。
IS を用いるためには pdf で定義される分布に従ってサンプルを生成できる必要があります。一般的にサンプリングの方法として inverse cumulative distribution function (逆CDF)rejection sampling が用いられます。

Resampled Importance Sampling

Direct Lighting を IS を用いて計算する場合は ρLeGV\rho \cdot L_e \cdot G \cdot V に比例した pdf に従ってサンプリングするのが理想ですが、このような pdf は closed-form で表現できず複雑なため 逆CDF によるサンプリングは難しくなります。
Resampled Importance Sampling (以降 RIS と表記) はこのような複雑な pdf を用いて IS を行うことができます。 RIS では2つの pdf を用います。1つはソース pdf ppff との相関は強くないが 逆CDF ができる pdf です (例えば pLep \propto L_e など)。もう1つは ターゲット pdf p^\hat{p}ff との相関は強いが正規化が難しく 逆CDF ができない pdf です (例えば p^ρLeG\hat{p} \propto \rho \cdot L_e \cdot G など)。

先ず、 MM 個 (M1M \ge 1) の候補サンプル x={x1,,xM}\boldsymbol{x} = \left\{ x_1, \ldots, x_M \right\} を ソース pdf pp に従ってサンプリングします。その候補サンプルの中から zz 番目、 xzx_z (z{1,,M}z \in \left\{1, \ldots, M \right\}) を重み w(x)=p^(x)p(x)w \left( x \right) = \frac{\hat{p} \left( x \right)}{p \left( x \right)} に従ってランダムに選びます。候補の中から xzx_z が選択される確率は、

p(zx)=w(xz)i=1Mw(xi)(4) p \left( z | \boldsymbol{x} \right) = \frac{w \left( x_z \right)}{\sum_{i=1}^{M} w \left( x_i \right)} \tag{4}

となります。上記の手順でサンプリングされた xzx_z はターゲット pdf p^\hat{p} に近似した分布になります。この xzx_z を使った推定量は以下のようになります、

<L>risN,M=1Ni=1N(f(xiz)p^(xiz)(1Mj=1Mw(xij)))(5) \left< L \right>_{ris}^{N,M} = \frac{1}{N} \sum_{i=1}^{N} \left( \frac{f \left( x_{iz} \right)}{\hat{p} \left( x_{iz} \right)} \cdot \left( \frac{1}{M} \sum_{j=1}^{M} w \left( x_{ij} \right) \right) \right) \tag{5}

(5) の直感的な理解は、この推定量は pdf p^(xz)\hat{p} \left( x_z \right) に従って xzx_z をサンプリングしているように振る舞いますが、 実際には p^\hat{p} は正規化されておらず xzx_zp^\hat{p} では無く p^\hat{p} に近似した分布に従ってサンプリングされているため、 1Mj=1Mw(xj)\frac{1}{M} \sum_{j=1}^{M} w \left( x_j \right) の項でその補正をかけている形になります。

(5)M=1M=1 の時は pp を使って IS しているのと同じになり、 M=M=\infty の時は p^\hat{p} を使って IS しているのと同じになります。 MM が大きくなるほど p^\hat{p} に近い分布で IS できますが計算量は多くなります。
以下の図は[1]からの引用ですが、ソース pdf pp を一様分布、ターゲット pdf p^\hat{p}p^cos(θ)+sin4(6θ)\hat{p} \propto cos \left( \theta \right) + sin^4 \left( 6 \theta \right) として MM を変化させた時の xzx_z の分布を表しています、

RISM,N1M,N \ge 1 かつ f(x)0f \left( x \right) \neq 0 となる xx に対して常に p(x)>0,p^(x)>0p \left( x \right) \gt 0, \hat{p} \left( x \right) \gt 0 となる場合に unbiased となります。

Weighted Reservoir Sampling

RIS を行う際に候補サンプルを一度に全て保持しようとすると MM 個分の候補サンプルのメモリ容量が必要となります。 Weighted Reservoir Sampling (以降 WRS と表記) を RIS と組み合わせることで MM 個の候補の中から nn 個を選択する場合 n(+1)n (+1) 個分のメモリ消費のみで行うことができます。ここでは n=1n=1 とした場合、つまり候補サンプルの中から1個を選択する場合について説明します。
WRS は事前に候補数 MM を知っておく必要は無く、 {x1,x2,,xM}\left\{ x_1, x_2, \ldots, x_M \right\} を全て保持しておく必要もありません。例えば候補サンプルが x1x_1 から順にストリーミング入力されて総数 MM がわからない場合でも 1 個を選択することができます。

WRS では候補サンプルを x1x_1 から順に処理していき、 resevoir と呼ぶ入れ物 (ここでは候補サンプル 1 個分のメモリ容量) に確率的に挿入 (既に他の候補が入っている場合は交換)していきます。 x1x_1 から処理を始め mm 番目の xmx_m までの処理を行っている時、 xix_ireservoir に入っている確率は w(xi)j=1mw(xj)\frac{w \left( x_i \right)}{\sum_{j=1}^{m} w \left( x_j \right)} です。ここで用いる w(x)w \left( x \right)RIS で用いる重みと同じものです。次の xm+1x_{m+1} の処理では、reservoir に入っているものと以下の確率で交換します、

w(xm+1)j=1m+1w(xj)(6) \frac{w \left( x_{m+1} \right)}{\sum_{j=1}^{m+1} w \left( x_j \right)} \tag{6}

(6) から逆に xm+1x_{m+1}reservoir に入らず xix_i が 残っている確率は、

w(xi)j=1mw(xj)(1w(xm+1)j=1m+1w(xj))=w(xi)j=1m+1w(xj)(7) \frac{w \left( x_i \right)}{\sum_{j=1}^{m} w \left( x_j \right)} \left( 1 - \frac{w \left( x_{m+1} \right)}{\sum_{j=1}^{m+1} w \left( x_j \right)} \right) = \frac{w \left( x_i \right)}{\sum_{j=1}^{m+1} w \left( x_j \right)} \tag{7}

となります。m+1=Mm+1=M まで処理が終了した段階で xix_ireservoir に入っている確率は、

w(xi)j=1Mw(xj)(8) \frac{w \left( x_i \right)}{\sum_{j=1}^{M} w \left( x_j \right)} \tag{8}

となり式 (4) と同じ形となります。このことから RISWRS を組み合わせても xzx_z の分布を変えずにサンプリングできることがわかります。また、必要なメモリ容量を reservoir 分と重みの和 j=1mw(xj)\sum_{j=1}^{m} w \left( x_j \right) を保持する分のみに削減できます。

終わりに

RISWRS について調べましたが実はまだ試していません。以下の事についてまだ疑問が解決していないのでもう少し調べてみようと思っています。

  1. RIS + WRS では乱数が多く必要になるが必要な個数を削減する方法はあるか
  2. RIS と他のサンプリング戦略の Multiple Importance Sampling (MIS) を行う際の RIS 戦略の pdf を決定論的に計算する方法はあるか

1 については、通常の IS逆CDF を用いてサンプリングする際に乱数を 1 個使うのに対し、 RIS + WRS では候補サンプルのサンプリングや reservoir への確率的交換で多くの乱数を必要とします。何か必要な乱数の数を減らす方法はないでしょうか。
2 については、式 (5) から RIS で用いる pdf を pris(xz)=p^(xz)1Mj=1Mw(xj)p_{ris} \left( x_z \right) = \frac{\hat{p} \left( x_{z} \right)}{\frac{1}{M} \sum_{j=1}^{M} w \left( x_{j} \right)} とすると、例えば MIS 時に xzx_z から pris(xz)p_{ris} \left( x_z \right) を評価する際に j=1Mw(xj)\sum_{j=1}^{M} w \left( x_{j} \right) を計算するために乱数を用いて他の候補をサンプルをサンプリングする事になると思います。もっと決定論的に prisp_{ris} を評価する方法はないでしょうか。

参考文献

  1. Talbot J, Cline D, Egbert P: Importance Resampling for Global Illumination (2005)
  2. Efraimidis PS, Spirakis PG: Weighted Random Sampling with a Reservoir (2006)
  3. Efraimidis PS: Weighted Random Sampling over Data Streams (2015)
  4. Bitterli B, Wyman C, Pharr M, Shirley P, Lefohn A, Jarosz W: Spatiotemporal Reservoir Resampling for Real-Time Ray Tracing with Dynamic Direct Lighting (2020)