写在前面
本文是关于噪声生成的精简总结篇,重新对前面三篇的内容进行了梳理。 😷牙疼疼头疼疼,越写话越少...初四立马去看牙🦷 戳戳下面链接🔗阅读之前的子篇,可以看到更多二女士学习过程中的笔记与体会。 ODOS | 吵吵儿 MAKE SOME NOISES🔇 - 1 ODOS | 吵吵儿 MAKE SOME NOISES🔇 - 2 ODOS | 吵吵儿 MAKE SOME NOISES🔇 - 3
产生随机
为了生成不规则的程序化纹理,我们需要不规则的图元函数(primitive function),这些函数一般被叫作Noise函数。 这应当是一个伪随机(apparently random/pseudorandom)函数,它将纹理坐标作为输入,输入相同纹理坐标能返回相同的值。 理想中的noise函数有如下的属性:
- Noise是一个可输入参数控制的可重复的伪随机函数
- Noise 范围值的范围在-1到1
- Noise的带宽有限(band-limited),最大频率为1
- Noise不存在明显的周期。比如伪随机函数一般都是周期函数,但是周期非常长以至于不明显。
- Noise是稳定的(Stationary),也就是说他的统计属性(译注:应该指方差之类的属性)不随移动而变化。
- Noise是各向同性的(Isotropic),就是说旋转也不会改变统计属性。
SINE-BASED hash
最常见的噪声函数是
y = fract(sin(x))
,通过对x和sin(x)进行放大,就可以产生伪随机的效果。 通过点乘,可以将其拓展到2D。注意这里点乘向量的xy之间不要有明显的倍数关系,不然就会产生条纹。 在互联网的噪声生成函数中,最典型的就是 fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453) 至于这几个数,其实没有什么数学上的支持,这里有关于其出处的一些讨论。
SINE-LESS hash
作为初学者,我们可以大胆的使用👆这种方式生成随机的效果,但它们其实不是严格正确的。在不同的GPU上,sine函数的精度是不同的,尤其是数字很大时(动态noise常常会乘入时间),差异会更加明显。Shadertoy上列举了不使用sine的hash算法: Hash without Sine 鉴于2022年glsl早已支持整数运算,以下哈希算法更加高效且得到了数学上的验证。 Integer Hash - II 顺便一提:
基本噪声
根据wiki,由程序产生噪声的方法大致可以分为两类: | 类别 | 名称 |
---|---|---|
基于晶格的方法(Lattice based) | 又可细分为两种: 第一种是梯度噪声(Gradient noise),包括Perlin噪声, Simplex噪声,Wavelet噪声等; 第二种是Value噪声(Value noise)。 |
|
基于点的方法(Point based) | Worley噪声 |
晶格噪声 Lattice Noise
基于lattice的噪音生成算法都需要为整数格点(integer lattice)生成PRN,每一个像素对所处晶格的四个角点进行插值。生成PRN的内容和插值的方式形成了不同的噪声效果。 阅读 实现
值噪声 Value Noise
最简单粗暴的Value Noise就是在每个格点生成0~1的PRN,然后直接插值。 阅读 实现
柏林噪声 Perlin Noise
又名Gradient Noise。为了避免value noise的块状感,Perlin用2维的梯度(Gradient)PRN替换了value noise中1维的PRN。 对于每个点P,我们需要:
- 找到P所处晶格的2^n个顶点(二维下有4个,三维下有8个,n维下有2^n个);
- 对 顶点到P 和 顶点处的随机梯度向量 做 点乘 ;
- 根据顶点到P的距离进行 平滑插值 。
在原始的Perlin噪声实现中,插值使用的缓和曲线是s(t)=3t^2−2t^3(yep就是smoothstep那个曲线),在2002年的论文6中,Perlin改进为s(t)=6t^5−15t^4+10t^3。s(t)=3t^2−2t^3在一阶导满足连续性,s(t)=6t^5−15t^4+10t^3在二阶导上仍然满足连续性。 阅读 或者回到顶部查看子篇-1看看牙不疼的废话较多版本。 实现
单形噪声 Simplex Noise
单形噪声较柏林噪声的进步在于使用了单形(Simplex)切分空间,这使计算的复杂度从O(2^N)降至了O(N^2)。 我们可以把单形进行坐标偏斜(skewing),把平铺空间的单形变成一个新的网格结构,这个网格结构是由超立方体组成的,而每个超立方体又由一定数量的单形构成。 阅读,去掉simplex部分的注释可以清楚地观察到坐标系如何变化。 实现
细胞噪声 Worley Noise
又称Cellular Noise或者Voronoi Noise,基于Voronoi空间分割理论,在空间中,我们随机放置若干特征点(Feature points),对任一输入点P,计算其到所有特征点的距离,这个距离值的最小值就是最后的噪声值。 但是计算输入点到所有点到距离最小值会花费大量的时间,因此worley提出了基于晶格的优化算法,每个输入点只需计算自己所处的晶格和周边8个晶格中的特征点距离即可,即3×3 Worley。 后续进一步优化产生了2×2Worley。但是牙好痛,再说吧。 阅读
噪声处理
动态噪声 - n+1
希望产生动态变化的噪声,需要n+1维的噪声。例如动态的二维噪声需要以时间作为第三维。
四方连续 - n*2
希望产生四方连续的噪声,需要n*2维的噪声。例如左右连续的一维噪声可以从二维噪声上按圆的方式采样取得。
分形布朗运动 Fractal Brownian Motion
fbm = noise(st) + 0.5 noise(2st) + 0.25 noise(4st)
通过在循环(循环次数为 octaves,一次循环为一个八度)中叠加噪声,并以一定的倍数(lacunarity,间隙度)连续升高频率,同时以一定的比例(gain,增益)降低 噪声 的振幅,最终的结果会有更好的细节。这项技术叫“分形布朗运动(fractal Brownian Motion)”(fBM),或者“分形噪声(fractal noise)”。
一般采用每次频率升高一倍,振幅降低1/2。 前文value noise、perlin noise右侧即为fbm后的效果。
湍流 Turbulence
在fbm中对噪声函数取绝对值,使噪声值等于0处发生突变,产生湍流纹理: fbm = |noise(st)| + 0.5 |noise(2st)| + 0.25 |noise(4st)|
域翘曲 Domain Warping
翘曲域噪声用来模拟卷曲、螺旋状的纹理,比如烟雾、大理石等,实现公式如下: f(p) = fbm( p + fbm( p + fbm( p ) ) ) 阅读 实现
Reference
开源的noise库:https://github.com/stegu/webgl-noise psrdnoise:https://github.com/stegu/psrdnoise/
ODOS —— One Day One Shader 努力日更的学习计划。 包括但不限于基于Shadertoy、UnityShaderLab等平台的shader学习笔记