噪声贴图现在已经有许多种类,比如值噪声(value noise)、柏林噪声(perlin noise)、单形噪声(simplex noise)等,这些噪声在随机的基础上,明显遵守了一定的规则,展现出了某种特点。
Cpu这边,许多库都可以提供产生随机数的函数,其中有些是确定性随机,即每次输入的相同的值,输出的随机数都会是一样的;有些是非确定性随机,即每次输入相同的值,输出的随机数几乎一定是不同的。
一般来讲,生成噪声倾向于使用确定性随机,这样一来,每一次生成的结果就会是唯一且确定的,更加利于控制随机的效果。
GPU这边,可以通过哈希函数来获取随机。
这是一个经典的随机函数,输入二维变量,通过点乘、三角函数和取小数
1 | |
但是在稍大一些的范围时,该函数会给出重复的结果,这个ShaderToy列举了一些更好的hash函数
https://www.shadertoy.com/view/4djSRW
白噪声(White Noise)
如果把随机值直接作为结果返回颜色,就可以获得白噪声
1 | |

添加一项缩放系数,对噪声进行初步控制
1 | |
当_Scale为4时,结果如下:
上面的代码是把uv[0,1]^2^转换为[0,4]^2^,然后再取整数部分,使得[n,n + 1)的值都为n
如此一来,相当于把整个uv分为4*4=16个区域,每一个区域中的uv值都是相同的,根据uv生成的噪声自然也是相同的。
值噪声(Value Noise)
上面使用了Scale的白噪声,每个区域内的值都是该区域左下那一点的值,区域内的值是相同的。
为了让让每个区域内的值不再相同,有些变化,值噪声在上面的基础上,通过插值,获得了有过渡的结果。
具体做法是根据一个uv的整数,获取其所在区域内的四个顶点的值,根据其小数部分,用那四个值进行双线性插值。
1 | |
使用线性插值的结果,过于锐利,不够平滑,改为使用smoothstep平滑插值
1 | |
smoothstep实际上是一个三次多项式函数,在许多生成噪声的代码里使用的是下面的式子,和smoothstep是等价的。
1 | |
还有一些其他的多项式插值函数,后面在表。
梯度噪声
值噪声的结果不免有些过于方正,不能很好的模拟自然界的随机产物,为了消除这些“方正”的效果1985年Ken Perlin开发了一种噪声算法——梯度噪声。
Perlin Noise
Perlin 噪声是梯度噪声的一种类型,因为它是Ken Perlin提出的第一种梯度噪声,所以被以Perlin的名字命名。
不同于值噪声直接使用一个区域的四角的随机值进行插值,Perlin噪声会为一个区域的四角分别生成一个随机方向(2D的就是float2),然后计算输入点分别到四角的方向,然后一一对应计算他们的点积,用这计算得来的4个点积进行插值。
下面的代码不是正宗的Perlin噪声算法,在获取随机方向的部分使用了简化的方法。
1 | |
Simplex Noise
Ken Perlin在2001年提出的噪声算法,比起之前的噪声,它不再使用四个角的值进行插值,而是改为从三个点,划分区域时,也不再划分为方格,而是划分为三角形。
这么做的好处是顶点少了后,需要的计算就少了非常多,这在2D上只是少了一个点,在3D上就少了4个!在更高维会少得更多。(确切说是由2^n^ 减少到了n^2^)
自然而然我们希望用等边三角形铺满纹理,因为生成得噪声应该是没有方向性的,这就需要正三角形。那如何获得等边三角形的区域?
方法是先将四角方格分为两个等腰直角三角形,然后再把三角形拉成等边三角形
通过比较x和y得大小,可以判断这个点是在上三角形还是在下三角形中。
由等腰三角形拉成等边三角形,我们先用一种容易理解的方法,我们让B点不动,把连带C点的整个空间往下拉,把C点拉到C^’^
用B(0,1)=>B’(0,1)和C(1,1)=>C’($\sqrt 3 /2,-1/2$)建立矩阵方程,求得转换矩阵为
sqrt(3)/2 0
-1/2 1
方格=》三角形
\[x'=\frac {\sqrt 3} 2x \space y'= -1/2x+y\]需要注意,纹理上的uv值是(x’,y’),即平铺的三角形的平面中位置,我们需要逆向计算出该点原来的uv值,好把uv转换在[0,1]之间
总之就是输入的是(x’,y’),需要求的是(x,y),下面是代码:
1 | |
我们换种更合适的方式转换空间,让整个空间沿对角线方向挤压,每个点都在保持在对角线上移动,边长不再是1了:

代码如下:
1 | |
各顶点对输入点的权重

r^2^ 一般规定为0.5,因为单形的三角形的一个顶点到对面边的距离是$\frac {\sqrt 2} 2$,平方就是$\frac 1 2$
完整的噪声代码:
1 | |

Cellular Noise
WorleyNoise
网格噪声是另一种不用于梯度噪声的噪声算法,现在基本用Worley Noise的名称——作者Steven Worley的名字。
基本思想是随机选择一些位置,称为特征点;
然后对于任意一个输入点,根据距离函数,取到这些特征点的距离中最近的那个距离作为输出。
实现上的优化,先把空间划分成网格,在每个网格中随机选一点作为特征点,之后计算最短距离时,只需要计算所在的网格和周围8个网格的特征点(优化方法使用4个网格就足够了)的距离就好了,不必遍历全部特征值。
1 | |
在计算得到最近的特征点距离时,同时保存下最近特征点的坐标,通过把坐标转化为一个随机值,输出这个随机值而不是输出距离值

分型噪声(fbm)
分型布朗运动,通过将不同频率和振幅的噪声函数进行组合,最常用的方式每一级频率×2,振幅÷2,最后相加:
1 | |
该处理可以对任何种的噪声使用,下面分别是柏林分型噪声和worley分型噪声


湍流(turbulence)
用于fbm的技术,对于类似值域在[-1,1]的噪声,不使用线性变换到[0,1],而是使用绝对值,从而获得非常尖锐的噪声值为0的范围,用来刻画尖锐的山谷
1 | |
下面是柏林分型湍流

山脊(ridge)
在湍流的基础上,把最低值翻上来,再做一些处理,作为山脊
1 | |
同样是perlin噪声的演变:


还有一些其他的处理方法,比如把每一次迭代的分量乘在一起,而不是叠加:

域翘曲(Domain Warping)
https://iquilezles.org/articles/warp/
通过多次嵌套fbm函数,来扭曲每一次输入的值,生成的噪声
1 | |
两次嵌套和三次嵌套的perlin fbm噪声的结果:


平铺无缝
当在一个面上平铺纹理时,需要多个贴图上下左右首尾相接,但是我们之前生成的噪声纹理并不能无缝的连接在一起:

要让这些纹理能够自然的无缝平铺,需要一些额外的处理。
周期化输入
一个简单的方法是,我们通过uv来获得随机噪声,那么只需要让输入的uv按照像素的大小体现周期性就好了。
以perlin噪声为例,在生成随机梯度向量这一步,把输入的四角的坐标,除余操作获得以分割数为周期的梯度向量:
1 | |


使用这个方法的缺点是,缩放、或者说是分格数量(代码中的SplitNum),必须是整数,不然不能做到无缝。
高维采样
另一种方法是,通过更高维度的噪声贴图采样获取四周无缝噪声。
例如在2d纹理上,可以采样一个环,从而获得一个左右循环的的一维噪声;在3d纹理上,可以采样一个圆筒,获得一个左右无缝的2d纹理。
想要获取2d的完全无缝的纹理,则需要在4d空间的纹理中进行采样,3d的则需要在6d纹理中采样,即n维噪声需要在2n维噪声中采样。
还是以2dperlin为例,首先需要实现4d perlin噪声。
4d perlin噪声在一个超立方体中有16个顶点,即需要生成16个梯度向量、分别和距离向量点乘,然后插值
1 | |
然后2维的uv在4d中采样。
具体来说,是把2d的uv转化乘4d的uvwx,然后用4d的uvwx获取4d的噪声值作为2d噪声在uv处的值。
2d转换为4d uv的方法是每一个轴向,转化为两个三角函数值:
注意不再对uv进行缩放(注释掉的那行代码),而是对转化的4d的uvwz进行缩放(SplitNum分格)
这里使用了TimeOffset作为随机种子
1 | |

缩放不必是整数,对所有噪声都有效,但是计算量更大
参考:
https://catlikecoding.com/unity/tutorials/pseudorandom-noise/
https://thebookofshaders.com/10/?lan=ch