物理ベースレンダリング入門2(第6回)
レンダリング方程式の数値計算法について

このページではレンダリング方程式を数値計算する方法の一つであるモンテカルロ積分について説明する。

レンダリング方程式の数値解法

レンダリング方程式を再掲する。

Lo(x,ωo)=Le(x,ωo)+Ωf(x,ωi,ωo)Li(x,ωi)(ωin)dωiL_o(x, \vec{\omega_o}) = L_e(x, \vec{\omega_o}) + \int_{\Omega} f(x, \vec{\omega_i}, \vec{\omega_o})L_i(x, \vec{\omega_i})(\vec{\omega_i}\cdot\vec{n})d\vec{\omega_i}

積分の項は数値積分を用いて計算することができる。Li(x,ωi)L_i(x, \vec{\omega_i})の部分は再びレンダリング方程式で計算できるから、この積分は実際のところ超高次元積分である(経路積分による表現)。しかし通常の数値積分で超高次元積分を計算しようとすると、評価点の数が爆発的に多くなり計算が不可能になる。(次元の呪い)

次元の呪い
次元の呪い

そこで評価点を乱数を用いてランダムに発生させ、その点で値を評価した結果を足し合わせていく方法がある。これが モンテカルロ積分(Monte Carlo Integration) である。

モンテカルロ積分

以下のような積分を評価することを考える。

I=abf(x)dxI = \int_a^b f(x)dx

サンプリング点{x1,x2,,xN}\{ x_1, x_2, \dots, x_N \}[a,b][a, b]から一様サンプリングして生成する。この時サンプリング点の確率密度関数はp(xi)=1bap(x_i) = \frac{1}{b - a}である。すると

I1Ni=1Nf(xi)p(xi)=baNi=1Nf(xi)I \approx \frac{1}{N}\sum_{i = 1}^N \frac{f(x_i)}{p(x_i)} = \frac{b - a}{N}\sum_{i = 1}^N f(x_i)

で計算することができる。

モンテカルロ積分の期待値

E[1Ni=1Nf(xi)p(xi)]=1Ni=1NE[f(x)p(x)]=1Ni=1Nf(x)p(x)p(x)dx=f(x)dx\begin{aligned} E[\frac{1}{N}\sum_{i = 1}^N\frac{f(x_i)}{p(x_i)}] &= \frac{1}{N}\sum_{i = 1}^N E[\frac{f(x)}{p(x)}] \\ &= \frac{1}{N}\sum_{i=1}^N\int \frac{f(x)}{p(x)}p(x)dx \\ &= \int f(x)dx \end{aligned}

期待値が積分と一致することが分かる。

モンテカルロ積分の分散

V[1Ni=1Nf(xi)p(xi)]=1N2i=1NV[f(x)p(x)]=1N2i=1N(f(x)p(x)I)2p(x)dx=1N(f(x)p(x)I)2p(x)dx\begin{aligned} V[\frac{1}{N}\sum_{i=1}^N\frac{f(x_i)}{p(x_i)}] &= \frac{1}{N^2}\sum_{i=1}^N V[\frac{f(x)}{p(x)}] \\ &= \frac{1}{N^2}\sum_{i=1}^N\int (\frac{f(x)}{p(x)} - I)^2p(x)dx \\ &= \frac{1}{N}\int (\frac{f(x)}{p(x)} - I)^2p(x)dx \end{aligned}

分散はO(N)O(N)で減少することが分かる。したがって標準偏差はO(N)O(\sqrt{N})で減少する。これから言えるのは誤差を12\frac{1}{2}にするには4倍のサンプリング数が必要であるということ。このようにモンテカルロ積分は収束が遅いという問題点がある。

モンテカルロ積分の収束の様子
モンテカルロ積分の収束の様子
これはπ\piの値をモンテカルロ積分を用いて推定した結果のグラフである。サンプル数が2000未満のところでは推定結果が大きくぶれているのが分かる。10000サンプル付近では収束しているように見える。

モンテカルロ積分によるレンダリング方程式の数値解法

