1. 程式人生 > >GraphicsLab Project 之 Curl Noise

GraphicsLab Project 之 Curl Noise

作者:i_dovelemon

日期:2020-04-25

主題:Perlin Noise, Curl Noise, Finite Difference Method

 

引言

        最近在研究流體效果相關的模擬。經過一番調查,發現很多的演算法都基於一定的物理原理進行模擬,計算量相對來說都比較高昂。最終尋找到一個基於噪音實現的,可在視覺上模擬流體效果的方法:Curl Noise。題圖就是通過 Curl Noise 模擬的流體向量場控制的百萬粒子的效果。

 

背景知識

        在講解什麼是 Curl Noise 之前,我們需要了解一些相關背景知識。

 

向量場(Vector Field)

        一個2D 或者 3D 的向量場,表示的是賦予空間中任意點一個 2D 或者 3D 向量的函式。公式表示如下所示:

$\vec{F}\left(x,y \right)=P\left(x,y\right)\vec{i}+Q\left(x,y\right)\vec{j}$

$\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}$

        其中,$P$,$Q$,$R$ 各表示一個標量函式,即它們的返回值是一個標量;$\vec{i}$,$\vec{j}$,$\vec{k}$ 各表示一個基向量。(參考文獻[1])

        上面數學的解釋大家可能不熟悉,但是很多人或多或少的都看過向量場的圖片形式,如下所示:

 

散度和旋度(Curl and Divergence)

        首先,我們來定義一個 $\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 Noise,即是對一個隨機向量場,進行 Curl 操作之後得到的新場。因為滿足散度為 0 的特性,所以這個場看上去就具有流體的視覺特性。如果用這個場作為速度去控制粒子,即可得到開頭視訊中流動的效果。

 

2D Curl Noise

        前面我們說過,需要一個隨機的向量場。這裡我們使用 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)來近似求解。

 

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 控制粒子運動的效果:

 

3D 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

[3] Section 5-4:Cross Product

[4] Section 6-1:Curl And Divergence

[5] Curl-Noise for Procedural Fluid