其实之前那篇文章已经把Perlin Noise讲的很详细了,但是在读RT: next week,我看到作者这么一句话,瞬间来了兴趣
I’ll build that hack up incrementally based on Andrew Kensler’s description.
Andrew Kensler,你可能不知道他是谁,但是如果你在简中互联网搜他的名字,排序第一的可能是个知乎的回答 你见过最美的程序是什么?-知乎
然后我去翻了他的博客,找到了这么一篇文章Building Up Perlin Noise,我本想把其用中文转述一遍的...但是原文写的真的太好了。
文章中给了另一种理解柏林噪声的方法-把Perlin noise看作是几个surflet的和。文章非常直观,能看到Gradient vector的方向和大小对perlin noise产生的影响....真的真的推荐去看看。
surflet这个词我没找到中文翻译QAQ..
在书里作者循序渐进的把一个白噪声产生器变成了柏林噪声发生器。
我会跳过这步直接分析代码。
注意:代码相比原版有改动,我以一种自认为方便理解的方式重写了部分作者的代码。
比如说我把原来的perlin类类名改成了perlin3D
class perlin3D {
public:
perlin3D();
~perlin3D();
//[-1,1]
double noise(const vec3 &p)const;
//[0,1]
double noise01(const vec3&p)const;
private:
vec3 *ranVec;
// Hash seed in x,y,z
int *perm_x;
int *perm_y;
int *perm_z;
static constexpr int point_count = 256;
static constexpr int mask = point_count - 1;
// ease curve function
static double easeCurve(float t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
static vec3 easeCurve(const vec3 &p) {
return vec3(easeCurve(p.x()), easeCurve(p.y()), easeCurve(p.z()));
}
static double trilinear_interp(double c[2][2][2], const vec3 &);
// Generate hash seed array
static int *perlin_generate_perm();
// Shuffle the array
static void permute(int *p, int n);
};
noise函数产生在目标点的噪声,不用多说。
这里让人疑惑的可能是permute,perlin_generate_perm函数和那几个int数组(指针)和vec3数组。
perlin_generate_perm函数是用来生成一个包含了经过重排序的0-255数字的数组,其中permute就是负责重排序的。
利用perlin_generate_perm生成三个数组,perm_x,perm_y,perm_z。这三个数组将会在之后成为计算hash时的种子。
ranVec里存放256个归一化后的随机向量,这些向量后面会作为Gradient Vector使用。
trilinear_interp是个三线性插值(因为三维空间)。
permute函数,负责打乱数组
inline void perlin3D::permute(int *p, int n) {
for (int i = n - 1; i > 0; i--) {
int target = rand_i(0, i);
int tmp = p[i];
p[i] = p[target];
p[target] = tmp;
}
}
在Andrew Kensler的文章里有个很神奇的打乱函数,可以看看
perlin_generate_perm函数,生成一个数组并且调用permute将其打乱
inline int *perlin3D::perlin_generate_perm() {
auto p = new int[point_count];
for (int i = 0; i < perlin3D::point_count; i++)
p[i] = i;
permute(p, point_count);
return p;
}
构造函数和构析函数
inline perlin3D::perlin3D() {
perm_x = perlin_generate_perm();
perm_y = perlin_generate_perm();
perm_z = perlin_generate_perm();
ranVec = new vec3[point_count];
for (int i = 0; i < point_count; ++i) {
ranVec[i] = normalize(random_vec());
}
}
inline perlin3D::~perlin3D() {
delete[] ranVec;
delete[] perm_x;
delete[] perm_y;
delete[] perm_z;
}
三次插值函数trilinear_interp,这东西当时让我目瞪口呆,我思考了了好半天都没想明白,直到看了这篇文章 线性插值与双/三线性插值
inline double perlin3D::trilinear_interp(double c[2][2][2], const vec3 &weight) {
auto accum = 0.0;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
for (int k = 0; k < 2; k++)
accum += (i * weight.x() + (1 - i) * (1 - weight.x())) *
(j * weight.y() + (1 - j) * (1 - weight.y())) *
(k * weight.z() + (1 - k) * (1 - weight.z())) * c[i][j][k];
return accum;
}
这个函数等同与7个lerp套一起,就是类似我上篇文章的写法
最后的重点,噪声发生函数
inline double perlin3D::noise(const vec3 &p)const {
vec3 pi = vec3(floorf(p.x()), floorf(p.y()), floorf(p.z()));
vec3 pf = p - pi;
vec3 weight = easeCurve(pf);
double c[2][2][2];
for (int di = 0; di < 2; di++)
for (int dj = 0; dj < 2; dj++)
for (int dk = 0; dk < 2; dk++) {
vec3 corner(di, dj, dk);
vec3 c2p = pf - corner;
int hash_ = perm_x[((int)pi.x()+di) & 255] ^ perm_y[((int)pi.y()+dj) & 255] ^
perm_z[((int)pi.z()+dk) & 255];
auto &grad = ranVec[hash_];
c[di][dj][dk] = dot(c2p, grad);
}
return trilinear_interp(c, weight);
}
这里需要讲解一下,变量hash_是利用了之前生成的三个随机数组来得到一个随机值,接着用这个随机值取到gradient vector。三维数组c里储存的时gradient vector与 从顶点指向目标点的向量的点乘结果,与之前文章的代码在本质上一摸一样。最后调用三次插值函数得到结果。
注意,这里的结果大小在[-1,1]里,可以再包装一层用来返回一个[0,1]之间的大小。
class noise_texture :public texture{
public:
noise_texture() {}
//use scale to control frequent
noise_texture(double _scale):scale(_scale){};
virtual vec3 value(double u, double v, const vec3 &point) const override;
protected:
perlin3D noise;
double scale = 1;
};
vec3 noise_texture::value(double u, double v, const vec3 &point)const{
return vec3(noise.noise01(point*scale));
}
用scale参数可以控制噪声的频率,比如对一个小小的球,你想让它上面的噪声看着清楚点,就可以给个大的倍数....
这东西Perlin本人称之为turbulence,也就是湍流,其效果很惊艳。生成公式如下
$\sum|\frac{2^inoise(V)}{2^i}|$
代码
//in class perlin3D
inline double perlin3D::turb_noise(vec3 p,int layer = 8)const{
float amp = 1;
double res = 0;
for (int i = 0; i < layer; i++) {
res += noise(p)*amp;
amp *=0.5;
p = p*2;//double frequency
}
return fabs(res);
}
--------------------
//in turb_texture
class turb_texture :public noise_texture {
public:
turb_texture() {}
turb_texture(double _scale) : noise_texture(_scale) {}
virtual vec3 value(double u, double v, const vec3 &point) const override;
};
inline vec3 turb_texture::value(double u, double v, const vec3 &point)const{
return vec3(noise.turb_noise(scale * point));
}
渲染效果
公式如下
$0.5*(1+sin(x+\sum|\frac{2^inoise(V)}{2^i}))|$
代码
class marble_texture : public noise_texture {
public:
marble_texture() {}
marble_texture(double _scale) : noise_texture(_scale) {}
vec3 value(double u, double v, const vec3 &point) const {
return 0.5 * (1 + sin(scale * point.z() + 10 * noise.turb_noise(point)));
}
};
Ray tracing: next week - Peter Shirley
Building Up Perlin Noise - Andrew Kensler
Perlin noise -Wikipedia
【图形学】谈谈噪声 -冯乐乐大佬的博客
Improved Noise reference implementation -Ken Perlin
GPU Gems - charpter 5 Implementing Improved Perlin Noise
在一开始我尝试用Andrew Kensler的方式重写这个PerlinNoise,但是失败了....并且得到了个很奇怪的图
是不是很好玩啊哈哈哈哈哈