Theory of Physically Based Rendering 2
確率論とレンダリング方程式のモンテカルロ積分, パストレーシングの数理的な表現について

この資料では 物理ベースレンダリング(Physically Based Rendering) を実現するために必要な数学・理論・実装について説明しています。

次のような事柄をこの資料では説明しています。

  • 確率論
  • レイトレでの確率論
  • モンテカルロ積分
  • レイトレでのモンテカルロ積分
  • レンダリング方程式の数値計算

確率論

モンテカルロ積分を行うにあたって、確率論の基礎的な部分をある程度抑えている必要があります。ここではそれらの事柄について説明を行います。

確率空間

試行の結果得られる可能性のある標本全体を 標本空間(Sample Space) といい、Ω\Omegaで表します。

サイコロを一回投げるという例の場合、それぞれの目に対応するΩ={ω1,ω2,,ω6}\Omega = \{\omega_1, \omega_2, \cdots, \omega_6\}を標本空間として取ります。

標本空間から生成されるσ加法族をF\mathcal{F}で表し、F\mathcal{F}の元を 事象(Event) と呼びます。特にΩF\Omega \in \mathcal{F}を全事象、ϕF\phi \in \mathcal{F}を空事象と呼びます。

F\mathcal{F}の元(つまり事象)に対し、確率測度(Probability Measure) P:F[0,1]P:\mathcal{F} \rightarrow [0, 1]が次のように定義されます。

  1. PPF\mathcal{F}上の測度
  2. P(Ω)=1P(\Omega) = 1

確率測度が通常の測度と違うところは、P(Ω)=1P(\Omega) = 1が指定されているところです。このため任意の事象AFA \in \mathcal{F}に対し、0P(A)10 \le P(A) \le 1を満たします。

3つ組(Ω,F,P)(\Omega, \mathcal{F}, P)確率空間(Probability Space) と呼びます。

確率変数

可測関数X:ΩRX:\Omega \rightarrow \mathbb{R}実数値確率変数(Real Valued Random Variable) といいます。以降確率変数と言う場合は実数値確率変数を指すものとします。

この関数は標本空間の元ωΩ\omega \in \Omegaに対応する実数値を定めます。サイコロを1回投げる例では、サイコロの目の数という確率変数XX

