執筆者:廣瀬
映像業界を経てモノリスソフトへ入社。 以来、テクニカルアーティストとして主にエフェクト関連の業務を担当。 好きな食べものはソフトクリーム。
TECH BLOG
こんにちは。モノリスソフト テクニカルアーティストの廣瀬です。
今回はエフェクトのシミュレーションなんかを実装しようとすると出てくる
こいつ → ∆φ = f ポアソン方程式 をHoudiniで解く方法をいくつか解説していきたいと思います。
Houdiniに限らず、アプリケーションを問わず役立つ知識ですので暇な時にでも目を通してみてください。
ポアソン方程式とはラプラス方程式の解の部分が f のもののことを呼びます。
\( \nabla^2\phi = \Delta\phi = 0 \)
\( \nabla^2\phi = \Delta\phi = f \)
ポアソン方程式は物理現象のいたるところで出てきます。
\( \Delta\phi = -\frac{\rho}{\epsilon_0} \)
\( \Delta\phi = 4{\pi}G\rho \)
\( \frac{\partial v}{\partial t} + (v・\nabla)v = -\frac{1}{\rho}\nabla p + \nu\nabla^2v + g \)
\( \nabla・v = div \, v = 0 \)
エフェクトを担当されている皆さんの身近なところですと、流体シミュレーションの処理の一部で、もう少し具体的に説明しますと、上記のナビエ=ストークス方程式の 圧力項 (-1/ρ∇p) と 連続の式 (∇・v = div v = 0) を解く過程で、このポアソン方程式を解くことが行われていたりします。
行われている処理
実装はHoudiniの既存の機能だけで簡単にできるいくつかの方法で行っています。
Houdiniは並列計算を簡単に実装できるソフトですので、並列計算ができる方法であれば簡単に実装することができます。
また、numpyを呼び出したり、高速フーリエ変換用のノードも存在していますので、それらを用いた方法も簡単に実装することができます。
サンプルファイル(monolithtech_poisson.zip 24.6KB)はこちらです。
2次元の格子状のラプラシアン行列(グラフラプラシアン)を考えます。
ある点におけるグラフの接続性は以下のようになります。
\( L_{33} = \begin{matrix} [0]_{51} & - & [0]_{52} & - & [0]_{53} & - & [0]_{54} & - & [0]_{55} \\ | & & | & & | & & | & & | \\ [0]_{41} & - & [0]_{42} & - & [1]_{43} & - & [0]_{44} & - & [0]_{45} \\ | & & | & & | & & | & & | \\ [0]_{31} & - & [1]_{32} & - & [-4]_{33} & - & [1]_{34} & - & [0]_{35} \\ | & & | & & | & & | & & | \\ [0]_{21} & - & [0]_{22} & - & [1]_{23} & - & [0]_{24} & - & [0]_{25} \\ | & & | & & | & & | & & | \\ [0]_{11} & - & [0]_{12} & - & [0]_{13} & - & [0]_{14} & - & [0]_{15} \\ \end{matrix} \)
NOTE:
これは2次元ラプラス方程式の差分の式に相当します。
\( \Delta \phi(x,y) = \frac{\partial^2 \phi(x,y)}{\partial x^2} + \frac{\partial^2 \phi(x,y)}{\partial y^2} = \frac{\phi(x+1,y)+\phi(x-1,y)+\phi(x,y+1)+\phi(x,y-1)-4\phi(x,y)}{h^2} \)
上記のグラフの接続性は、位置(3,3) におけるグラフの接続性ですが、他のすべての点についても同様に考えられます。
\( L_{32} = \begin{matrix} [0]_{51} & - & [0]_{52} & - & [0]_{53} & - & [0]_{54} & - & [0]_{55} \\ | & & | & & | & & | & & | \\ [0]_{41} & - & [1]_{42} & - & [0]_{43} & - & [0]_{44} & - & [0]_{45} \\ | & & | & & | & & | & & | \\ [1]_{31} & - & [-4]_{32} & - & [1]_{33} & - & [0]_{34} & - & [0]_{35} \\ | & & | & & | & & | & & | \\ [0]_{21} & - & [1]_{22} & - & [0]_{23} & - & [0]_{24} & - & [0]_{25} \\ | & & | & & | & & | & & | \\ [0]_{11} & - & [0]_{12} & - & [0]_{13} & - & [0]_{14} & - & [0]_{15} \\ \end{matrix} \)
境界部分の点では境界条件を考える必要が出てきます。
これもポアソン方程式を解くうえで重要な要素となりますが、今回説明は割愛します。(値0のデイリクレ条件(固定境界)で実装していきます。)
\( L_{11} = \begin{matrix} [0]_{51} & - & [0]_{52} & - & [0]_{53} & - & [0]_{54} & - & [0]_{55} \\ | & & | & & | & & | & & | \\ [0]_{41} & - & [0]_{42} & - & [0]_{43} & - & [0]_{44} & - & [0]_{45} \\ | & & | & & | & & | & & | \\ [0]_{31} & - & [0]_{32} & - & [0]_{33} & - & [0]_{34} & - & [0]_{35} \\ | & & | & & | & & | & & | \\ [1]_{21} & - & [0]_{22} & - & [0]_{23} & - & [0]_{24} & - & [0]_{25} \\ | & & | & & | & & | & & | \\ [-4]_{11} & - & [1]_{12} & - & [0]_{13} & - & [0]_{14} & - & [0]_{15} \\ \end{matrix} \)
各点ごとのグラフの接続性が行(対称行列になるので列でもOK)となるように行列を構築することで、ラプラシアン行列を作成できます。
\( A = \begin{bmatrix} -4 & 1 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & ← L_{11} \\ \vdots & \ddots & & & & & & & & & & & & & & & & & & & & & & & \vdots & ← L_{..} \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 1 & -4 & 1 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & ← L_{32} \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 1 & -4 & 1 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & ← L_{33} \\ \vdots & & & & & & & & & & & & & & & & & & & & & & & \ddots & \vdots & ← L_{..} \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 1 & -4 & ← L_{55} \\ \end{bmatrix} \)
上記のラプラシアン行列 A と解の値である f とでポアソン方程式を解くことができます。
\( A\phi = f \)
上記をHoudiniで実装していきます。
一般的なヤコビ法は、反復的に値を更新していくことで連立方程式を解く方法として知られています。
これから解説するヤコビ法は上記の一般的なヤコビ法とは異なり、時間方向と空間方向の双方に対して離散化した式を使って解いていきます。ですので、一般的なヤコビ法とは解いている式が異なりますので注意してください。
一般的なヤコビ法については話の本筋とそれてしまうためここでは解説しませんので、詳しく知りたい方は「ヤコビ法」で検索してみてください。
直接法でポアソン方程式を解くことができましたが、上記で分かるように直接法で解くためには巨大な行列を構築する必要があります。
2次元の 64×64グリッドの場合は (64*64)2 = 16,777,216、
3次元の 64×64×64グリッドの場合は (64*64*64)2 = 68,719,476,736
と途方もない行列の要素数になってしまいます。
そこで反復法というもので、省メモリでポアソン方程式を解いていきます。
ポアソン方程式を解く場合のヤコビ法では、以下の2次元ポアソン方程式の陽的差分の式(2次元ラプラス方程式の差分の式に f の項が追加されたもの)に対して、CFL条件である k ≦ h2/4 を課して時間方向に値を更新することで解いていきます。
\( \begin{align} \frac{\phi_{t+1}(x,y)-\phi_{t}(x,y)}{k} &= \frac{\phi_{t}(x+1,y)+\phi_{t}(x-1,y)+\phi_{t}(x,y+1)+\phi_{t}(x,y-1)-4\phi_{t}(x,y)}{h^2}-f(x,y) \\ \phi_{t+1}(x,y) &= \frac{\phi_{t}(x+1,y)+\phi_{t}(x-1,y)+\phi_{t}(x,y+1)+\phi_{t}(x,y-1)}{4}-\frac{h^2 f(x,y)}{4} \\ \end{align} \)
また、ポアソン方程式を反復法で解く場合の境界条件は、値が存在しない箇所(ボクセル)に任意の値を割り当てることで設定できます。
値 0 のデイリクレ条件(固定境界)の場合は、値が存在しない箇所(ボクセル)の値を 0 とすることで実装することができます。
上記をHoudiniで実装していきます。
多くの並列での反復計算が必要なため OpenCL SOP でGPUを使い計算します。
次に同じ反復法であるガウス=ザイデル法とSOR法について実装していきます。
ここでも、一般的なガウス=ザイデル法とSOR法とは解いている式が異なりますので注意してください。
ヤコビ法とガウス=ザイデル法の違いは値の更新の仕方です。ヤコビ法では上下左右のセルの値は1つ前の反復の値を参照していましたが、ガウス=ザイデル法では上下左右セルのうち2つのセルは現在の反復で更新した値を使用することで収束を早めます。
逐次計算の場合は全グリッドのループ中に φ の配列を上書きしていくだけで簡単に実装できますが、並列計算の場合は少し工夫が必要となります。並列計算ではチェス盤の赤マス・黒マスのように値を更新していくことでガウス=ザイデル法を実現します。それにちなんでこの計算方法はRed-Black ガウス=ザイデル法などと呼ばれます。
SOR法はガウス=ザイデル法に加速パラメータ ω を追加することで、より収束を高速化させます。ω の値が1の場合はガウス=ザイデル法と等価になります。
\( \phi_{i+1}(x,y) = \phi_{i}(x,y) + \omega(\tilde{\phi}_{i+1}(x,y) - \phi_{i}(x,y)) \\ \)
ということで、ガウス=ザイデル法とSOR法をHoudiniで実装していきます。
ポアソン方程式を解くのにグリーン関数(高速フーリエ変換)を使う方法もあります。今回は、重力ポテンシャルを求めてみます。
以下の手順で重力ポテンシャルを求められます。
3次元重力場のポアソン方程式は以下です。(G は重力定数、ρ は密度分布)
\( \Delta\Phi = -4{\pi}G{\rho} \\ \)
3次元ポアソン方程式のグリーン関数の定義は以下です。
\( G(r,r^{\prime}) = \frac{1}{4{\pi}|r-r^{\prime}|} \\ \)
重力ポテンシャル Φ は、密度分布 ρ とグリーン関数 G(r) を畳み込み積分することで求められます。
\( \begin{align} \Phi(r) &= -4{\pi}G\int\rho(r^{\prime})\frac{1}{4{\pi}|r-r^{\prime}|}dr^{\prime} \\ &= -4{\pi}G(\rho*G)(r) \\ \end{align} \)
畳み込み積分はフーリエ変換の結果を乗算することで求められます。F はフーリエ変換です。
\( \mathscr{F}(\rho*G) = \mathscr{F}(\rho)\mathscr{F}(G) \\ \)
よって、重力ポテンシャルは 密度分布 ρ をフーリエ変換した値 ρ~ と、グリーン関数 G をフーリエ変換した値 G~ と、-4πG を掛け合わせた値(Φ~)を逆フーリエ変換することで求められます。
\( \tilde{\Phi}(k) = -4{\pi}G\tilde{\rho}(k)\tilde{G}(k) \\ \)
また、上記の方法で何もせずにポアソン方程式を解いた場合の境界条件は周期境界条件となります。
今回は、孤立境界条件で解くためにゼロパディングを使用します。
下の図のように3Dボリュームを8分割したうちの1つの象限に密度分布を配置し、他の象限の密度分布を0とするとこで孤立境界条件で解くことができます。
上記をHoudiniで実装していきます。
今回はHoudiniでポアソン方程式を色々な方法で解いてみました。
Houdiniでは並列計算が容易に実装できることもあり、並列化できる方法であれば、簡単に高速で動作する処理を作成することができます。
ノード単位で処理を分割することで、処理の順番を簡単に入れ替えたり、処理を行った結果を簡単に視覚化することもできます。
ですので、著者はHoudiniを数式や論文を実装するにあたってのプロトタイピングツールとしても使ったりしています。
また、今回ポアソン方程式を解くいろいろな方法を紹介しましたが、方法による優劣はなく目的によって最適な方法を使い分けるのが重要だと著者は考えています。
例えば、リアルタイムでのシミュレーションが目的で解の精度を必要としないなら、ヤコビ法などで数回の反復で計算を打ち切ってしまうのも手です。
正確な解が欲しくてメモリに余裕があるようでしたら直接法で解くのもまた手です。
マシンのメモリや、計算速度、計算精度などと相談して、ケースバイケースで最適な方法を探ってみてください。
この記事がポアソン方程式を解く機会があったときの手助けになれば幸いです。
執筆者:廣瀬
映像業界を経てモノリスソフトへ入社。 以来、テクニカルアーティストとして主にエフェクト関連の業務を担当。 好きな食べものはソフトクリーム。