本文资源来自libnoise网站和GPU Gems一书。
一.什么是Coherent noise
Coherent noise由coherent noise函数生成,具备三个特点:
1) 相同的输入总得到相同的输出
2) 输入的小变化引起输出的小变化
3) 输入的大幅变化引起输出的随机变化
coherent-noise函数输出
non-coherent-noise函数输出
如何生成Coherent noise将会在第四部分给出。
二.什么是Perlin Noise
Perlin Noise是几个Coherent noise的叠加,这几个coherent noise间的关系是:频率递增,振幅递减。
下图中为f为频率,a为振幅。
以上几个噪声的和就组成了Perlin噪声:
Ken Perlin在1983年发明Perlin噪声,1985年发表相应文章。
三.Perlin噪声中的各个术语
1. Octave
由多个coherent noise组成一个Perlin噪声,每一个coherent noise就称为一个octave.
octave越多,Perlin噪声就越细致,但计算时间也越长。
2. 振幅Amplitude
每个Coherent noise函数所能输出的最大值,在有名的libnoise库中振幅为n的函数输出范围为-n~n。
3. 频率Frequency
单位长度内Coherent noise函数循环的次数,每次循环的起点函数输出为0。
两个连续octave间频率frequency递增的速度的度量。
当前octave的频率f * lacunarity = 下一个octave的频率f1
5. Persistence
两个连续octave间振幅amplitude递增的速度的度量。
当前octave的振幅A * Persistence = 下一个octave的振幅A1
6. Seed
用于改变Coherent noise函数的输出。改变Seed值,则Coherent noise函数的输出改变。
四.如何生成Coherent noise
Perlin噪声由Coherent noise叠加生成,那么只要知道了如何生成Coherent noise ,就能生成Perlin噪声了。下面将逐步说明。
1. Integer-noise函数
给定一个整数作为输入,则一维Integer-noise函数输出一个伪随机浮点数(通常在-1到1之间)。N维Integer-noise函数需要n个整数作为输入,输出仍是一个伪随机浮点数(通常在-1到-1之间)。
下面是一个这样的函数:
double IntegerNoise (int n)
{
n = (n >> 13) ^ n;
int nn = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff;
return 1.0 - ((double)nn / 1073741824.0);
}
2. 在Interger-noise间进行插值
线形插值linear interpolation: lerp(a, b, w) = a * (1 - w) + b * w,权重值w在0到1之间。
函数如下:
double CoherentNoise (double x)
{
int intX = (int)(floor (x));
double n0 = IntegerNoise (intX);
double n1 = IntegerNoise (intX + 1);
double weight = x - floor (x);
double noise = lerp (n0, n1, weight);
return noise;
}
二维函数用双线性插值bilinear interpolation,三维函数用三线性插值trilinear interpolation。
下图是一维插值后的输出:
二维输出:
线性插值的缺点在于生成的纹理有creasing瑕疵,也就是说界线太明显,图像不自然。在生成凹凸纹理时尤其明显,如下图:
3. 在线性插值的基础上进一步改进
线性插值的方法保证了插值函数的输出在插值点上取得了正确的结果,但不能保证插值函数在插值点上的导数值。
利用Hermit插值可以在保证函数输出的基础上保证插值函数的导数在插值点上为0,这样就提供了平滑性。
插值点为0、1,结果值为0、1,导数为0、0,则可以求得Hermit插值函数为s(x) = -2x3 + 3x2 也就是所谓的S曲线函数。
用立方插值(S曲线插值)代替线性插之后的代码为:
double CoherentNoise (double x)
{
int intX = (int)(floor (x));
double n0 = IntegerNoise (intX);
double n1 = IntegerNoise (intX + 1);
double weight = x - floor (x);
// double noise = lerp (n0, n1, weight);
double noise = lerp (n0, n1, SCurve (weight));
return noise;
}
如果再使插值函数的二阶导数值在插值点处为0,则可以进一步平滑。五次插值函数:s(x) = 6x5 - 15x4 + 10x3
五次插值 三次插值 线性插值
ps:libnoise开源库可以生成Perlin noise。