什么是柏林噪声?

说起噪声大家可能会想起这个:

但是这个噪声看起来很不自然,而且现实中的自然噪声并不长这个样子,比如起伏的山脉,大理石的纹理,天空的云彩,这些噪声虽然看起来杂乱无章,其实它们都有着内在规律。柏林噪声的用处就在这里:生成看似杂乱但是有序的内容。

那么如何生成它们呢?

主要有三个步骤:

1.固定一部分点的颜色。

2.“平滑”这些固定点之间的颜色。

3.用上面的方法生成几个不同频率的平滑噪音然后相加。

如何确定噪音的生成频率?只要改变固定点的个数就可以了,固定点多的频率就高,固定点少的频率就低。那么怎么平滑固定点之间的颜色?

perlin noise

其实上面所说的固定一部分点的颜色然后进行平滑的方法是一个效果比较差的函数,它其实叫value 噪声,百度柏林噪声第一页就有几篇博客,标题是perlin noise,而实现的是value noise,o(╥﹏╥)o。
现在就到了perlin噪声的关键环节,我们不固定点的颜色,而是固定点的梯度(gradient)。

步骤就变成了:

1.固定一部分点的gradient。

2"平滑"这些固定点中间的gradient。

3.用上面的方法生成几个不同频率的平滑噪音, 然后相加(准确来说这一步是属于分形噪声的部分)。

先上代码,之后一点点讲,这个代码是简化版的,只是为了帮助理解,真正用的时候还是要用官方的那篇代码,我把我写的代码的C++版放在了文章的末尾:

#include "pch.h"
#include <iostream>
#include <ctime>
#include<fstream>
using namespace std;
struct vec3
{double x;double y;double z;vec3() {};vec3(double x, double y,double z) :x(x), y(y),z(z) {}double operator*(vec3 t){return x * t.x + y * t.y+z*t.z;}vec3 operator+(vec3 t){return vec3(x + t.x, y + t.y,z+t.z);}
};
class perlinNoise {
public:vec3 g[12] = { vec3(1,1,0),vec3(-1,1,0),vec3(1,-1,0),vec3(-1,-1,0),vec3(1,0,1),vec3(-1,0,1),vec3(1,0,-1),vec3(-1,0,-1), vec3(0,1,1),vec3(0,-1,1),vec3(0,1,-1),vec3(0,-1,-1) };vec3 vertex[25][25][25];perlinNoise(){srand((int)time(0));for (int i = 0; i < 25; i++){for (int j = 0; j < 25; j++){for (int k = 0; k < 25; k++){int mrand = rand() % 12;vertex[i][j][k] = g[mrand];}}}}double generateNoise(double x, double y,double z){int X = (int)floor(x);int Y = (int)floor(y);int Z = (int)floor(z);double u = x - X;double v = y - Y;double w = z - Z;vec3 vec000 = vec3(u, v,w);vec3 vec010 = vec3(u, v, w) + vec3(0, -1, 0);vec3 vec100 = vec3(u, v, w) + vec3(-1, 0, 0);vec3 vec110 = vec3(u, v, w) + vec3(-1, -1, 0);vec3 vec001 = vec3(u, v, w) + vec3(0, 0, -1);vec3 vec011 = vec3(u, v, w) + vec3(0, -1, -1);vec3 vec101 = vec3(u, v, w) + vec3(-1, 0, -1);vec3 vec111 = vec3(u, v, w) + vec3(-1, -1, -1);double g000 = vec000 * vertex[X][Y][Z];double g010 = vec010 * vertex[X][Y + 1][Z];double g100 = vec100 * vertex[X + 1][Y][Z];double g110 = vec110 * vertex[X + 1][Y + 1][Z];double g001 = vec001 * vertex[X][Y][Z + 1];double g011 = vec011 * vertex[X][Y + 1][Z + 1];double g101 = vec101 * vertex[X + 1][Y][Z + 1];double g111 = vec111 * vertex[X + 1][Y + 1][Z + 1];u = fade(u);v = fade(v);w = fade(w);double lerpx1 = lerp(g000, g100, u);double lerpx2 = lerp(g010, g110, u);double lerpy1 = lerp(lerpx1, lerpx2, v);double lerpx3 = lerp(g001, g101, u);double lerpx4 = lerp(g011, g111, u);double lerpy2 = lerp(lerpx3, lerpx4, v);double lerpz = lerp(lerpy1, lerpy2, w);return lerpz;}double lerp(double a, double b, double t){return a + t * (b - a);}double fade(double t){return t * t * t * (t * (t * 6 - 15) + 10);}
};
int main()
{perlinNoise a;ofstream outfile("perlinNoise.ppm");int X = 400, Y = 400;outfile << "P3" << endl << X << " " << Y << endl << "255" << endl;for (double i = 0; i < 20; i += 0.05){for (double j = 0; j < 20; j += 0.05){int temp = (a.generateNoise(i, j,7.856413) + 1.0) / 2.0*255.0;outfile << temp << " " << temp << " " << temp << " ";}}
}