レンダリング方程式は無限次元積分になるが、モンテカルロ積分を使うことで数値計算することができる。

Lo(x,ωo)=Le(x,ωo)+Ωf(x,ωi,ωo)Li(x,ωi)(ωin)dωiL_o(x, \vec{\omega_o}) = L_e(x, \vec{\omega_o}) + \int_{\Omega} f(x, \vec{\omega_i}, \vec{\omega_o})L_i(x, \vec{\omega_i})(\vec{\omega_i}\cdot\vec{n})d\vec{\omega_i}

これをモンテカルロ積分の形式に直す。{ω1,ω2,,ωN}\{\vec{\omega_1}, \vec{\omega_2}, \dots, \vec{\omega_N} \}を確率密度関数p(ωi)p(\vec{\omega_i})からサンプリングされた方向とすると

Lo(x,ωo)Le(x,ωo)+1Ni=1Nf(x,ωi,ωo)Li(x,ωi)(ωin)p(ωi)L_o(x, \vec{\omega_o}) \approx L_e(x, \vec{\omega_o}) + \frac{1}{N}\sum_{i=1}^N\frac{f(x, \vec{\omega_i}, \vec{\omega_o})L_i(x, \vec{\omega_i})(\vec{\omega_i}\cdot\vec{n})}{p(\vec{\omega_i})}

で計算することができる。Li(x,ωi)L_i(x, \vec{\omega_i})の計算で再びモンテカルロ積分を適用すれば再帰的に計算していくことができる。

コンピューターで無限回の反射を計算することはできないので、一定の回数の反射を計算したところで計算を終了させる必要がある。計算を終える反射回数の決定方法として ロシアンルーレット(Russian Roulette) という方法がよく使われる。

ロシアンルーレット(Russian Roulette)

次のような無限級数を評価することを考える。

S=L1+L2+S = L_1 + L_2 + \dots

これを有限の項で打ち切って

SL1+L2++LNS \approx L_1 + L_2 + \dots + L_N

とすると誤差が残ってしまう。そこで次の項を足すかどうかを確率的に選択することで 統計的に偏りのない 計算をすることができる。

uu[0,1][0, 1]からの一様乱数、ppを次の項を足す確率とすると

  • u<pu < pなら次の項を足す
  • upu \ge pなら足さない

というルールで計算するのが ロシアンルーレット(Russian Roulette) である。

Path Tracing

モンテカルロ積分とロシアンルーレットを組み合わせればレンダリング方程式を統計的に偏りなく計算することができる。

Path TracingではLi(x,ωi)L_i(x, \vec{\omega_i})の計算を行う際に複数のレイをサンプリングするのではなく、毎回1つのレイしか生成しないようにする。仮に毎回2つのレイを生成するとすると、10回の反射を経た後のレイの数は2102^{10}になってしまう。これでは反射を深く追いかけることができなくなるので、1つのレイしか追いかけないようにする。

Path Tracingのアルゴリズムは以下のようになる。

//与えられたrayの方向から来る放射輝度を計算する
RGB Li(Ray ray) {
  //ロシアンルーレット
  if(rand() >= p) return RGB(0)

  レイを飛ばして物体と衝突するか調べる
  if(衝突しなかった) return 空の色
  
  //Le項の計算(本来はこの後にも反射を追いかけるべき)
  if(物体が光っている) return Le
  
  nextRay = 反射させたレイを生成
  pdf = 生成したレイの確率密度
  f = BRDF項
  cos = コサイン項
  return 1/(p*pdf) * f * cos * Li(nextRay)
}

この関数をNN回呼び出して平均を取ればレンダリング方程式のモンテカルロ積分になる。

モンテカルロ積分の分散低減手法

通常のモンテカルロ積分は収束が遅いという問題を抱えている。収束を早めるための方法として以下のようなものがある。

  • 重点的サンプリング(Importance Sampling)
  • 層化サンプリング(Stratified Sampling)
  • 準モンテカルロ積分(Quasi Monte Carlo Integration)

以下の積分を評価することを考える。

I=f(x)dxI = \int f(x)dx

重点的サンプリング(Importance Sampling)