X(ω)={1(ω=ω1)2(ω=ω2)6(ω=ω6)X(\omega) = \begin{cases} 1 & (\omega = \omega_1) \\ 2 & (\omega = \omega_2) \\ \vdots \\ 6 & (\omega = \omega_6) \end{cases}

のように定義できます。

分布測度

確率変数XXがある値BBを取る確率PX(B)P_X(B)を計算することを考えます。この確率は確率変数の逆写像を用いて元の確率空間(Ω,F,P)(\Omega, \mathcal{F}, P)に引き戻し、そこでPPを用いることで計算できます。

PX(B)=P({ωX(ω)B})=P(X1(B))=P(XB)P_X(B) = P(\{\omega | X(\omega) \in B\}) = P(X^{-1}(B)) = P(X \in B)

PXP_X分布測度(Distribution Measure) と呼ばれます。

PXP_XR\mathbb{R}上の確率測度を定めます。したがって、確率変数XXにより別の確率空間(R,B,PX)(\mathbb{R}, \mathcal{B}, P_X)が誘導されます。(B\mathcal{B}はボレル集合族)

分布関数(累積分布関数)

以下のような関数FX(x)F_X(x)分布関数(Distribution Function) または 累積分布関数(Cumulative Distribution Function - cdf) といいます。

FX(x)=PX([,x])F_X(x) = P_X([-\infty, x])

分布関数は確率変数から導かれる分布測度を用いて定義されていますが、逆に分布関数を指定することで分布測度が一意に定まります

分布関数を用いることで様々な確率が計算できます。例えば確率変数XX[a,b][a, b]に値をとる確率PX([a,b])P_X([a, b])

PX([a,b])=FX(b)FX(a)P_X([a, b]) = F_X(b) - F_X(a)

と計算できます。

確率密度関数

次のような条件を満たすfX(x)f_X(x)確率密度関数(Probability Density Function - pdf) といいます。 1. f(x)dx=1\int_{-\infty}^{\infty} f(x)dx = 1 2. FX(x)=xf(x)dxF_X(x) = \int_{-\infty}^x f(x)dx

この定義よりdFXdx=f(x)\frac{dF_X}{dx} = f(x)となっていることに注意してください。(dFXdx=f(x)\frac{dF_X}{dx} = f(x)はRadon-Nikodym微分と呼ばれる。このようなffが存在することはRadon-Nikodymの定理より言える)

確率密度関数を指定することで、分布関数が定まり、さらに分布測度が定まるので、確率密度関数を指定することで確率変数を構成することがよく行われます。

例: 一様分布

確率変数XXが一様分布U(a,b)U(a, b)に従うとは、XXの確率密度関数fX(x)f_X(x)

fX(x)={1ba(axb)0(otherwise)f_X(x) = \begin{cases} \frac{1}{b - a} & (a \le x \le b) \\ 0 & (otherwise) \end{cases}

となっているときをいいます(a<b)(a < b)。これは閉区間[a,b][a, b]から点を一様に取ることを意味しています。

例: 正規分布

確率変数XXが正規分布N(μ,σ2)N(\mu, \sigma^2)に従うとは、XXの確率密度関数fX(x)f_X(x)

fX(x)=12πσ2e(xμ)22σ2f_X(x) = \frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{(x - \mu)^2}{2\sigma^2}}

になっているときをいいます。確率変数が正規分布に従っていると仮定して、様々な解析を行うことが頻繁にあります。

期待値(平均)

確率変数XX期待値(平均)E[X]E[X] を分布測度PXP_Xによる積分として以下のように定義します。

E[X]=DX(t)dPX(t)=DX(t)fX(t)dtE[X] = \int_D X(t)dP_X(t) = \int_D X(t)f_X(t)dt

DDXXの値域です。期待値はXXから出てくる値が平均的にどのような値を取るかを示しています。

注意としてはXXは常に可積分であるとは限らないので、期待値が計算出来ない場合もあります。

期待値は線形性を持ちます。

E[aX1+bX2]=aE[X1]+bE[X2]E[aX_1 + bX_2] = aE[X_1] + bE[X_2]

分散

確率変数XX分散V[X]V[X] を以下で定義します。

V[X]=E[(XE[X])2]V[X] = E[(X - E[X])^2]

分散はXXから出てくる値が平均周りでどれだけばらついているかを表している量です。分散が大きいことはばらつきが大きいことを示します。

分散も常に計算可能とは限らないことに注意してください。

分散は次のような性質を持ちます。

V[X+Y]=V[X]+V[Y]+2Cov[X,Y]V[X + Y] = V[X] + V[Y] + 2\mathrm{Cov}[X, Y]

Cov[X,Y]\mathrm{Cov}[X, Y]は共分散といい、

Cov[X,Y]=E[(XE[X])(YE[Y])]\mathrm{Cov}[X, Y] = E[(X - E[X])(Y - E[Y])]

で定義されます。

また、分散はスカラー倍に対しては次のような性質を持ちます。

V[aX]=a2V[X]V[aX] = a^2V[X]

標準偏差

分散の平方根をとったものを 標準偏差(Standard Deviation) といい、σ\sigmaで表します。

σ(X)=V(X)\sigma(X) = \sqrt{V(X)}

分散は元の量を二乗してしまった値のため、元の値の範囲でどれだけばらついているかが分かりにくいという問題点があります。標準偏差は元の値の範囲と一致するように分散を修正したものです。

確率変数変換

確率変数XXによって誘導された確率空間を(R,BX,PX)(\mathbb{R}, \mathcal{B}_X, P_X)、確率変数YYによって誘導された確率空間を(R,BY,PY)(\mathbb{R}, \mathcal{B}_Y, P_Y)とします。

確率変数XXと確率変数YYの間に、関数h:RRh : \mathbb{R} \rightarrow \mathbb{R}による関係Y=h(X)Y = h(X)があるとします。もしhhが単調増加関数なら

FY(y)=PY(Yy)=PX(Xh1(y))=FX(h1(y))F_Y(y) = P_Y(Y \le y) = P_X(X \le h^{-1}(y)) = F_X(h^{-1}(y))

という関係が成り立つため、確率密度関数の間には

pY(y)=pX(h1(y))dh1dy(y)p_Y(y) = p_X(h^{-1}(y))|\frac{dh^{-1}}{dy}(y)|

という関係が成り立ちます。同様にhhが単調減少関数の場合もこの式が成り立ちます。つまり、hhが単調関数ならこのようにして、確率変数変換後の確率密度関数を求めることができます。

多次元確率変数

上では実数値確率変数X:ΩRX: \Omega \rightarrow \mathbb{R}を考えましたが、値域をR2\mathbb{R^2}にしたX:ΩR2X : \Omega \rightarrow \mathbb{R}^2を考えます。

このような確率変数に対しても確率密度関数が同様に定義されますが、名前が変わります。

結合確率密度関数

次のような条件を満たす関数pX,Y(x,y)p_{X, Y}(x, y)を確率変数XX結合確率密度関数(Joint Probability Density Function) といいます。

  1. R2pX,Y(x,y)dxdy=1\int_{\mathbb{R}^2} p_{X, Y}(x, y)dxdy = 1
  2. PX(D)=DpX,Y(x,y)dxdyP_X(D) = \int_D p_{X, Y}(x, y)dxdy

周辺確率密度関数

結合確率密度関数p(x,y)p(x, y)から次のようにして求まる確率密度関数を 周辺確率密度関数(Marginal Probability Density Function) と呼びます。

pX(x)=pX,Y(x,y)dyp_X(x) = \int_{-\infty}^{\infty} p_{X, Y}(x, y)dy
pY(x)=pX,Y(x,y)dxp_Y(x) = \int_{-\infty}^{\infty} p_{X, Y}(x, y)dx

これらはそれぞれの次元の実数値確率変数XXYYの確率密度関数になっています。

条件付き確率密度関数

YYの値yyを観測した上でのXXの確率密度関数pXY(x)p_{X|Y}(x)XX条件付き確率密度関数(Conditional Probability Density Function) といいます。定義は以下の通りです。

pXY(xy)=pX,Y(x,y)pY(y)   (pY(y)0)p_{X|Y}(x | y) = \frac{p_{X, Y}(x, y)}{p_Y(y)} ~~~ (p_Y(y) \neq 0)

確率変数の独立

確率変数XXYY独立(Independent) であるとは

pXY(xy)=pX(x)p_{X | Y}(x|y) = p_X(x)

となるときをいいます。

このときXXYYの結合確率密度関数は

pX,Y(x,y)=pX(x)pY(y)p_{X, Y}(x, y) = p_X(x)p_Y(y)

のように表わされます。

モンテカルロ積分

次のような一変数関数の積分を考えます。

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

xxは確率密度関数p(x)p(x)が指定され、確率変数XXになっているとします([a,b][a, b]に値をとるとする)。p(x)p(x)により点が(x1,x2,,xN)(x_1, x_2, \cdots, x_N)NN個生成されたとします。このときIIを以下のような計算で近似することができます。

I^=1Ni=1Nf(xi)p(xi)\hat{I} = \frac{1}{N}\sum_{i=1}^N \frac{f(x_i)}{p(x_i)}

これをIIモンテカルロ積分(Montecarlo Integration) といいます。

各項f(X)p(X)\frac{f(X)}{p(X)}の期待値を計算してみると

E[f(X)p(X)]=abf(x)p(x)p(x)dx=abf(x)dx=I\begin{aligned} E[\frac{f(X)}{p(X)}] &= \int_a^b \frac{f(x)}{p(x)}p(x)dx \\ &= \int_a^b f(x)dx = I \end{aligned}

となり、各項の期待値はIIに一致していることが分かります。したがってE[Iˉ]E[\bar{I}]IIに一致しているので、Iˉ\bar{I}II不偏推定量(Unbiased Estimator) になっています。

ここで 大数の法則(Law of large numbers) と呼ばれる次の定理を紹介します。

大数の法則

X1,X2,,XNX_1, X_2, \cdots, X_Nを平均μ\muを持つnn個の確率変数とする。Xnˉ=1ni=1NXi\bar{X_n} = \frac{1}{n}\sum_{i=1}^N X_iとすると、nn \to \inftyとしたときにXˉ\bar{X}は確率1でμ\muに収束する。

この定理より、モンテカルロ積分Iˉ\bar{I}はNを無限大にすると確率1で求めたい積分Iˉ\bar{I}に一致することが分かります。これがモンテカルロ積分でIIが計算できる理由です。

モンテカルロ積分の誤差

一方でIˉ\bar{I}の分散を計算してみると

V[I^]=V[1Ni=1Nf(xi)p(xi)]=1N2V[i=1Nf(xi)p(xi)]=1NV[f(X)p(X)]=1Nab(f(x)p(x)I)2dx\begin{aligned} V[\hat{I}] &= V[\frac{1}{N}\sum_{i=1}^N \frac{f(x_i)}{p(x_i)}] \\ &= \frac{1}{N^2}V[\sum_{i=1}^N\frac{f(x_i)}{p(x_i)}] \\ &= \frac{1}{N}V[\frac{f(X)}{p(X)}] \\ &= \frac{1}{N}\int_a^b (\frac{f(x)}{p(x)} - I)^2dx \end{aligned}

したがって分散はO(N)O(N)のオーダーで減少することが分かります。

標準偏差は推定誤差を表していますが、これはO(N)O(\sqrt{N})のオーダーで減少します。これはアルゴリズム論的にはあまり良くない性質です。例えば誤差を1100\frac{1}{100}にするためには、サンプル数を1000010000倍にしないといけないことを意味しています。

モンテカルロ積分の収束の遅さを改善するための手法として、重点的サンプリング(Importance Sampling) と呼ばれるものがあります。

高次元積分のモンテカルロ積分

モンテカルロ積分の利点は高次元積分を少ないサンプル数で計算が可能なことです。

以下のような高次元積分を考えます。

I=kDf(x1,,xk)dμ(x1)dμ(xk)I = \int \underbrace{\cdots}_k \int_D f(x_1, \cdots, x_k)d\mu(x_1)\cdots d\mu(x_k)

それぞれの次元の点を確率変数X1,,XkX_1, \cdots, X_kで表します。それぞれの次元の確率変数を互いに独立になるように設定すれば、pX1,,Xk=pX1pXkp_{X_1, \cdots, X_k} = p_{X_1} \cdots p_{X_k}となるため、以下のようにモンテカルロ積分でIIを求めることができます。

Iˉ=1Ni=1Nf(x1i,,xki)pX1(x1i)pX2(x2i)pXk(xki)\bar{I} = \frac{1}{N}\sum_{i=1}^N \frac{f(x_1^i, \cdots, x_k^i)}{p_{X_1}(x_1^i)p_{X_2}(x_2^i)\cdots p_{X_k}(x_k^i)}

ここでxjix_j^iは次元jjii番目のサンプルを表します。

確率密度関数から点をサンプリングする方法

モンテカルロ積分を行うためには指定した確率密度関数からサンプリング点を生成する必要があります。次のような方法が有名です。

  • 逆関数法(Inverse Transform Sampling)
  • マルコフ連鎖モンテカルロ法(Markov Chain Monte Carlo - MCMC)

逆関数法

サンプリング点を生成したい確率変数をXX(ここでは実数値確率変数とします)とし、その確率密度関数をpX(x)p_X(x)とします。まずXXの分布関数FX(x)F_X(x)を求めます。

FX(x)=xpX(x)dxF_X(x) = \int_{-\infty}^{x} p_X(x)dx

FX(x)F_X(x)が狭義単調増加関数と仮定します。すると逆関数FX1(y)F_X^{-1}(y)が存在します。

ここでUUを一様分布U(0,1)U(0, 1)に従う確率変数とすると、FX1(U)F_X^{-1}(U)の分布関数は

P(FX1(U)x)=P(UFX(x))=FX(x)P(F_X^{-1}(U) \le x) = P(U \le F_X(x)) = F_X(x)

となり、XXの分布関数FX(x)F_X(x)に一致することが分かります。

したがって[0,1][0, 1]の一様乱数uuを生成し、x=FX1(u)x = F_X^{-1}(u)とすることで確率変数XXのサンプルxxが得られます。

例1

以下のような積分をモンテカルロ積分で計算してみましょう。

0πsin2xdx=π2\int_0^{\pi} \sin^2xdx = \frac{\pi}{2}

[0,π][0, \pi]の一様分布を使ってサンプリング点XXを生成することにします。XXの確率密度関数はpX(x)=1πp_X(x) = \frac{1}{\pi}です。

逆関数法を使うと、[0,1][0, 1]の一様乱数uuに対してx=πux = \pi uとすればXXのサンプルが生成できます。

以下はC++でこれを実装したコードです。

#include <iostream>
#include <cmath>
#include <random>


std::random_device rnd_dev;
std::mt19937 mt(rnd_dev());
std::uniform_real_distribution<double> dist(0, 1);
inline double rnd() {
  return dist(mt);
}


inline double f(double x) {
  double v = std::sin(x);
  return v*v;
}


int main() {
  const int N = 10000;

  double sum = 0;
  for(int i = 0; i < N; i++) {
    double x = M_PI * rnd();
    sum += M_PI*f(x);
  }
  sum /= N;

  std::cout << sum << std::endl;

  return 0;
}

収束する様子は以下のようになります。

重点的サンプリング

p(x)p(x)を被積分関数f(x)f(x)に比例させるように取る方法です。

もし完全に比例させるように取ることができて、p(x)=kf(x)p(x) = kf(x)とできたとします。 このときabp(x)dx=1\int_a^b p(x)dx = 1という条件があるのでk=1Ik = \frac{1}{I}です。

この状況でモンテカルロ積分の分散を求めると

V[Iˉ]=1Nab(f(x)p(x)I)2=1Nab0dx=0\begin{aligned} V[\bar{I}] &= \frac{1}{N}\int_a^b (\frac{f(x)}{p(x)} - I)^2 \\ &= \frac{1}{N}\int_a^b 0 dx = 0 \end{aligned}

となり、分散が0なので確率1でIˉ\bar{I}IIに一致することが分かります。

実際にはp(x)p(x)f(x)f(x)に完全に比例するように取ることはできないですが、ある程度似るように取ることができれば収束が早くなることが期待できます。

例2

例1のモンテカルロ積分に重点的サンプリングを適用してみましょう。 被積分関数はsin2(x)\sin^2(x)なので、比例定数kkを用いてpX(x)=ksinxp_X(x) = k\sin xと置きます。

確率密度関数は0πpX(x)dx=1\int_0^{\pi} p_X(x)dx = 1を満たさないといけないので、これよりk=12k = \frac{1}{2}と求まります。したがってpX(x)=12sinxp_X(x) = \frac{1}{2}\sin xです。

XXのサンプルを生成するために逆関数法を使用します。XXの分布関数FX(x)F_X(x)を求めると

FX(x)=0x12sintdt=12(1cosx)F_X(x) = \int_0^x \frac{1}{2}\sin tdt = \frac{1}{2}(1 - \cos x)

となります。したがってFX1(y)F_X^{-1}(y)

FX1(y)=cos1(12y)F_X^{-1}(y) = \cos^{-1}(1 - 2y)

と求まります。[0,1][0, 1]の一様乱数uuを用いてx=FX1(u)x = F_X^{-1}(u)としてXXのサンプルが生成できます。

以下はこれを実装したC++のコードです。

#include <iostream>
#include <cmath>
#include <random>


std::random_device rnd_dev;
std::mt19937 mt(rnd_dev());
std::uniform_real_distribution<double> dist(0, 1);
inline double rnd() {
  return dist(mt);
}


inline double f(double x) {
  double v = std::sin(x);
  return v*v;
}
inline double p(double x) {
  return 0.5 * std::sin(x);
}


int main() {
  const int N = 10000;

  double sum = 0;
  for(int i = 0; i < N; i++) {
    double u = rnd();
    double x = std::acos(1 - 2*u);
    sum += f(x)/p(x);
  }
  sum /= N;

  std::cout << sum << std::endl;

  return 0;
}

以下は収束の様子を重点的サンプリングを行わない場合を比較したものです。 重点的サンプリングを行ったほうが早く理論値の周辺に収束していることが分かります。

レイトレでのモンテカルロ積分

モンテカルロ積分をレンダリング方程式に適用しようとする場合、確率変数になるのは点の位置xMx \in \mathcal{M}や、方向ベクトルωS2\vec{\omega} \in \mathcal{S^2}になります。つまり確率変数は実数値確率変数ではなく、M\mathcal{M}-確率変数やS2\mathcal{S^2}-確率変数となっています。

確率空間の設定

確率空間は明示的には与えず、抽象的に(Ω,F,P)(\Omega, \mathcal{F}, P)と置くことにします。

S2\mathcal{S^2}-確率変数

S2\mathcal{S}^2-確率変数XS2:ΩS2X_{\mathcal{S^2}} : \Omega \rightarrow \mathcal{S^2}は方向ベクトルを生成する確率変数です。この確率変数から新たに確率空間(S2,FS2,PS2)(\mathcal{S^2}, \mathcal{F}_{\mathcal{S^2}}, P_{\mathcal{S^2}})が誘導されます。

XS2X_{\mathcal{S^2}}の確率密度関数をpS2(ω)=dPS2dσp_{\mathcal{S^2}}(\vec{\omega}) = \frac{dP_{\mathcal{S^2}}}{d\sigma}と表すことにします。方向ベクトルの集合DS2D \in \mathcal{S^2}が与えられたとき、この集合(事象)に対する確率PS2P_{\mathcal{S^2}}

PS2(D)=DpS2(ω)dσ(ω)P_{\mathcal{S}^2}(D) = \int_{D} p_{\mathcal{S^2}}(\vec{\omega})d\sigma(\vec{\omega})

と計算できます。

M\mathcal{M}-確率変数

M\mathcal{M}-確率変数XM:ΩMX_{\mathcal{M}} : \Omega \rightarrow \mathcal{M}は物体表面上の点を生成する確率変数です。この確率変数から新たに確率空間(M,FM,PM)(\mathcal{M}, \mathcal{F}_{\mathcal{M}}, P_{\mathcal{M}})が誘導されます。

XMX_{\mathcal{M}}の確率密度関数をpM(x)=dPMdAp_{\mathcal{M}}(x) = \frac{dP_{\mathcal{M}}}{dA}と表すことにします。物体表面上の点集合DMD \in \mathcal{M}が与えられたとき、この集合(事象)に対する確率PMP_{\mathcal{M}}

PM(D)=DpM(x)dA(x)P_{\mathcal{M}}(D) = \int_D p_{\mathcal{M}}(x)dA(x)

と計算できます。

S2\mathcal{S^2}-確率変数とM\mathcal{M}-確率変数の変換

立体角測度と面積測度の間には次のような関係がありました。

dσ(ω)=V(x,x)G(x,x)cosθdAd\sigma(\vec{\omega}) = V(x, x')\frac{G(x, x')}{|\cos{\theta}|}dA

