GraphicsLab Project 之 Curl Noise

做者:i_dovelemonhtml

日期:2020-04-25算法

主题:Perlin Noise, Curl Noise, Finite Difference Methodsession

 

引言

        最近在研究流体效果相关的模拟。通过一番调查,发现不少的算法都基于必定的物理原理进行模拟,计算量相对来讲都比较高昂。最终寻找到一个基于噪音实现的,可在视觉上模拟流体效果的方法:Curl Noise。题图就是经过 Curl Noise 模拟的流体向量场控制的百万粒子的效果。curl

 

背景知识

        在讲解什么是 Curl Noise 以前,咱们须要了解一些相关背景知识。ide

 

向量场(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}$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

 

散度和旋度(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 Flow

相关文章
相关标签/搜索