モンテカルロ積分の分散Vは

V=1N(f(x)p(x)I)2p(x)dxV = \frac{1}{N}\int (\frac{f(x)}{p(x)} - I)^2p(x)dx

で与えられる。仮に確率密度関数をf(x)f(x)を比例するようにとるとp(x)=kf(x)p(x) = kf(x)となるが、p(x)dx=1\int p(x)dx = 1という条件があるので

1=kf(x)dxk=1I1 = k\int f(x)dx \\ k = \frac{1}{I}

よって分散VV

V=1N(II)21If(x)dx=0V = \frac{1}{N}\int (I - I)^2 \frac{1}{I}f(x)dx = 0

したがって、確率密度関数を被積分関数f(x)f(x)に完全に比例するように取ると、モンテカルロ積分は完全に収束する。しかし被積分関数の形が事前に分かることは稀なので、実際にはある程度比例するように確率密度関数を取る。この場合分散は0にはならないが、完全にランダムに取った場合より分散は減少する。

重点的サンプリング
重点的サンプリング

このように確率密度関数p(x)p(x)を被積分関数f(x)f(x)に出来るだけ近づけて分散を低下させる方法を 重点的サンプリング(Importance Sampling) という。

重点的サンプリングによる収束の様子
重点的サンプリングによる収束の様子
オレンジの線は重点的サンプリングを用いてモンテカルロ積分を行った結果である。青の線は完全にランダムなサンプルを使ってモンテカルロ積分を行った結果である。重点的サンプリングを行ったほうが早く収束するのが分かる。

層化サンプリング(Stratified Sampling)

モンテカルロ積分ではサンプリング点をランダムに取るが、もしサンプリングされた点に偏りがあると分散が増大する原因になる。そこでサンプリングする領域を幾つかの小領域に分けて、必ず各小領域からサンプリング点を一つずつ取ることで、全体的に偏りなくサンプリング点を得ることができる。

このようにして分散を減少させる方法を 層化サンプリング(Stratified Sampling) という。

準モンテカルロ法(Quasi Monte Carlo Method)

層化サンプリング法では小領域から乱数を生成することで分散の低減を図ったが、次元数が高くなると次元の呪いで小領域に分割するのが困難になる。

そこで、そもそも使用する乱数がある程度の規則性を持って分布させることを考える。このような規則性を持った乱数のような数のことを 超一様分布列(Low Discrepancy Sequence) または 準乱数 といい、超一様分布列を用いてモンテカルロ積分を行う方法を 準モンテカルロ積分(Quasi Monte Carlo Integration) という。

準モンテカルロ積分の誤差の標準偏差の収束オーダーはO(N)O(N)である。これは普通のモンテカルロ積分の収束オーダーO(N)O(\sqrt{N})よりかなり良い。

超一様分布列として

  • ハルトン列(Halton Sequence)
  • ソボル列(Sobol Sequence)

といったものがある。

Halton列
Halton列
これはハルトン列により生成された数をプロットしたものである。[0,1]×[0,1][0, 1]\times[0,1]の平面を埋めるように分布しているのが分かる。

Halton列によるモンテカルロ積分
Halton列によるモンテカルロ積分
一様乱数を用いたモンテカルロ積分と、ハルトン列を用いた準モンテカルロ法の収束具合を比較したのが上の図である。ハルトン列を用いた方が早く収束しているのが分かる。

拡散反射面への重点的サンプリングの適用

拡散反射面ではf(x,ωi,ωo)=ρπf(x, \vec{\omega_i}, \vec{\omega_o}) = \frac{\rho}{\pi}だから、レンダリング方程式は

Lo(x,ωo)=Le(x,ωo)+ρπΩLi(x,ωi)(ωin)dωiL_o(x, \vec{\omega_o}) = L_e(x, \vec{\omega_o}) + \frac{\rho}{\pi}\int_{\Omega}L_i(x, \vec{\omega_i})(\vec{\omega_i}\cdot\vec{n})d\vec{\omega_i}

