基于C++實(shí)現(xiàn)柏林噪聲算法(Perlin?Noise)
概述
引述維基百科的介紹:
Perlin噪聲(Perlin noise,又稱(chēng)為柏林噪聲)指由Ken Perlin發(fā)明的自然噪聲生成算法,具有在函數(shù)上的連續(xù)性,并可在多次調(diào)用時(shí)給出一致的數(shù)值。 在電子游戲領(lǐng)域中可以透過(guò)使用Perlin噪聲生成具連續(xù)性的地形;或是在藝術(shù)領(lǐng)域中使用Perlin噪聲生成圖樣。
維基百科的介紹相當(dāng)?shù)墓俜?,其?shí)可以理解為一個(gè)隨機(jī)函數(shù),不過(guò)有以下兩個(gè)特點(diǎn):
- 連續(xù)的輸入得到的輸出更加平滑(對(duì)連續(xù)的輸入有一定權(quán)重采樣)
- 相同的輸入必定得到相同的輸出(有的隨機(jī)函數(shù)有狀態(tài)(時(shí)間種子),這里更像是Hash函數(shù))
它適用于希望給定連續(xù)的輸入,能夠給出相對(duì)連續(xù)的隨機(jī)輸出。(例如,模擬自然地形生成:想象地形不能前一步是高山,腳下是深谷,后一步又是高山這種連續(xù)劇烈的變化)
隨機(jī)函數(shù)噪聲:

柏林噪聲:

原理
對(duì)于有經(jīng)驗(yàn)的同學(xué)來(lái)說(shuō),一提到“平滑”,直覺(jué)上就會(huì)想到插值、平滑函數(shù)等。沒(méi)錯(cuò),柏林噪聲其實(shí)就是使用插值、平滑函數(shù),有時(shí)會(huì)在此基礎(chǔ)上使用倍頻,波形疊加(傅里葉變換)等方法對(duì)波形調(diào)整。

先把復(fù)雜問(wèn)題簡(jiǎn)單化,考慮一個(gè)一維的柏林噪聲生成:
上面提到了插值,插值首先要有值:靜態(tài)生成一組隨機(jī)數(shù),在一個(gè)坐標(biāo)系中每單位距離散落一個(gè)隨機(jī)數(shù)。不妨令:rands是這個(gè)隨機(jī)數(shù)數(shù)組,上圖中y1 = rands[0], y2 = rands[1], ...,x2 - x1 = delta_x = 上述的單位距離,建立一個(gè)坐標(biāo)系。
對(duì)于散落在[0, rands.Len - 1]區(qū)間的某個(gè)值n來(lái)說(shuō)([rands.Len-1, rands.Len]區(qū)間對(duì)應(yīng)的x的點(diǎn)規(guī)定不能取到,因?yàn)橄旅嬗?jì)算會(huì)推到rands[n + 1]),假設(shè)n對(duì)應(yīng)上圖P點(diǎn)則有:
Noise(P) = Y1 + (Y2 - Y1) * F((xp - x1)/(delta_x))
理解下這個(gè)公式:
Y1指紅色的那個(gè)函數(shù)表達(dá)式(N),Y2指黃色的(N + 1)
Noise(P)類(lèi)型插值函數(shù): Lerp = yn + (yn+1 - yn) * t, t 取值 [0, 1],在這里:
- yn = Y1
- yn+1 = Y2
- t = F((xp - x1)/(delta_x))
這里的F是指平滑函數(shù),上述(t)可知F在[0,1]的輸出也必須在[0,1]區(qū)間內(nèi),通常F(x) = 6 * x^5 - 15 * x^4 - 10 * x^3,顧名思義就是對(duì)輸入進(jìn)行平滑,函數(shù)圖像如下:

帶入數(shù)據(jù)來(lái)算:
Noise(p) = Y1(xp) + (Y2(xp) - Y1(xp)) * F((xp - x1)/(delta_x))
就不展開(kāi)了
再來(lái)思考下它的實(shí)現(xiàn)原理:
- 隨機(jī):對(duì)于Noise(p)來(lái)說(shuō)它的值取決于y1和y2兩個(gè)隨機(jī)數(shù)
- 平滑: Noise(p)取值是通過(guò)前后插值得到的,其插值參數(shù)t也經(jīng)過(guò)平滑處理
其思路可以拓展到2維、3維,以2維舉例:

p落在abcd組成的2維網(wǎng)格中,其實(shí)可以視為3次1維的計(jì)算:分別計(jì)算pab、pcd所在1維直線(xiàn)(ab、cd)的結(jié)果,在此基礎(chǔ)上計(jì)算pad、pcd所在的線(xiàn)上p點(diǎn)的結(jié)果。這個(gè)計(jì)算會(huì)在下面的代碼實(shí)現(xiàn)中更加具象化體現(xiàn)出來(lái)。(注意有一點(diǎn)計(jì)算是不一樣的,一維中y = kx + b計(jì)算兩個(gè)點(diǎn)之間的影響在2維空間不適用,點(diǎn)會(huì)受到2個(gè)維度的影響,具體看下面實(shí)現(xiàn)中的示例)
經(jīng)典實(shí)現(xiàn)
static int p[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,
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
};
static float3 grads[12] = {
{1,1,0},
{-1,1,0},
{1,-1,0},
{-1,-1,0},
{1,0,1},
{-1,0,1},
{1,0,-1},
{-1,0,-1},
{0,1,1},
{0,-1,1},
{0,1,-1},
{0,-1,-1}
};
float grad(int hash, float x, float y, float z)
{
float3 v3 = float3(x,y,z);
hash = hash & 0xb;
return dot(grads[hash],v3);
}
int inc(int num) {
num++;
return num;
}
float fade(float t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
float perlin(float x,float y,float z)
{
int xi = (int)x & 255;
int yi = (int)y & 255;
int zi = (int)z & 255;
float xf = x - xi;
float yf = y - yi;
float zf = z - zi;
float u = fade(xf);
float v = fade(yf);
float w = fade(zf);
int aaa, aba, aab, abb, baa, bba, bab, bbb;
aaa = p[p[p[ xi ]+ yi ]+ zi ];
aba = p[p[p[ xi ]+inc(yi)]+ zi ];
aab = p[p[p[ xi ]+ yi ]+inc(zi)];
abb = p[p[p[ xi ]+inc(yi)]+inc(zi)];
baa = p[p[p[inc(xi)]+ yi ]+ zi ];
bba = p[p[p[inc(xi)]+inc(yi)]+ zi ];
bab = p[p[p[inc(xi)]+ yi ]+inc(zi)];
bbb = p[p[p[inc(xi)]+inc(yi)]+inc(zi)];
float x1, x2, y1, y2;
x1 = lerp( grad (aaa, xf , yf , zf),
grad (baa, xf-1, yf , zf),
u);
x2 = lerp( grad (aba, xf , yf-1, zf),
grad (bba, xf-1, yf-1, zf),
u);
y1 = lerp(x1, x2, v);
x1 = lerp( grad (aab, xf , yf , zf-1),
grad (bab, xf-1, yf , zf-1),
u);
x2 = lerp( grad (abb, xf , yf-1, zf-1),
grad (bbb, xf-1, yf-1, zf-1),
u);
y2 = lerp (x1, x2, v);
return lerp (y1, y2, w);
}

這段代碼是3維的perlin函數(shù),控制參數(shù)也可以實(shí)現(xiàn)1維、2維計(jì)算,從perlin函數(shù)看起:
1.靜態(tài)的p[512]數(shù)組散落隨機(jī)數(shù)數(shù)組每256個(gè)分為一塊,共兩塊(為了方便計(jì)算)。aaa = p[p[p[ xi ]+ yi ]+ zi ] 類(lèi)似的其實(shí)就是進(jìn)行一次哈希計(jì)算,打亂順序結(jié)果盡可能隨機(jī),類(lèi)似于一維中的每隔單位距離散落隨機(jī)數(shù)。
2.grads數(shù)組和grad函數(shù)就是確定這個(gè)p點(diǎn)分別受這8個(gè)頂點(diǎn)影響的程度,在計(jì)算上體現(xiàn)就是進(jìn)行內(nèi)積(投影),注意這里的類(lèi)比于一維的計(jì)算是有差別的:這里提到所謂的“梯度”,在一維計(jì)算里梯度就是指y = kx + 1中的k也就是斜率,而在三維空間中,梯度受3個(gè)維度的影響,在這里進(jìn)行了簡(jiǎn)化從預(yù)設(shè)的12個(gè)向量中選?。ㄖ劣跒槭裁匆?jiàn)參考鏈接:柏林噪聲作者論文)。
3.接著就是進(jìn)行l(wèi)erp插值,對(duì)各個(gè)頂點(diǎn)方向上的計(jì)算結(jié)果進(jìn)行平滑。
一個(gè)其他非典型實(shí)現(xiàn)示例
float rand(float2 p){
return frac(sin(dot(p ,float2(12.9898,78.233))) * 43758.5453);
}
float noise(float2 x)
{
float2 i = floor(x);
float2 f = frac(x);
float a = rand(i);
float b = rand(i + float2(1.0, 0.0));
float c = rand(i + float2(0.0, 1.0));
float d = rand(i + float2(1.0, 1.0));
float2 u = f * f * f * (f * (f * 6 - 15) + 10);
float x1 = lerp(a,b,u.x);
float x2 = lerp(c,d,u.x);
return lerp(x1,x2,u.y);
}
可以看到這種實(shí)現(xiàn)和上文中的思路是一樣的,只是hash函數(shù)和計(jì)算各個(gè)方向上的影響計(jì)算進(jìn)行了簡(jiǎn)化。
波形調(diào)整
可以看出柏林函數(shù)的輸出具有“波”的特點(diǎn),那么自然可以所有對(duì)于波的操作。
進(jìn)行類(lèi)似正弦波調(diào)幅、調(diào)頻、調(diào)相,還可以上下偏移

