柏林噪声(Perlin Noise)

柏林噪声(Perlin Noise)估计你能找到我这篇没人看的文章也是被网上的相关博客给弄得头大 其实柏林噪声并不是很难 但是由于 perlin 的疏忽和网上一些错误博客 导致 perlinnoise 变得很混乱 恩 本菜鸡的这篇很有

大家好,欢迎来到IT知识分享网。

什么是柏林噪声?

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

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

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

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

perlin noise

步骤就变成了:

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.) + 1.0) / 2.0*255.0; outfile << temp << " " << temp << " " << temp << " "; } } } 

下面这些就是已经定义好的随机向量,它们是由立方体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]; } } } } 
 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); 
 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 ] 
 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的介绍(后面所有代码均以文末代码为基础写的

这样做的结果是一个包含不同大小特性的函数。低频率函数的大幅度振动构成基本的形状,而高频率函数的小幅度振动构成小范围的细节信息。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.415)*amplitude; maxValue += amplitude; } sum /= maxValue; int b = (sum+1)*255.0/2.0; outfile << b << " " << b << " " << b << endl; //* } } 
sum += abs(pNoise.noise(i*frequency, j*frequency, 7.415))*amplitude; 
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.415))*amplitude; } sum = sin(sum + i * frequency / 16.0); int b = (sum+1)*255.0/2.0; outfile << b << " " << b << " " << b << endl; 
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.415); int b = (sum + 1)*255.0 / 2.0; outfile << b << " " << b << " " << b << endl; } } //cout << count; return 0; } 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/119489.html

(0)
上一篇 2025-11-05 09:00
下一篇 2025-11-05 09:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信