となる。積分の中身に注目するとコサイン項(ωin)=cosθ(\vec{\omega_i}\cdot\vec{n}) = \cos{\theta}がかかっている。そこで、ωi\vec{\omega_i}をサンプリングする確率密度関数p(ωi)p(\vec{\omega_i})cosθ\cos{\theta}に比例するように取れば分散を減少(つまりノイズが減少)させることができる。

一般的に拡散反射面以外でも、BRDFf(x,ωi,ωo)f(x, \vec{\omega_i}, \vec{\omega_o})とコサイン項(ωiωo)(\vec{\omega_i}\cdot\vec{\omega_o})に比例するように確率密度関数p(ωi)p(\vec{\omega_i})を取れば分散を減少させることができる。

cosθ\cos{\theta}に比例する確率密度関数p(ωi)p(\vec{\omega_i})の導出

比例定数kkを用いて

p(ωi)=kcosθp(\vec{\omega_i}) = k\cos{\theta}

とおく。Ωp(ωi)dωi=1\int_{\Omega} p(\vec{\omega_i})d\vec{\omega_i} = 1という条件から

kΩcosθdωi=1k\int_{\Omega} \cos{\theta}d\vec{\omega_i} = 1

立体角を球面座標系に変換するとdωi=sinθdθdϕd\vec{\omega_i} = \sin{\theta}d\theta d\phiより

k02π0π2cosθsinθdθdϕ=1k\int_0^{2\pi}\int_0^{\frac{\pi}{2}}\cos{\theta}\sin{\theta}d\theta d\phi = 1

計算していくと

k=1πk = \frac{1}{\pi}

よって

p(ωi)=cosθπp(\vec{\omega_i}) = \frac{\cos{\theta}}{\pi}

と求まる。

p(ωi)p(\vec{\omega_i})からの方向のサンプリング

確率密度関数p(ωi)p(\vec{\omega_i})が求まったら、あとはこれから方向をサンプリングすればよい。 与えられた確率密度関数に従うようにサンプルを生成する方法として主に以下の3つがある。

  • 逆関数法(Inverse Transform Method)
  • 棄却サンプリング法(Rejection Sampling)
  • マルコフ連鎖モンテカルロ法(Markov Chain Monte Carlo Method)

逆関数法(Inverse Transform Method)

逆関数法は以下のような手順で確率密度関数p(x)p(x)に従うサンプルを生成する。

  1. [0,1][0, 1]からの一様乱数uuを生成
  2. 累積分布関数F(x)F(x)の逆関数F1(x)F^{-1}(x)uuを代入する

棄却サンプリング法(Rejection Sampling)

  1. 方向ωi\vec{\omega_i}を一様にランダムに生成
  2. pi=p(ωi)p_i = p(\vec{\omega_i})を計算
  3. [0,1][0, 1]からの一様乱数uuを生成
  4. u<pu < pならωi\vec{\omega_i}をサンプルとして受容する。upu \ge pなら棄却する

マルコフ連鎖モンテカルロ法(Markov Chain Monte Carlo Method)

省略

累積分布関数F(x)F(x)の逆関数が求まる場合には逆関数法を使うのが一番効率が良い。

p(ωi)=cosθπp(\vec{\omega_i}) = \frac{\cos{\theta}}{\pi}からの逆関数法によるサンプリング

逆関数法を用いてp(ωi)=cosθπp(\vec{\omega_i}) = \frac{\cos{\theta}}{\pi}から方向ωi\vec{\omega_i}をサンプリングしてみよう。

立体角から球面座標系への測度変換

立体角のままでは扱いずらいので、球面座標系に変換することを考える。立体角による確率密度関数p(ω)p(\vec{\omega})と球面座標系による確率密度関数p(θ,ϕ)p(\theta, \phi)には

p(ω)sinθ=p(θ,ϕ)p(\vec{\omega})\sin{\theta} = p(\theta, \phi)

という関係がある。

これを利用しp(ωi)=cosθπp(\vec{\omega_i}) = \frac{\cos{\theta}}{\pi}を球面座標系に変換すると

p(θ,ϕ)=cosθsinθπp(\theta, \phi) = \frac{\cos{\theta}\sin{\theta}}{\pi}