对于二维来说,要实现perlin噪声需要:

1.定义一个晶格结构(就当成一个单位正方形),每个晶格的顶点有一个“伪随机”的梯度向量(说它是伪随机的是因为:1.梯度向量是我们随机的选择我们自定义的向量,而不是随机的选择随机的向量。2.一旦确定了,固定的点就有了固定的梯度向量)。这个梯度向量就当做向量理解吧。对于二维的Perlin噪声来说,晶格结构就是一个平面网格,三维的就是一个立方体网格。
2.输入一个点(二维的话就是二维坐标,三维就是三维坐标,n维的就是n个坐标),我们找到和它相邻的那些晶格顶点(二维下有4个,三维下有8个,n维下有个),计算该点到各个晶格顶点的距离向量,再分别与顶点上的梯度向量做点乘,得到个点乘结果。
3.使用缓和曲线(ease curves)来计算它们的权重和

下面这些就是已经定义好的随机向量,它们是由立方体12条棱的中点决定的,至于为什么这样做,《GPU精粹》上说这样生成的图像比随机产生固定向量的“污点”更少:

 vec3 g[12] = { vec3(1,1,0),vec3(-1,1,0),vec3(1,-1,0),vec3(-1,-1,0),vec3(1,0,1),vec3(-1,0,1),vec3(1,0,-1),vec3(-1,0,-1), vec3(0,1,1),vec3(0,-1,1),vec3(0,1,-1),vec3(0,-1,-1) };

这个构造函数就是为每个点固定一个梯度,由生成的随机数来确定 使用g[12]中的哪一个梯度,由于空间原因只定义了(0,0,0)到(25,25,25)的点,所以最后输入的点的大小不能超过25,不过官方的代码可以输入更大的值。

 vec3 vertex[25][25][25];perlinNoise(){srand((int)time(0));for (int i = 0; i < 25; i++){for (int j = 0; j < 25; j++){for (int k = 0; k < 25; k++){int mrand = rand() % 12;vertex[i][j][k] = g[mrand];}}}}

我们需要求出另外4个向量(在3维空间则是8个),它们分别从各顶点指向输入点(蓝色点)。下面有个2维空间下的例子:

二维空间下:

     int X = (int)floor(x);int Y = (int)floor(y);double u = x - X;double v = y - Y;vec2 vec00 = vec2(u, v);vec2 vec01 = vec2(u, v) + vec2(0, -1);vec2 vec10 = vec2(u, v) + vec2(-1, 0);vec2 vec11 = vec2(u, v) + vec2(-1, -1);

在三维空间中求出这8个向量:

int X = (int)floor(x);
int Y = (int)floor(y);
int Z = (int)floor(z);double u = x - X;
double v = y - Y;
double w = z - Z;vec3 vec000 = vec3(u, v,w);
vec3 vec010 = vec3(u, v, w) + vec3(0, -1, 0);
vec3 vec100 = vec3(u, v, w) + vec3(-1, 0, 0);
vec3 vec110 = vec3(u, v, w) + vec3(-1, -1, 0);
vec3 vec001 = vec3(u, v, w) + vec3(0, 0, -1);
vec3 vec011 = vec3(u, v, w) + vec3(0, -1, -1);
vec3 vec101 = vec3(u, v, w) + vec3(-1, 0, -1);
vec3 vec111 = vec3(u, v, w) + vec3(-1, -1, -1);

接着,对每个顶点的梯度向量和距离向量做点积运算,我们就可以得出每个顶点的影响值:

double g000 = vec000 * vertex[X][Y][Z];
double g010 = vec010 * vertex[X][Y + 1][Z];
double g100 = vec100 * vertex[X + 1][Y][Z];
double g110 = vec110 * vertex[X + 1][Y + 1][Z];
double g001 = vec001 * vertex[X][Y][Z + 1];
double g011 = vec011 * vertex[X][Y + 1][Z + 1];
double g101 = vec101 * vertex[X + 1][Y][Z + 1];
double g111 = vec111 * vertex[X + 1][Y + 1][Z + 1];

