图形学——Make Some Noise

发表于2018-04-23
评论0 4.3k浏览
周末逛Unity Assets 商店的时候发现一个免费的体积云,下下来看了下代码发现是procedural noise + ray maching做的, 看到noise代码那块发现自己噪声都忘的差不多了,正好趁机会梳理下吧。

1、PerlinNoise

理论方面因为懒得翻论文就直接参考了乐乐姐的博客,其中perlin noise的主要描述如下:
  • 定义一个晶格结构,每个晶格的顶点有一个“伪随机”的梯度向量(其实就是个向量啦)。对于二维的Perlin噪声来说,晶格结构就是一个平面网格,三维的就是一个立方体网格。
  • 输入一个点(二维的话就是二维坐标,三维就是三维坐标,n维的就是n个坐标),我们找到和它相邻的那些晶格顶点(二维下有4个,三维下有8个,n维下有2n个),计算该点到各个晶格顶点的距离向量,再分别与顶点上的梯度向量做点乘,得到2n个点乘结果。
  • 使用缓和曲线(ease curves)来计算它们的权重和。在原始的Perlin噪声实现中,缓和曲线是s(t)=3t2?2t3,在2002年的论文6中,Perlin改进为s(t)=6t5?15t4+10t3。这里简单解释一下,为什么不直接使用s(t)=t,即线性插值。直接使用的线性插值的话,它的一阶导在晶格顶点处(即 t=0 或 t=1)不为0,会造成明显的不连续性。s(t)=3t2?2t3在一阶导满足连续性,s(t)=6t5?15t4+10t3 在二阶导上仍然满足连续性。

前两条很简单,就是针对每一个点,在晶格顶点上计算方向导数, 第三条我个人觉得描述是不严谨的,我们将问题简化到一维:

在一维的情况下,晶格就变成了线段,顶点到P的方向导数在P的定义域上离散分布,要使函数平滑则其必须处处可导,因此,我们设:

因为在进行插值的时候,前一个晶格的1在下一个晶格看来是0, 因此调和函数要满足的是其一阶导数在 01 这个特殊的点上可导,即:f′(0)=f′(1)

当 
,其一阶导数在 0 和 1上的值为0,故 f(t) 处处可导

此时s′(t)的二阶导数在0 和1 上的值分别为6 和 -6, 因此,f′(t)在 01 处不可导

则是一二阶导数在0 和 1 上都为0,故此时f′(t)处处可导

在12x12 晶格的情况下,其效果分别如下:
                                                                     s(t)=t


2、Simplex Noise

Simplex噪声也是一种基于晶格的梯度噪声,它和Perlin噪声在实现上唯一不同的地方在于,它的晶格并不是方形(在2D下是正方形,在3D下是立方体,在更高纬度上我们称它们为超立方体,hypercube),而是单形,simplex。那么什么是单形呢?

通俗解释单形的话,可以认为是在N维空间里,选出一个最简单最紧凑的多边形,让它可以平铺整个N维空间。我们可以很容易地想到一维空间下的单形是等长的线段(1-单形),把这些线段收尾相连即可铺满整个一维空间。在二维空间下,单形是三角形(2-单形),我们可以把等腰三角形连接起来铺满整个平面。三维空间下的单形,即3-单形就是四面体。更高维空间的单形也是存在的。

因为使用了单形,因此每个点的值不再是通过插值得到,而是通过所属单形的顶点处的值按一定权重混合叠加得到,每个顶点的噪声贡献度为:

下面的问题就是如何找到顶点

具体推导可以参考 catlike 的教程, 这个把N维空间下的单形网格变形成新网格的公式如下:

同理逆变换的公式如下:

这样的变换我们可以通过旋转矩阵和非均匀缩放矩阵实现,在二维的情况下,以Unity中的Quad为例,先逆时针旋转45°,然后将Y方向缩放为之前的1/3–√ 倍,即可得到:

在二维情况下,我分别用上面的两种变换方式进行了实现,如图所示:


在用矩阵实现的过程中我发现,生成的时候少做一次变换,产生的噪声类似水流的效果

将灰度值作为高度,着色后的效果如下:

实践

上图是 Unity 中通过 raytrace 绘制的经过 fbm 处理的 3D 柏林噪声等位面, 其中 fbm 是分型布朗运动(Fractal Brownian Motion)的缩写, 即将多个不同频率、振幅的基础噪声叠加已得到最终结果。 3D 柏林噪声则是按照上面的理论从二维扩展导三维, 标准实现可参考 iq 的代码。
来自:https://blog.csdn.net/notmz/article/details/77413131

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: