SH(LT発表スライド)
UnityCG勉強会 2021/10/24 LT「SH」
自己紹介
shivaduke
シバヅケ
ゴール
UnityにおけるSHの雰囲気がわかるようになる
UnityCG.cgincのSH
Rendering.SphericalHarmonicsL2構造体
球面
球面
$ S=\{(x,y,z)\in \mathbb{R}^3 \mid x^2+y^2+z^2=1 \}
$ Sの点を$ \omegaで書くことがある
https://upload.wikimedia.org/wikipedia/commons/thumb/c/c0/Spherical_with_grid.svg/1024px-Spherical_with_grid.svg.png
範囲
$ 0 \leq \theta \leq \pi
$ 0 \leq \phi \leq 2\pi
変換
$ x= \sin\theta \sin \phi
$ y = \sin \theta \cos \phi
$ z = \cos\theta
球面上の関数
$ f(x,y,z)
$ f(\omega)
$ f(\theta, \phi)
球面上の積分
$ \int_S f(\omega) d\omega = \int_{\theta=0}^\pi\int_{\phi=0}^{2\pi}f(\theta,\phi)\sin\theta d\theta d\phi
$ 球の面積 = \int_S 1 d\omega = 4\pi
SH
Spherical Harmonics
球面調和(関数)
球面上のとても由緒正しく性質の良い関数たち
$ Y_l^m
$ l=0,1,2,\dots
$ |m|\leq l
https://upload.wikimedia.org/wikipedia/commons/6/62/Spherical_Harmonics.png
SH関数の特徴
内積
球面上の関数$ f, gに対して、
$ \langle f, g \rangle = \int_S f(\omega)g(\omega)d\omega
正規直交
$ \langle Y_l^m, Y_{l'}^{m'} \rangle = \begin{cases} 1 & m = m' \;\text{and}\; l=l'\\ 0 & \text{otherwise}\end{cases}
SH射影
$ f_l^m = \langle f, Y_l^m \rangle = \int_S f(\omega)Y_l^m(\omega)d\omega とすると、
$ f(\omega) = \sum_{l = 0}^\infty \sum_{m = -l}^l f_l^m Y_l^m (\omega)
いくつかのユースケースでは$ l=2(や$ l = 4)で十分な近似を得ることができる。
与えられた$ fに対して、9つの係数$ f_0^0,f_1^{-1},f_1^{0},f_1^{1},f_2^{-2},f_2^{-1},f_2^{0},f_2^{1},f_2^{2},を計算すれば、ある程度の精度で$ fを復元できる。
例:放射照度環境マップ
サーフェスの点$ \mathbf{p}の法線を$ \mathbf{n}、アルベド色を$ \rho(\mathbf{p})とする。
平行光源(ディレクショナルライト)の方向を$ \omega_0、色を$ c_0としたときの、点$ \mathbf{p}でのランバート反射
$ B(\mathbf{p},\mathbf{n}) = \pi \rho(\mathbf{p}) \max(0,\mathbf{n}\cdot\omega_0)c_0
光源が環境マップなどから与えられる場合
$ L(\omega)を$ \omega方向からの放射輝度としたときの、点$ \mathbf{p}でのランバート反射
$ B(\mathbf{p},\mathbf{n}) = \rho(\mathbf{p})\int_S \max(0, \mathbf{n}\cdot\omega)L(\omega) d\omega
環境マップが32x32ピクセルx6面とすると
$ B(\mathbf{p},\mathbf{n})= \rho(\mathbf{p})\times
$ \sum_{f=1}^6\sum_{u,v=1}^{32}\max(0,\mathbf{n}\cdot\omega(f,u,v))L(f,u,v) d\omega(f,u,v)
32*32*6=6144
とてもリアルタイムで計算できない。
放射照度環境マップ
入力となる光の情報が点$ \mathbf{p}に依存しない場合(共通の環境マップを使用する場合)
$ E(\mathbf{n})=\int_S \max(0, \mathbf{n}\cdot\omega)L(\omega) d\omega は$ \mathbf{n}だけの関数で次を満たす。
$ B(\mathbf{p},\mathbf{n}) = \rho(\mathbf{p})E(\mathbf{n})
$ E(\mathbf{n})を事前に計算して、テクスチャとして保存する。
放射照度環境マップ(Irradiance Environment Map)という。
困ること
テクスチャの生成に時間がかかる
テクスチャなので沢山使えない
SHを使う
R. Ramamoorthi and H. Hanrahan, An Efficient Representation for Irradiance Environment Maps, SIGGRAPH 2001.
左:真面目に計算した放射照度環境マップ、右:SH(L2まで)射影
https://cseweb.ucsd.edu/~ravir/papers/envmap/Figs/grace_filter.jpg https://cseweb.ucsd.edu/~ravir/papers/envmap/Figs/grace_filtermat.jpg
$ l=2までのSHで十分足りる
$ E(\mathbf{n})は9つのパラメータで十分復元できる(誤差1%くらいらしい?)
(※HDRだと$ l=4までほしくなる場合があるらしい)
$ E(\mathbf{n})が、低周波(角度が変わっても値が緩やかにしか変わらない)から成り立つ。
放射照度環境マップ3
$ E(\theta, \phi) = \sum_{l = 0}^\infty \sum_{m = -l}^l E_l^m Y_l^m (\theta, \phi)
$ L(\theta, \phi) = \sum_{l = 0}^\infty \sum_{m = -l}^l L_l^m Y_l^m (\theta, \phi)
$ A(\theta) = \max(0, \cos\theta) = \sum_{l=0}^\infty A_l^0 Y_l^0(\theta,0)
とすると、畳み込み積分の形でかけて
$ E(\theta,\phi) = A \ast L(\theta,\phi)
SHの良い性質などを使うと以下が成り立つ
$ E_l^m = \sqrt{\frac{4\pi}{2l+1}} A_l^0 L_l^m
特にディレクショナルライト(方向:$ \omega_0、色: $ c)のとき
$ L_l^m = \pi c Y_l^m(\omega_0)
Unityでの扱い
SphericalHarmonicsL2
code:cs
var sh = new SphericalHarmonicsL2();
var omega = new Vector3(1,0,0);
var c = new Color(1,1,1);
var intensity = 1f;
sh.AddDirectionalLight(omega, c, intensity);
var colorIndex = 0; // r=0,g=1,b=2
var shIndex = 0; // 0 ~ 8
AddDirectionalLightを自前で実装したもの
Skybox -> SHはUnityが自動でやっている様子
シーンにライトプローブがない状態で"LightMode = FowardBase"にすると自動で渡される
https://gyazo.com/7a7796717fcde2b055b6aa802f1b47f1
Editorでは自動で更新してくれてるが、ランタイムで更新するにはDynamicGI.UpdateEnvironment()を実行する必要がある(内的にはSHの計算をしているはずなのでそこそこ負荷があるはず + 同期的かは不明)
References
UnityのライトプローブのSHの仕様説明
↓のStupidSHを採用していることが書かれている
Stupid SH
元ネタ。幅広いしサンプルコードもある。難しめ。
GPU Gem。放射照度環境マップについて書かれてる
放射照度環境マップにSHを使う方法を提案した論文
読みやすい。$ A_l^0の値はここに書いてある
球面調和関数の一覧が書いてある
複素版と実版が書いてて便利
SH -> Shaderのやりかたが書いてる
多分Rendererがやっているのと同じ処理だと思う
係数の順番が変わっているので気を付ける
AddDirectionalLightを自前で実装したもの
SHとか$ A_l^0とかを定数で書いてる
間違ってたらすいません