簡単な多重重点サンプリングの検証の続き。
早く綺麗な絵をレイトレで描こうとした時に、ハマって確認した内容を残しておく。 直接レイトレは扱わないが、暗いシーンに限られた光源が置かれているようなシーンにおいては、収束を早くするため、ライト方向の拡散や反射を重点サンプリングすることがある。
https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html#samplinglightsdirectly
参考にGLSLに書き下ろしたもの。 https://www.shadertoy.com/view/WlycW3
この光源への重点サンプリング、 単独で積分して期待値を求めると違う値に収束するので、適切に混ぜないとちゃんと収束しない。
拡散(ランバート反射)とライトの重点サンプリングのを混合した多重重点サンプリング
まず、ライト方向だけで重点サンプリングした場合は異なる値に収束することが確認できる。 それに対して、cosine分布との多重重点サンプリングした場合には、同じ値に収束する。
ただ、ライト方向を加えた多重重点サンプリングの場合は、cosine分布のみの重点サンプリングより収束が遅くなっていることがわかる。 これは期待する収束値に対してライト方向のサンプリング値が小さく乖離した値であるため、収束への貢献が小さいためであると考えられる。
通常はライト方向にサンプリングは、大きな値(明るい色)が取得できるため、収束が早まることが期待できる。
検証のためのコード
サンプリングの起点は、原点(0, 0, 0)。 ライトの重点サンプリングは、ライト上の点に向かう反射だけをサンプリングするもの。 ライトは、(1, 5, 1)を中心として、XZ平面に幅light_w=2、奥行きlight_d=2にあると仮定しており、 PDFは確率密度関数。
import math import random import numpy as np import matplotlib.pyplot as plt def main(): nv = np.array([0, 1, 0]) # ベクトルを正規化する def normalize(v): l = np.linalg.norm(v) return v / l # 最小値と最大値を指定して乱数を生成する def random_double(min_v, max_v): return min_v + (max_v - min_v) * random.random() # 球内の点を一様分布で生成 def random_in_sphere(): while True: x = random_double(-1,1) y = random_double(-1,1) z = random_double(-1,1) dist_sq = x * x + y * y + z * z if dist_sq > 0 and dist_sq < 1: return np.array([x, y, z]) # ランバート反射方向(cosine分布)を生成 def random_lambertian(): return normalize(nv + normalize(random_in_sphere())) # ランバート反射の確率密度関数 def cosine_pdf(v): cosine = np.dot(nv, normalize(v)) return 0 if cosine <= 0 else cosine / math.pi light_p = np.array([1, 5, 1]) light_nv = np.array([0, -1, 0]) light_w = 2 light_d = 2 # 正方形のライト内部の点を一様分布で生成 def random_in_light(): x = light_p[0] + random_double(-light_w / 2, light_w / 2) y = light_p[1] z = light_p[2] + random_double(-light_d / 2, light_d / 2) return normalize(np.array([x, y, z])) # ライト内部の点の確率密度関数 def light_pdf(v): x, y, z = v t = light_p[1] / y if t <= 0: return 0 cx = x * t cz = z * t if cx < light_p[0] - light_w / 2 or cx > light_p[0] + light_w / 2: return 0 if cz < light_p[2] - light_d / 2 or cz > light_p[2] + light_d / 2: return 0 dist_sq = t * t cosine = np.dot(v, light_nv) return dist_sq / (4 * abs(cosine)) def sampling_cosine(n): s = 0.0 nv = np.array([0, 1, 0]) seq = [] for i in range(1, n+1): v = random_lambertian() cosine = np.dot(v, nv) pdf_val = cosine_pdf(v) if pdf_val > 0: s += math.pow(cosine, 2) / pdf_val seq.append(s / i) return seq def sampling_light(n): s = 0.0 seq = [] for i in range(1, n+1): v = random_in_light() cosine = np.dot(nv, v) pdf_val = light_pdf(v) if pdf_val > 0: s += math.pow(cosine, 2) / pdf_val seq.append(s / i) return seq def sampling_mix_cosine_and_light(n): s = 0.0 mix_rate = 0.5 seq = [] def random_mix_direction(): if random.random() < mix_rate: return random_in_light() else: return random_lambertian() def mix_pdf(v): return (1 - mix_rate) * cosine_pdf(v) + mix_rate * light_pdf(v) for i in range(1, n+1): v = random_mix_direction() cosine = np.dot(nv, v) pdf_val = mix_pdf(v) if pdf_val > 0: s += math.pow(cosine, 2) / pdf_val seq.append(s / i) return seq # main body n = 10000 sampling=5 colors = ('#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf') col_idx = 0 for i in range(sampling): if i == 0: label='cosine' else: label=None plt.scatter(range(1, n+1), sampling_cosine(n), s=2, c=colors[col_idx], marker='o', label=label) col_idx += 1 for i in range(sampling): if i == 0: label='light' else: label=None plt.scatter(range(1, n+1), sampling_light(n), s=2, c=colors[col_idx], marker='o', label=label) col_idx += 1 for i in range(sampling): if i == 0: label='mix' else: label=None plt.scatter(range(1, n+1), sampling_mix_cosine_and_light(n), s=2, c=colors[col_idx], marker='o', label=label) col_idx += 1 plt.scatter(range(1, n+1), [math.pi*2/3]*n, c=colors[col_idx], s=1, marker='.', label='ans') plt.xscale('log') plt.grid(which='major',color='black',linestyle='-') plt.grid(which='minor',color='gray',linestyle='-') plt.legend() plt.savefig('chart.png') main()
とはいえ、いろいろ前提の説明は足りないので詳しくは原著にあたってください。
https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html#samplinglightsdirectly