下面是平滑曲线的函数,最初平滑曲线是这样的 w(t)=3t² - 2t³ ,它保证了w(0)=0, w(1)=1.和w’(0) = 0和w’(1)=0,而新版的这个函数还保证了w’’(0) = 0和w’’(1)=0,总之就是更平滑了

 double fade(double t){return t * t * t * (t * (t * 6 - 15) + 10);}

三次方插值和五次方插值对比:
我们需要用fade函数来变换u,v,w的值来让插值更加平滑:

u = fade(u);
v = fade(v);
w = fade(w);

根据各顶点对该点的影响值确定数值大小,总共需要7次插值(二维需要3次):

     double lerpx1 = lerp(g000, g100, u);double lerpx2 = lerp(g010, g110, u);double lerpy1 = lerp(lerpx1, lerpx2, v);double lerpx3 = lerp(g001, g101, u);double lerpx4 = lerp(g011, g111, u);double lerpy2 = lerp(lerpx3, lerpx4, v);double lerpz = lerp(lerpy1, lerpy2, w);return lerpz;

还有一个内存问题
要想获得更大的频率,我们就必须要有更多的固定点,所以高频率的噪音绝对是有必要的. 而为了储存固定点的gradient, 要建立一个数组才行. 而当噪音的频率非常高的时候, 由于需要的固定点会很多, 25X25X25显然是不够的,这个数组也会非常的大. 为了降低内存的使用, perlin使用了1个256个元素的哈希表. 也就是说, 预先找出合理的, 足够随机的256个gradient, 存在一个表里. 然后每次需要某个固定点的gradient值的时候, 通过这个这个点的坐标, 伪随机的在表里选出一个值. 对于3d的情况, 如果我们想要坐标(i,j,k)的gradient g(i,j,k),而P里预存储了256个gradient, 那么:

g(i, j, k) = P[ ( i + P[ (j + P[k]) mod 256 ] ) mod 256 ]

在1D数组保存3D数据
index=x+(nx)y+(nx)(ny)*z
这样, 在生成perlin noise的时候, 内存的使用限定在了1个256大小的哈希表.,下面是这一部分的代码,由于很多容易混的问题都堆在了这块,所以可能不是很容易理解

     int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z,     B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z),grad(p[BA], x - 1, y, z)), lerp(u,grad(p[AB], x, y - 1, z),  grad(p[BB], x - 1, y - 1, z))),lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1),grad(p[BB + 1], x - 1, y - 1, z - 1))));

