做者:i_dovelemonhtml
日期:2020-04-25算法
主題:Perlin Noise, Curl Noise, Finite Difference Methodsession
最近在研究流體效果相關的模擬。通過一番調查,發現不少的算法都基於必定的物理原理進行模擬,計算量相對來講都比較高昂。最終尋找到一個基於噪音實現的,可在視覺上模擬流體效果的方法:Curl Noise。題圖就是經過 Curl Noise 模擬的流體向量場控制的百萬粒子的效果。curl
在講解什麼是 Curl Noise 以前,咱們須要瞭解一些相關背景知識。ide
一個2D 或者 3D 的向量場,表示的是賦予空間中任意點一個 2D 或者 3D 向量的函數。公式表示以下所示:函數
$\vec{F}\left(x,y \right)=P\left(x,y\right)\vec{i}+Q\left(x,y\right)\vec{j}$ui
$\vec{F}\left(x,y,z \right)=P\left(x,y,z\right)\vec{i}+Q\left(x,y,z\right)\vec{j}+R\left(x,y,z\right)\vec{k}$url
其中,$P$,$Q$,$R$ 各表示一個標量函數,即它們的返回值是一個標量;$\vec{i}$,$\vec{j}$,$\vec{k}$ 各表示一個基向量。(參考文獻[1])spa
上面數學的解釋你們可能不熟悉,可是不少人或多或少的都看過向量場的圖片形式,以下所示:code
首先,咱們來定義一個 $\nabla$ 操做,以下所示:
$\nabla=\frac{\partial }{\partial x}\vec{i}+\frac{\partial }{\partial y}\vec{j}+\frac{\partial }{\partial z}\vec{k}$
其中$\partial$表示的是偏導數符號,不熟悉的讀者能夠去複習下微積分或者參考文獻[2]。有了這個操做符以後,咱們定義旋度爲:
$curl\vec{F}=\nabla\times\vec{F}=(\frac{\partial R}{\partial y}-\frac{\partial Q}{\partial z},\frac{\partial P}{\partial z}-\frac{\partial R}{\partial x},\frac{\partial Q}{\partial x}-\frac{\partial P}{\partial y})$
其中$\times$爲叉積操做符(參考文獻[3])。
有了旋度以後,咱們再來定義散度,一樣的,公式以下所示:
$div\vec{F}=\nabla\cdot \vec{F}=\frac{\partial P}{\partial x}+\frac{\partial Q}{\partial y} + \frac{\partial R}{\partial z}$
特別的,散度和旋度之間有以下的一個關係:
$div(curl\vec{F})=0$
以上內容,參考文獻[4]。
根據上面的公式,咱們能夠知道,對於一個向量場的旋度場,它的散度爲 0,即它是一個無源場(Divergence-Free)。而一個散度爲 0 的向量場,表示這個場是不可壓縮的流體,這對平常所見的流體來講是一個很重要的視覺性質,因此據此咱們可使用一個場的旋度場來模擬流體效果。
所謂 Curl Noise,便是對一個隨機向量場,進行 Curl 操做以後獲得的新場。由於知足散度爲 0 的特性,因此這個場看上去就具備流體的視覺特性。若是用這個場做爲速度去控制粒子,便可獲得開頭視頻中流動的效果。
前面咱們說過,須要一個隨機的向量場。這裏咱們使用 Perlin Noise 來進行模擬,關於 Perlin Noise 網上一堆資料,這裏就再也不贅述。
咱們假設 Perlin Noise 的函數爲:
$N(x,y)$
它的返回值是一個標量值。而後據此創建一個新的向量場:
$\vec{F}(x,y) = (N(x,y), N(x,y))$
而後對這個新的向量場進行 Curl 操做,便可獲得旋度場。
前面只說過 3D 狀況下的 Curl 操做是怎麼樣的,這裏給出 2D 版本的 Curl 操做:
$curl\vec{F}(x, y) = (\frac{\partial N(x,y)}{\partial y}, -\frac{\partial N(x,y)}{\partial x})$
這裏就只剩下了最後一個問題,那就是形如 $\frac{\partial N(x,y)}{\partial x}$ 這樣的偏導數,該怎麼計算。咱們這裏使用一個名爲有限差分的方法(Finite Difference Method)來近似求解。
根據文獻[2]中對於偏導數的描述,咱們知道 $\frac{\partial N(x,y)}{\partial x}$ 只是一種表達方式,它的精確表示方法爲:
$\frac{\partial N(x,y)}{\partial x}= N_x(x,y) = \lim_{h\to0}{\frac{N(x + h,y)-N(x,y)}{h}}$
然後面極限的表達方式則給了咱們近似計算這個偏導數的方法,只要給定一個較小的 $h$ 值,就可以近似的獲得偏導數的結果。而這種計算方法即爲:有限差分方法(Finite Difference Method)。
除了上面的極限表示方法以外,還有另一種極限表示方法,以下所示:
$\frac{\partial N(x,y)}{\partial x}= N_x(x,y) = \lim_{h\to0}{\frac{N(x,y)-N(x-h,y)}{h}}$
這兩種差分方法分別稱之爲前向差分(Forward Difference)和逆向差分(Backward Difference)方法。我這裏主要使用逆向差分方法。
有了計算偏導數的方法以後,咱們就能夠實際帶到 2D Curl 操做的公式進行計算,以下是計算 2D Curl Noise 的僞代碼:
vec2 computeCurl(float x, float y) { float h = 0.0001f; float n, n1, n2, a, b; n = N(x, y); n1 = N(x, y - h); n2 = N(x - h, y); a = (n - n1) / h; b = (n - n2) / h; return vec2(a, -b); }
知道怎麼計算 2D Curl Noise 以後,咱們用計算出來的 Curl Noise 做爲速度場去控制粒子進行運動,以下是 2D Curl Noise 控制粒子運動的效果:
有了前面 2D Curl Noise 的實現,如法炮製的實現 3D Curl Noise 的推導。
3D Perlin Noise 函數定義爲:
$N(x,y,z)$
以此構造出來的 3D 向量場爲:
$\vec{F}(x,y,z)=(N(x,y,z),N(x,y,z)N(x,y,z))$
對這個場進行 Curl 操做,獲得:
$curl\vec{F}=(\frac{\partial N(x,y,z)}{\partial y}-\frac{\partial N(x,y,z)}{\partial z},\frac{\partial N(x,y,z)}{\partial z}-\frac{\partial N(x,y,z)}{\partial x},\frac{\partial N(x,y,z)}{\partial x}-\frac{\partial N(x,y,z)}{\partial y})$
據此,給出計算 3D Curl Noise 的僞代碼:
vec3 computeCurl(float x, float y) { vec3 curl; float h = 0.0001f; float n, n1, a, b; n = N(x, y, z); n1 = N(x, y - h, z); a = (n - n1) / h; n1 = N(x, y, z - h); b = (n - n1) / h; curl.x = a - b; n1 = N(x, y, z - h); a = (n - n1) / h; n1 = N(x - h, y, z); b = (n - n1) / h; curl.y = a - b; n1 = N(x - h, y, z); a = (n - n1) / h; n1 = N(x, y - h, z); b = (n - n1) / h; curl.z = a - b; return curl; }
如下是根據獲得的 3D Curl Noise,並一次控制粒子進行運動的效果:
Curl Noise 在遊戲中有大量的運用,Unity 的粒子系統的 Noise Module 就內置了 Curl Noise 的實現。做爲遊戲開發的人員,頗有必要了解下這個技術的原理,便於在實際開發中靈活運用。本文的主要原理來自於參考文獻[5],感興趣的能夠深刻去了解。
[1] Section 5-1 : Vector Field
[2] Section 2-2:Partial Derivatives