(f(x)=Asin(ωx+φ) + b 這里 A = 0.5, w = 2, φ = 1, b = 0.5)
波的疊加

傅里葉變換說(shuō)一個(gè)波可以由為n個(gè)波疊加而成,疊加結(jié)果如圖所示。
波形的調(diào)整在實(shí)際應(yīng)用中作用很大,如:
- 模擬生成地圖中某個(gè)區(qū)域的地質(zhì)運(yùn)動(dòng)劇烈,地形起伏很大,可以對(duì)波形調(diào)幅把振幅調(diào)大。
- 如果想讓生成的波形更加連續(xù),可以先調(diào)頻(倍頻)然后疊加
以上就是基于C++實(shí)現(xiàn)柏林噪聲算法(Perlin Noise)的詳細(xì)內(nèi)容,更多關(guān)于C++柏林噪聲算法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Qt實(shí)現(xiàn)編輯數(shù)據(jù)庫(kù)數(shù)據(jù)的方法詳解
這篇文章主要為大家詳細(xì)介紹了Qt是如何實(shí)現(xiàn)編輯數(shù)據(jù)庫(kù)數(shù)據(jù)的,文中的示例代碼簡(jiǎn)潔易懂,對(duì)我們深入了解QT有一定的幫助,感興趣的小伙伴可以了解一下2023-02-02
C++哈希表之線(xiàn)性探測(cè)法實(shí)現(xiàn)詳解
線(xiàn)性探測(cè)法的優(yōu)點(diǎn):只要散列表未滿(mǎn),總能找到一個(gè)不沖突的散列地址;缺點(diǎn):每個(gè)產(chǎn)生沖突的記錄被散列到離沖突最近的空地址上,從而又增加了更多的沖突機(jī)會(huì)2022-05-05
C語(yǔ)言中楊氏矩陣與楊輝三角的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中楊氏矩陣與楊輝三角的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
c語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的易語(yǔ)言
在本篇內(nèi)容里小編給大家整理了一篇關(guān)于c語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的易語(yǔ)言的相關(guān)知識(shí)點(diǎn),需要的朋友們參考下。2018-12-12
C語(yǔ)言修煉之路數(shù)據(jù)類(lèi)型悟正法 解析存儲(chǔ)定風(fēng)魔下篇
使用編程語(yǔ)言進(jìn)行編程時(shí),需要用到各種變量來(lái)存儲(chǔ)各種信息。變量保留的是它所存儲(chǔ)的值的內(nèi)存位置。這意味著,當(dāng)您創(chuàng)建一個(gè)變量時(shí),就會(huì)在內(nèi)存中保留一些空間。您可能需要存儲(chǔ)各種數(shù)據(jù)類(lèi)型的信息,操作系統(tǒng)會(huì)根據(jù)變量的數(shù)據(jù)類(lèi)型,來(lái)分配內(nèi)存和決定在保留內(nèi)存中存儲(chǔ)什么2022-02-02
C++二叉搜索樹(shù)模擬實(shí)現(xiàn)示例
本文主要介紹了C++二叉搜索樹(shù)模擬實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11
關(guān)于C++中數(shù)據(jù)16進(jìn)制輸出的方法
本文主要介紹了關(guān)于C++中數(shù)據(jù)16進(jìn)制輸出的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03

