Perlin Noise
w
White noise的效果如下图所示:
而Perlin noise的效果则有些像模糊后的white noise:
Perlin noise的一个重要特征是可重复性:只要输入的三维坐标不变,它返回的随机数就是固定的。Perlin Noise还有一个特征是使用的方法快速且复杂型相对较小,在本章节中我们将会实现自己的Perlin Noise类。
原博客中基本没有详细介绍Perlin Noise生成的思路,所以这篇博客主要是展示代码。我会在后续单独更新一篇文章,更深入地探讨相关内容。
5.1 Using Blocks of Random Numbers
如果我们尝试用随机数构成的三位数组平铺在空间中,我们会清楚地看到重复的样式:
所以我们还需要使用某种散列来扰乱它:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#ifndef PERLIN_H
#define PERLIN_H
#include "rayTracing.h"
class perlin
{
public:
perlin()
{
randFloat = new double[pointCount];
for (int i = 0; i < pointCount; i++)
{
randFloat[i] = randomZeroToOne();
}
permX = perlinGeneratePerm();
permY = perlinGeneratePerm();
permZ = perlinGeneratePerm();
}
~perlin()
{
delete[] randFloat;
delete[] permX;
delete[] permY;
delete[] permZ;
}
double noise(const point3& p) const
{
auto i = static_cast<int>(4 * p.x()) & 255;
auto j = static_cast<int>(4 * p.y()) & 255;
auto k = static_cast<int>(4 * p.z()) & 255;
return randFloat[permX[i] ^ permY[j] ^ permZ[k]];
}
private:
static constexpr int pointCount = 256;
double* randFloat;
int* permX;
int* permY;
int* permZ;
static int* perlinGeneratePerm()
{
auto p = new int[pointCount];
for (int i = 0; i < pointCount; i++)
{
p[i] = i;
}
permute(p, pointCount);
return p;
}
static void permute(int* p, int n)
{
for (int i = n - 1; i > 0; i--)
{
int target = randomInt(0, i);
int temp = p[i];
p[i] = p[target];
p[target] = temp;
}
}
};
#endif
现在我们可以实现Perlin noise的纹理了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "perlin.h"
#include "rtw_stb_image.h"
...
class noiseTexture final : public texture
{
public:
noiseTexture() = default;
color value(double u, double v, const point3& p) const override
{
return color(1, 1, 1) * noise.noise(p);
}
private:
perlin noise;
};
我们创建一个测试场景来看看noise纹理的效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void perlinSpheres()
{
hittableList world;
auto perlinNoiseTexture = make_shared<noiseTexture>();
world.add(make_shared<sphere>(point3(0,-1000,0), 1000, make_shared<lambertian>(perlinNoiseTexture)));
world.add(make_shared<sphere>(point3(0,2,0), 2, make_shared<lambertian>(perlinNoiseTexture)));
camera cam;
cam.aspectRatio = 16.0 / 9.0;
cam.imageWidth = 400;
cam.samplesPerPixel = 100;
cam.maxDepth = 50;
cam.verticalFOV = 20;
cam.lookFrom = point3(13, 2, 3);
cam.lookAt = point3(0, 0, 0);
cam.viewUp = vec3(0,1,0);
cam.defocusAngle = 0;
cam.render(world);
}
int main()
{
switch (4)
{
case 1: bouncingSphere(); break;
case 2: checkeredSphere(); break;
case 3: earth(); break;
case 4: perlinSpheres(); break;
default: ;
}
}
渲染中。。。
5.2 Smoothing out the Result
我们可以使用三线性插值来让当前的Perlin noise更加的平滑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class perlin
{
public:
...
double noise(const point3& p) const
{
double u = p.x() - floor(p.x());
double v = p.y() - floor(p.y());
double w = p.z() - floor(p.z());
auto i = static_cast<int>(floor(p.x()));
auto j = static_cast<int>(floor(p.y()));
auto k = static_cast<int>(floor(p.z()));
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++)
c[di][dj][dk] = randFloat[
permX[i+di & 255] ^
permY[j+dj & 255] ^
permZ[k+dk & 255]
];
return trilinearInterp(c, u, v, w);
}
private:
...
static double trilinearInterp(double c[2][2][2], double u, double v, double w)
{
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 * u + (1 - i) * (1 - u))
* (j * v + (1 - j) * (1 - v))
* (k * w + (1 - k) * (1 - w))
* c[i][j][k];
}
}
}
return accum;
}
};
再次运行程序,渲染中。。。
5.3 Improvement with Hermitian Smoothing
我们可以看到noise纹理的平滑处理生效了,但是也会带来明显的网格状的瑕疵,其中有一些被称为Mach Band,是一种颜色线性插值带来的感知上的artifact。我们可以使用Hermite方法来改进:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class perlin (
public:
...
double noise(const point3& p) const {
auto u = p.x() - floor(p.x());
auto v = p.y() - floor(p.y());
auto w = p.z() - floor(p.z());
u = u*u*(3-2*u);
v = v*v*(3-2*v);
w = w*w*(3-2*w);
auto i = int(floor(p.x()));
auto j = int(floor(p.y()));
auto k = int(floor(p.z()));
...
5.4 Tweaking The Frequency
当前的所产生的效果还是无法满足我们的要求,我们可以通过缩放输入的point3来让Perlin noise纹理的变化更加快速:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class noiseTexture final : public texture
{
public:
noiseTexture() = default;
explicit noiseTexture(double scale) : scale(scale) {}
color value(double u, double v, const point3& p) const override
{
return color(1, 1, 1) * noise.noise(scale * p);
}
private:
perlin noise;
double scale;
};
然后,我们在测试场景中的噪音纹理的缩放值设置为4:
1
2
3
4
5
6
7
void perlinSpheres()
{
hittableList world;
auto perlinNoiseTexture = make_shared<noiseTexture>(4);
...
}
渲染中。。。
5.5 Using Random Vectors on the Lattice Points
5.6 Introducing Turbulence
5.7 Adjusting the Phase
本文由作者按照 CC BY 4.0 进行授权