接下来是fbm的介绍(后面所有代码均以文末代码为基础写的

现在想一些问题,
如何增大频率?增加更多采样点。
如何增加更多采样点?覆盖更多固定点。
如何覆盖更多固定点?增大输入数之间的间隔。
如果频率一直增大,而振幅一直减小会变成什么样子?

正如你所看到的,如果频率增加而振幅减少,那么这些函数的“特性”会越来越小,而且越来越近。如果两个频率值的比例正好是2:1,那么就叫做倍程。如果将不同频率的函数加和在一起,那么我们会看到更有意思的着色器效果。

这样做的结果是一个包含不同大小特性的函数。低频率函数的大幅度振动构成基本的形状,而高频率函数的小幅度振动构成小范围的细节信息。Perlin将不同倍程的噪声相加后的函数(每个噪声都比前一个倍程的噪声振幅减半)叫做1/f噪声,不过如今通常会使用分形布朗运动(fraction Brownian motion)或者fbm这个名词来描述它。

下面是perlin noise+fbm的代码

ImprovedNoise pNoise;
int octaves = 4;
ofstream outfile("分形噪声.ppm");
int xx=400, yy=400;
outfile << "P3" << endl << xx << " " << yy << " " << endl << "255" << endl;
for (double i = 0; i < 20; i+=0.05)
{for (double j = 0; j < 20; j+=0.05){
//*************************************************************************************double sum = 0,maxValue=0,frequency = 1, amplitude = 1;for (int k = 0; k < octaves; k++, frequency *= 2.0, amplitude *= 0.5){sum += pNoise.noise(i*frequency, j*frequency, 7.89101112131415)*amplitude;maxValue += amplitude;}sum /= maxValue;int b = (sum+1)*255.0/2.0;outfile << b << " " << b << " " << b << endl;
//*************************************************************************************}
}

下面是效果图,第一个是上面教学代码产生的Perlin噪声图,第二个是Perlin+fbm产生的图:


1.我们可以使用噪声函数的绝对值来产生其他一些有趣的效果。这一方法导致导数产生了不连续性,因为函数在0分界线上会发生明显的偏折。如果噪声函数在不同频率的结果都发生了偏折,并且将结果叠加在一起的话,那么得到的纹理结果将会在各个尺度上都出现皱痕效果。Perlin将这类噪声称作湍流,因为它看起来像是湍急水流产生的效果。这种效果可以模拟不同的自然现象,比如用这类噪声来模拟火焰或者熔岩。

sum += abs(pNoise.noise(i*frequency, j*frequency, 7.89101112131415))*amplitude;

湍流效果:

2.另一类噪声函数的形式就是将噪声函数作为正弦等周期函数的一部分使用。将噪声作为正弦函数的输入值加入,我们就可以得到一个“随机”振荡的函数。使用这个函数可以创建一些类似颜色脉络变化的效果。
sin(x+∣noise(p)∣+12∣noise(2p)∣+14∣noise(4p)∣+...)sin(x+|noise(p)|+12|noise(2p)|+14|noise(4p)|+...)sin(x+∣noise(p)∣+12∣noise(2p)∣+14∣noise(4p)∣+...)
这个公式可以让表面沿着x方向形成一个条纹状的结构,我们可以通过改变x分量前面的系数来控制条纹的疏密。

double sum = 0frequency = 1, amplitude = 1;
for (int k = 0; k < octaves; k++, frequency *= 2.0, amplitude *= 0.5)
{sum += abs(pNoise.noise(i*frequency, j*frequency, 7.89101112131415))*amplitude;
}
sum = sin(sum + i * frequency / 16.0);
int b = (sum+1)*255.0/2.0;
outfile << b << " " << b << " " << b << endl;

大理石效果:

3.(真·瞎搞)给不同的值赋予不同的颜色

if (b < 50)
{outfile << 255 << " " << b << " " << b << endl;
}
else if(b<100)
{outfile << b << " " << 255 << " " << b << endl;
}
else if (b < 150)
{outfile << 175 << " " << 175 << " " << b << endl;
}else if (b < 200)
{outfile << b << " " << 125 << " " << 125 << endl;
}
else
{outfile << b << " " << b << " " << 255 << endl;
}


一些Perlin噪声产生美图,这些作品基本上都来自这位大神的文章
1.nimitz发明了一种对每层噪音添加旋转的方法,得到的图形看起来像翻滚的岩浆

2.除了操作噪音本身之外,还可以操作噪音所在的空间(坐标系)。

3.1/z变换,最简单的一种共形变换,1/z再进展到把模长也除掉就会得到星光状的图像。

Perlin Noise代码的C++版

#include "pch.h"
#include <iostream>
#include <cmath>
#include<fstream>
using namespace std;class ImprovedNoise {
public :ImprovedNoise(){int permutation[512] = { 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180};for (int i = 0; i < 256; i++) p[256 + i] = p[i] = permutation[i];}double noise(double x, double y, double z) {int X = (int)floor(x) & 255,             Y = (int)floor(y) & 255,             Z = (int)floor(z) & 255;x -=floor(x);                            y -=floor(y);                            z -=floor(z);double  u = fade(x),                      v = fade(y),                         w = fade(z);int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z,     B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z),grad(p[BA], x - 1, y, z)), lerp(u,grad(p[AB], x, y - 1, z),  grad(p[BB], x - 1, y - 1, z))),lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1),grad(p[BB + 1], x - 1, y - 1, z - 1))));}double fade(double t) { return t * t * t * (t * (t * 6 - 15) + 10); }double lerp(double t, double a, double b) { return a + t * (b - a); }double grad(int hash, double x, double y, double z) {int h = hash & 15;                     double u = h < 8 ? x : y,              v = h < 4 ? y : h == 12 || h == 14 ? x : z;return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);}int p[512];};
int main()
{ImprovedNoise pNoise;int octaves = 4;ofstream outfile("noise.ppm");int xx=400, yy=400;outfile << "P3" << endl << xx << " " << yy << " " << endl << "255" << endl;for (double i = 0; i < 20; i+=0.05){for (double j = 0; j < 20; j+=0.05){double sum = pNoise.noise(i, j, 7.89101112131415);int b = (sum + 1)*255.0 / 2.0;outfile << b << " " << b << " " << b << endl;}}//cout << count;return 0;
}