となる

周辺化

これはθ\thetaϕ\phiの同時確率密度関数なので、これを周辺化してθ\thetaの周辺確率密度関数p(θ)p(\theta)を求める。

θ\thetaの周辺確率密度関数p(θ)p(\theta)を求めると

p(θ)=02πcosθsinθπdϕ=2cosθsinθ=sin2θp(\theta) = \int_0^{2\pi} \frac{\cos{\theta}\sin{\theta}}{\pi}d\phi = 2\cos{\theta}\sin{\theta} = \sin{2\theta}

よってϕ\phiの条件付き確率密度p(ϕθ)p(\phi | \theta)

p(ϕθ)=p(θ,ϕ)p(θ)=12πp(\phi | \theta) = \frac{p(\theta, \phi)}{p(\theta)} = \frac{1}{2\pi}

と求まる。

累積分布関数を求める

θ\thetaの累積分布関数P(θ)P(\theta)を求めると

P(θ)=0θsin2tdt=1cos2θ2P(\theta) = \int_0^{\theta}\sin{2t}dt = \frac{1 - \cos{2\theta}}{2}

同様に

P(ϕθ)=0ϕ12πdt=12πϕP(\phi | \theta) = \int_0^{\phi}\frac{1}{2\pi}dt = \frac{1}{2\pi}\phi

累積分布関数の逆関数を求める

P(θ)P(\theta)の逆関数は

Pθ1(u)=12cos1(12u)P_{\theta}^{-1}(u) = \frac{1}{2}\cos^{-1}(1 - 2u) \\

P(ϕθ)P(\phi | \theta)の逆関数は

Pϕθ1(v)=2πvP_{\phi | \theta}^{-1}(v) = 2\pi v

と求まる。

方向のサンプリング

あとはu,vu, v[0,1][0, 1]からの一様乱数として

θ=12cos1(12u)ϕ=2πv\theta = \frac{1}{2}\cos^{-1}(1 - 2u) \\ \phi = 2\pi v

とすれば良い。

あとは球面座標系から直交座標系(x,y,z)(x, y, z)に直せばサンプリングされた方向を得ることができる。

x=cosϕsinθy=cosθz=sinϕsinθ\begin{aligned} x &= \cos{\phi}\sin{\theta} \\ y &= \cos{\theta} \\ z &= \sin{\phi}\sin{\theta} \end{aligned}

プログラムにすると次のようになる。

Vec3 randomCosineHemisphere(double &pdf, const Vec3& n) {
    double u = rnd();
    double v = rnd();

    double theta = 0.5*std::acos(1 - 2*u);
    double phi = 2*M_PI*v;
    pdf = 1/M_PI * std::cos(theta);

    double x = std::cos(phi)*std::sin(theta);
    double y = std::cos(theta);
    double z = std::sin(phi)*std::sin(theta);
    Vec3 xv, zv;
    orthonormalBasis(n, xv, zv);
    return x*xv + y*n + z*zv;
}

pdfにはサンプリングされた方向ωi\vec{\omega_i}の確率密度p(ωi)p(\vec{\omega_i})が格納される。

これを使うと拡散反射面の重点的サンプリングは次のように書ける。

Vec3 getRadiance(const Ray& ray, int depth = 0) {
    ...
    if(res.hitSphere->type == "diffuse") {
        double pdf;
        Vec3 nextDir = randomCosineHemisphere(pdf, res.hitNormal);
        Ray nextRay(res.hitPos + 0.001*res.hitNormal, nextDir);
        double cos_term = std::max(dot(nextDir, res.hitNormal), 0.0f);
        return 1/roulette * 1/pdf * res.hitSphere->color/M_PI * cos_term * getRadiance(nextRay, depth + 1, roulette);
    }
    ...
}
半球一様サンプリング
半球一様サンプリング
重点的サンプリング
重点的サンプリング

上の画像は同じ100サンプル数で一様サンプリングと重点的サンプリングの結果を比較したものである。重点的サンプリングを使ったほうがノイズが減少しているのが分かる。