確率変数変換の式から、pS2p_\mathcal{S^2}pMp_\mathcal{M}の間には次のような関係が成り立ちます。

pS2(ω)=cosθV(x,x)G(x,x)pM(x)p_\mathcal{S^2}(\vec{\omega}) = \frac{|\cos{\theta}|}{V(x, x')G(x, x')}p_\mathcal{M}(x')

この式を使うと、経路積分形式で表されたレンダリングの方程式において、方向のサンプリングと物体表面上の点のサンプリングを組み合わせて使うことができるようになります。

レンダリング方程式のモンテカルロ積分

経路積分形式のレンダリング方程式は以下のように表されていました。

Ij=k=1MMk+1We(x0x1)i=1k{f(xi2xi1xi)V(xi1,xi)G(xi1,xi)}Le(xkxk1)dA(x0)dA(x1)dA(xk)I_j = \sum_{k=1}^{\infty}\underbrace{\int_{\mathcal{M}} \cdots \int_{\mathcal{M}}}_{k+1} W_e(x_0 \rightarrow x_1)\prod_{i=1}^{k}\{ f(x_{i-2} \rightarrow x_{i-1} \rightarrow x_i)V(x_{i-1}, x_i)G(x_{i-1}, x_i) \}L_e(x_k \rightarrow x_{k-1})dA(x_0)dA(x_1)\cdots dA(x_k)

これにモンテカルロ積分を適用することを考えます。経路点x0,,xkx_0, \cdots, x_kに対応する確率変数をX0,,XkX_0, \cdots, X_kとし、それぞれの確率変数は互いに独立であると仮定します。ii番目のサンプルを(x0i,,xki)(x_0^i, \cdots, x_k^i)と表し、それぞれの確率変数の確率密度関数をpX0(x0),,pXk(xk)p_{X_0}(x_0), \cdots, p_{X_k}(x_k)とすると

Ijˉ=1N1i=1N1We(x0ix1i)f(x1ix0ix1i)V(x0i,x1i)G(x0i,x1i)Le(x1ix0i)pX0(x0i)pX1(x1i)+1N2i=1N2We(x0x1)f(x1ix0ix1i)f(x0ix1ix2i)V(x0i,x1i)V(x1i,x2i)G(x0i,x1i)G(x1i,x2i)Le(x2ix1i)pX0(x0i)pX1(x1i)pX2(x2i)+\begin{aligned} \bar{I_j} &= \frac{1}{N_1}\sum_{i=1}^{N_1}\frac{W_e(x_0^i \rightarrow x_1^i)f(x_{-1}^i\rightarrow x_0^i \rightarrow x_1^i)V(x_0^i, x_1^i)G(x_0^i, x_1^i)L_e(x_1^i\rightarrow x_0^i)}{p_{X_0}(x_0^i)p_{X_1}(x_1^i)} \\ &+ \frac{1}{N_2}\sum_{i=1}^{N_2}\frac{W_e(x_0 \rightarrow x_1)f(x_{-1}^i\rightarrow x_0^i \rightarrow x_1^i)f(x_0^i \rightarrow x_1^i \rightarrow x_2^i)V(x_0^i, x_1^i)V(x_1^i, x_2^i)G(x_0^i, x_1^i)G(x_1^i, x_2^i)L_e(x_2^i\rightarrow x_1^i)}{p_{X_0}(x_0^i)p_{X_1}(x_1^i)p_{X_2}(x_2^i)} \\ &+ \cdots \end{aligned}

のようにしてモンテカルロ積分することができます。無限級数の各項が積分になっているので、項ごとにモンテカルロ積分を適用することでこの形を得ます。

この形では各項の評価に毎回違うサンプルを使っていますが、これは計算コストが高くなるためにあまり良い方法ではありません。後で見るようにPath Tracingではx0,x1,x_0, x_1, \cdotsを逐次的に生成し、各項でサンプルの使い回しを行うことで計算コストを抑えています。

無限級数の評価

レンダリング方程式を数値計算するためには、上記の式のように無限級数を評価する必要があります。

次のような無限級数を考えます。

S=S1+S2+=i=1SiS = S_1 + S_2 + \cdots = \sum_{i=1}^{\infty} S_i

コンピューターで無限個の項を足し合わせることはできないので、有限個の項の総和で無限級数を近似する必要があります。

単純に足し合わせる最大の項数を設定して近似を行っても良いのですが、この場合それより後ろにある項が評価されないために、わずかですがバイアスが残ってしまいます。また、どこまで足し合わせれば良いかは必ずしも明確ではないです。

そこで使用されるのが ロシアンルーレット(Russian Roulette) と呼ばれる確率論的な評価方法です。

ロシアンルーレットによる無限級数の評価

ロシアンルーレットでは次のような手順で無限級数の評価を行います。

  1. 次の項を足す確率p[0,1]p \in [0, 1]を設定します。
  2. ii回目の項を足すかどうか決めているとします。[0,1][0, 1]の一様分布に従う乱数uuを生成します。
  3. u<pu < pならSipi\frac{S_i}{p^i}を足し合わせます。そうでないなら級数の評価を打ち切ります。

SiS_iではなくてSipi\frac{S_i}{p^i}を足していることがポイントです。ii番目の項が足される確率はpip^iなので、Sipi\frac{S_i}{p^i}で足せば期待値が元の無限級数と一致することになります。

次の項を足す確率ppの設定

ppを小さな値に設定してしまうと分散が大きくなってしまうので、最初は確率を1にし、徐々に確率を下げていく方法がよく取られます。

レンダリング方程式の数値計算

ここではレンダリング方程式のモンテカルロ積分を利用して、レンダリング方程式の数値解を計算するためのアルゴリズムについて考えます。

レンダリング方程式を数値計算するためのアルゴリズムとしては以下のようなものがあります。

  • Path Tracing(PT)
  • Bidirectional Path Tracing(BDPT)
  • Metropolis Light Transport(MLT)
  • Stochastic Progressive Photon Mapping(SPPM)

これらのアルゴリズムのどこに相違点があるかと言うと、大まかに言えばサンプルの生成方法に違いがあると言えます。例えば次に見るようにPath Tracingでは視点から逐次的に経路点のサンプルを生成していきますが、BDPTでは視点と光源の両方向から経路点のサンプルを生成していきます。

Path Tracing

パストレーシング(Path Tracing - 経路追跡法) は視点から経路点の生成を行っていく方法です。

ここでレンダリング方程式のモンテカルロ積分の式を再掲します。

Ijˉ=1N1i=1N1We(x0x1)f(x1x0x1)V(x0,x1)G(x0,x1)Le(x1x0)pX0(x0)pX1(x1)+1N2i=1N2We(x0x1)f(x1x0x1)f(x0x1x2)V(x0,x1)V(x1,x2)G(x0,x1)G(x1,x2)Le(x2x1)pX0(x0)pX1(x1)pX2(x2)+\begin{aligned} \bar{I_j} &= \frac{1}{N_1}\sum_{i=1}^{N_1}\frac{W_e(x_0 \rightarrow x_1)f(x_{-1}\rightarrow x_0 \rightarrow x_1)V(x_0, x_1)G(x_0, x_1)L_e(x_1\rightarrow x_0)}{p_{X_0}(x_0)p_{X_1}(x_1)} \\ &+ \frac{1}{N_2}\sum_{i=1}^{N_2}\frac{W_e(x_0 \rightarrow x_1)f(x_{-1}\rightarrow x_0 \rightarrow x_1)f(x_0 \rightarrow x_1 \rightarrow x_2)V(x_0, x_1)V(x_1, x_2)G(x_0, x_1)G(x_1, x_2)L_e(x_2\rightarrow x_1)}{p_{X_0}(x_0)p_{X_1}(x_1)p_{X_2}(x_2)} \\ &+ \cdots \end{aligned}

この形では(x0,x1,,xk)(x_0, x_1, \cdots, x_k)の各経路点を自由に生成することができます。しかし、もし本当に経路点を自由に生成したとすると、サンプリングされる経路の大半は可視関数V(x0,x1)V(x_0, x_1)\cdotsのせいで0に評価されてしまうでしょう。これは明らかに無駄な計算を行っています。

Path Tracingでは経路点を直接サンプリングするのではなく、方向ベクトルをサンプリングすることで間接的に経路点をサンプリングします

経路の始点x0x_0をサンプリングし、そこから先は方向ベクトルω0,\vec{\omega_0}, \cdotsをサンプリングすることで経路を以下のように生成することができます。

x1=r(x0,ω0)x2=r(x1,ω1)\begin{aligned} x_1 &= r(x_0, \vec{\omega_0}) \\ x_2 &= r(x_1, \vec{\omega_1}) \\ &\vdots \end{aligned}

上のモンテカルロ積分を確率密度関数の変換公式を用いると

Ijˉ=1N1i=1N1We(x0x1)f(x1x0x1)cosθ0Le(x1x0)pω0(ω0)+1N2i=1N2We(x0x1)f(x1x0x1)f(x0x1x2)cosθ0cosθ1Le(x2x1)pω0(ω0)pω1(ω1)+\begin{aligned} \bar{I_j} &= \frac{1}{N_1}\sum_{i=1}^{N_1}\frac{W_e(x_0 \rightarrow x_1)f(x_{-1}\rightarrow x_0 \rightarrow x_1)|\cos{\theta_0}|L_e(x_1\rightarrow x_0)}{p_{\omega_0}(\vec{\omega_0})} \\ &+ \frac{1}{N_2}\sum_{i=1}^{N_2}\frac{W_e(x_0 \rightarrow x_1)f(x_{-1}\rightarrow x_0 \rightarrow x_1)f(x_0 \rightarrow x_1 \rightarrow x_2)|\cos{\theta_0}||\cos{\theta_1}|L_e(x_2\rightarrow x_1)}{p_{\omega_0}(\vec{\omega_0})p_{\omega_1}(\vec{\omega_1})} \\ &+ \cdots \end{aligned}

となり、VVGGが消えてBRDFとコサイン項のみが残ることが分かります。(pX0(x0)p_{X_0}(x_0)が消えていることに注意。一般的なカメラの場合、x0x_0を定めるとω0\vec{\omega_0}が定まるため、逆にω0\vec{\omega_0}を定めるとx0x_0が定まってしまう。このときX0X_0の確率密度関数にデルタ関数が含まれるので、1とみなしてよい)

ここでさらに、方向ベクトルの確率密度関数をBRDFとコサイン項に比例するように取って重点的サンプリングを行えば、分散を大幅に減少させることができます。このようなサンプリング方法をBRDF Samplingと呼びます。

さらにPath Tracingでは経路の生成を逐次的に行うことで、計算コストを抑えて効率よくモンテカルロ積分を行います。どういうことかというと、N1=N2==NN_1 = N_2 = \cdots = Nとして、総和の順番を入れ替えることで(厳密には無限級数が絶対収束することを示さないといけない)、Ijˉ\bar{I_j}の計算が

Ijˉ=1Ni=1Nk=1We(x0ix1i){j=1kf(xj2ixj1ixji)cosθj}Le(xkxk1)pω0(ω0i)pωk(ωki)\bar{I_j} = \frac{1}{N}\sum_{i=1}^{N}\sum_{k=1}^{\infty}\frac{W_e(x_0^i \rightarrow x_1^i)\{\prod_{j=1}^{k}f(x_{j-2}^i\rightarrow x_{j-1}^i \rightarrow x_j^i)|\cos{\theta_j}|\}L_e(x_k \rightarrow x_{k-1})}{p_{\omega_0}(\vec{\omega_0}^i)\cdots p_{\omega_k}(\vec{\omega_k}^i)}

のように置き換えられます。この形では長さk=1k = 1ω0i\vec{\omega_0}^iをサンプリングすると、それが長さk2k \ge 2の経路の評価においても使われるようになっています。つまり、方向ベクトルのサンプリングを繰り返すことで、経路を1つずつ長くしていくということです。

具体的にはii番目のサンプル(ω0i,ω1i,)(\vec{\omega_0}^i, \vec{\omega_1}^i, \cdots)の評価において k=1k = 1では

We(x0ix1i)f(x1x0x1)cosθ0Le(x1x0)pω0(ω0i)\frac{W_e(x_0^i \rightarrow x_1^i)f(x_{-1} \rightarrow x_0 \rightarrow x_1)|\cos{\theta_0}|L_e(x_1 \rightarrow x_0)}{p_{\omega_0}(\vec{\omega_0}^i)}

を評価して加算し、k=2k = 2では

We(x0ix1i)f(x1x0x1)f(x0x1x2)cosθ0cosθ1Le(x2x1)pω0(ω0i)pω1(ω1i)\frac{W_e(x_0^i \rightarrow x_1^i)f(x_{-1} \rightarrow x_0 \rightarrow x_1)f(x_0 \rightarrow x_1 \rightarrow x_2)|\cos{\theta_0}||\cos{\theta_1}|L_e(x_2 \rightarrow x_1)}{p_{\omega_0}(\vec{\omega_0}^i)p_{\omega_1}(\vec{\omega_1}^i)}

を評価して加算していきます。k=3k = 3以降も同様です。

どこで経路を打ち切るかが問題になりますが、ここでロシアンルーレットを使用します。ロシアンルーレットの確率ppを設定し、各kkで項を評価する前に確率1p1 - pで項を評価を取りやめます。もし項を評価する場合は評価した値をpkp^kで割る必要があることに注意が必要です。

Path Tracingのアルゴリズム

まとめると、Path Tracingは以下のようなアルゴリズムです。

  1. レンズ上の点をサンプリングし、始点x0x_0を決める。このときω0\vec{\omega_0}も定まる。
  2. ロシアンルーレットを行う。打ち切りなら終了。
  3. x0x_0からω0\vec{\omega_0}にレイを飛ばし、衝突点や法線を求める。
  4. 長さkkの項の評価を行う
  5. BRDFを用いてω1\vec{\omega_1}の重点的サンプリングを行う。
  6. 2に戻り、同様の手順を繰り返す。