柏林噪声(Perlin Noise)相关推荐

  1. [算法]柏林噪声 Perlin Noise

    时间:2017/1/26 大三上学年寒假 每次寒假都挺闲的,恰好最近家里网又不太稳定,不能LOL!于是想趁这个机会,利用OpenGL,尝试着写一个仿造的<Minecraft>.但是,思来想 ...

  2. 3D数学之柏林噪声(Perlin Noise)

    好了,转入正题. 其实它的原理并不是很难,但是由于网上实现的版本太多太杂,真要实现起来竟然不知从何处下手,而且自己写的时候会遇到各种各样的问题.最终写出来了,所以很欣然. 先看下,我在网上找的一些资料 ...

  3. Unity 使用柏林噪声(Perlin Noise)生成网格地图

    前言 最近在尝试制作一个基于网格地图的RPG游戏,所以想着自己生成一个网格地图. 但是网格地图的生成有很多的方式,大多数的方式都达不到我的要求,我需要一个地图可以随机生成各种地形,也可以储存一些地形数 ...

  4. Unity 动态网格地图的生成:基于Perlin Noise创建地形

    前言: 在前面的文章中,写了一个简单的网格地图生成脚本,不过是基于二维空间来完成的.为了更好的拓展该脚本,同时去了解学习大世界地图加载的一些知识,这段时间会通过一个系列的文章做一个类似于我的世界那样的 ...

  5. html 生成image java makenoise,[图形学] 柏林噪声 (perlin noise)

    参考论文:<An Image Synthesizer> Ken Perlin 如果你是游戏玩家,你也许曾在游戏风景中驻足,并仔细观察草木和岩石的贴图,并感叹于它那看似杂乱而又自然的纹脉.你 ...

  6. 木木的Unity学习笔记(四)—— Unity中的柏林噪声(Perlin Noise)

    木木的Unity学习笔记(四)-- Unity中的柏林噪声 柏林噪声是一个非常强大算法,经常用于程序生成随机内容,在游戏和其他像电影等多媒体领域广泛应用.算法发明者Ken Perlin也因此算法获得奥 ...

  7. 柏林噪声产生火焰等纹理

    from: http://blog.csdn.net/jia20003/article/details/7220740 柏林噪声是一种特殊的随机噪声,即对于每个给定的值产生的随机数是唯一的,但是不同的 ...

  8. 【Ray Tracing The Next Week 超详解】 光线追踪2-4 Perlin noise

     Preface 为了得到更好的纹理,很多人采用各种形式的柏林噪声(该命名来自于发明人 Ken Perlin) 柏林噪声是一种比较模糊的白噪声的东西:(引用书中一张图) 柏林噪声是用来生成一些看似杂乱 ...

  9. Perlin Noise

    原理 Perlin Noise属于晶格(grid) 噪声,其将空间分成一个个晶格(单位长度),输入的位置点配合晶格顶点处的随机梯度,生成噪声.常用于游戏中的地形生成等. 以二维Perlin Noise ...

最新文章

  1. Oralce数据库数据迁移到另一个数据
  2. 181. maven项目ssm(父工程 子工程)
  3. map分组后取前10个_海关数据 | 图解前10个月外贸
  4. javax线程池超时结束_没有Javax的Jakarta EE:这次世界也不会结束
  5. java readline 超时_跳过Java中的BufferedReader readLine()方法
  6. 基于角色的访问控制模型(RBAC)——学习笔记
  7. “我没搞懂元宇宙,但一天能赚9w块”
  8. 微课|中学生可以这样学Python(6.5节):lambda表达式
  9. swift3.0 coreData的基本使用,简单实现增删改查
  10. 用goquery从国家统计局拉取最新省市区3级行政区划代码,生成SQL文件导入数据库
  11. http-server 简介
  12. 图片的毛玻璃效果学习
  13. linux gcc生成可执行代码命令,Makefile万能写法(gcc程序以及arm-linux-gcc程序)
  14. Office EXCEL 如何将复制的一堆数据按空格断开
  15. 弧齿锥齿轮零件图_弧齿锥齿轮加工原理
  16. LG GP750 显示器评测
  17. python dataframe增加一行_python - 在pandas.DataFrame中添加一行
  18. 动词、名词、形容词还有什么词
  19. 国产银河麒麟系统配置源
  20. 阿里“看”AI + “ET大脑”战略启动

热门文章

  1. 3G UMTS与4G LTE核心网(二):4G网络概述
  2. Rasa课程、Rasa培训、Rasa面试、Rasa实战系列之UnexpecTED Intent Policy
  3. 【江苏省大学生核心就业能力培训(笔记)】
  4. GitHub入门:github查看项目的历史版本,并实现版本的回滚(网页版)
  5. 洛谷P1551 亲戚
  6. Java操作数据库方式(六)DataSource详解
  7. 在带有触控 ID 的妙控键盘上无法正常使用触控 ID的解决方法
  8. 关于Spark的部署yarn模式
  9. 驱动精灵 v9.61 去广告最终版绿色清爽单文件
  10. 课程设计实验--火车票座位分配