一篇文章搞懂柏林噪聲算法炕婶,附代碼講解

本文以一種通俗簡(jiǎn)單的方式介紹Ken Perlin的改進(jìn)版柏林噪聲算法姐赡,算法代碼采用c#編寫(xiě),開(kāi)源免費(fèi)使用柠掂。如果你只是想看完整代碼项滑,點(diǎn)擊這里

柏林噪聲是一個(gè)非常強(qiáng)大算法涯贞,經(jīng)常用于程序生成隨機(jī)內(nèi)容枪狂,在游戲和其他像電影等多媒體領(lǐng)域廣泛應(yīng)用。算法發(fā)明者Ken Perlin也因此算法獲得奧斯卡科技成果獎(jiǎng)(靠算法拿奧斯卡也是沒(méi)誰(shuí)了666)宋渔。本文將剖析他于2002年發(fā)表的改進(jìn)版柏林噪聲算法州疾。在游戲開(kāi)發(fā)領(lǐng)域,柏林噪聲可以用于生成波形皇拣,起伏不平的材質(zhì)或者紋理严蓖。例如薄嫡,它能用于程序生成地形(例如使用柏林噪聲來(lái)生成我的世界(Minecraft)里的地形),火焰燃燒特效颗胡,水和云等等毫深。柏林噪聲絕大部分應(yīng)用在2維,3維層面上毒姨,但某種意義上也能拓展到4維哑蔫。柏林噪聲在1維層面上可用于卷軸地形、模擬手繪線條等弧呐。
如果將柏林噪聲拓展到4維層面闸迷,以第4維,即w軸代表時(shí)間泉懦,就能利用柏林噪聲做動(dòng)畫(huà)稿黍。例如疹瘦,2D柏林噪聲可以通過(guò)插值生成地形崩哩,而3D柏林噪聲則可以模擬海平面上起伏的波浪。下面是柏林噪聲在不同維度的圖像以及在游戲中的應(yīng)用場(chǎng)景言沐。

噪聲維度 原始噪聲(灰階) 游戲應(yīng)用
1
使用噪聲進(jìn)行偏移邓嘹,制造手繪效果
2
制造火焰燃燒特效
3
最普遍的運(yùn)用莫過(guò)于生成游戲里的隨機(jī)地形

正如圖所示,柏林噪聲算法可以用來(lái)模擬許多自然中的噪聲現(xiàn)象险胰。接下來(lái)讓我們從數(shù)理上分析算法的實(shí)現(xiàn)原理汹押。

基本原理

注意:事先聲明,本節(jié)內(nèi)容大多源于this wonderful article by Matt Zucker起便,不過(guò)該篇文章內(nèi)容也是建立在1980年所發(fā)明的柏林噪聲算法基礎(chǔ)上的棚贾。本文我將使用2002年發(fā)明的改進(jìn)版柏林噪聲算法。因此榆综,我的算法版本跟Zucker的版本會(huì)有些不同妙痹。

讓我們從最基本的柏林噪聲函數(shù)看起:
public double perlin(double x, double y, double z);

函數(shù)接收x,y,z三個(gè)坐標(biāo)分量作為輸入,并返回0.0~1.0的double值作為輸出鼻疮。那我們應(yīng)該怎么處理輸入值怯伊?首先,我們?nèi)?個(gè)輸入值x,y,z的小數(shù)點(diǎn)部分判沟,就可以表示為單元空間里的一個(gè)點(diǎn)了耿芹。為了方便講解,我們將問(wèn)題降維到2維空間來(lái)討論(原理是一樣的)挪哄,下圖是該點(diǎn)在2維空間上的表示:

圖1:小藍(lán)點(diǎn)代表輸入值在單元正方形里的空間坐標(biāo)吧秕,其他4個(gè)點(diǎn)則是單元正方形的各頂點(diǎn)

接著,我們給4個(gè)頂點(diǎn)(在3維空間則是8個(gè)頂點(diǎn))各自生成一個(gè)偽隨機(jī)的梯度向量迹炼。梯度向量代表該頂點(diǎn)相對(duì)單元正方形內(nèi)某點(diǎn)的影響是正向還是反向的(向量指向方向?yàn)檎蚩艿椋喾捶较驗(yàn)榉聪颍6鴤坞S機(jī)是指,對(duì)于任意組相同的輸入拿霉,必定得到相同的輸出吟秩。因此,雖然每個(gè)頂點(diǎn)生成的梯度向量看似隨機(jī)绽淘,實(shí)際上并不是涵防。這保證了*在生成函數(shù)不變的情況下,每個(gè)坐標(biāo)的梯度向量都是確定不變的沪铭。

舉個(gè)例子來(lái)理解偽隨機(jī)壮池,比如我們從圓周率π(3.14159...)的小數(shù)部分中隨機(jī)抽取某一位數(shù)字,結(jié)果看似隨機(jī)杀怠,但如果抽取小數(shù)點(diǎn)后1位椰憋,結(jié)果必定為1;抽取小數(shù)點(diǎn)后2位赔退,結(jié)果必定為4橙依。

圖2:各頂點(diǎn)上的梯度向量隨機(jī)選取結(jié)果

請(qǐng)注意,上圖所示的梯度向量并不是完全準(zhǔn)確的硕旗。在本文所介紹的改進(jìn)版柏林噪聲中窗骑,這些梯度向量并不是完全隨機(jī)的,而是由單位正方體(3維)的中心點(diǎn)指向各條邊中點(diǎn)的12個(gè)向量:
(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)

采用這些特殊梯度向量的原因在Ken Perlin's SIGGRAPH 2002 paper: Improving Noise這篇文章里有具體講解漆枚。

注意:許多介紹柏林噪聲算法的文章都是根據(jù)最初版柏林噪聲算法來(lái)講解的创译,其預(yù)定義的梯度表不是本文所說(shuō)的這12個(gè)向量。如圖2所示的梯度向量就是最初版算法所隨機(jī)出來(lái)的梯度向量墙基,不過(guò)這兩種算法的原理都是一樣的软族。

接著,我們需要求出另外4個(gè)距離向量(在3維空間則是8個(gè))残制,它們分別從各頂點(diǎn)指向輸入點(diǎn)(藍(lán)色點(diǎn))立砸。下面有個(gè)2維空間下的例子:

圖3:各個(gè)距離向量

對(duì)每個(gè)頂點(diǎn)的梯度向量和距離向量做點(diǎn)積運(yùn)算,就可以得出每個(gè)頂點(diǎn)的影響值:
grad.x * dist.x + grad.y * dist.y + grad.z * dist.z

這正是算法所需要的值痘拆,點(diǎn)積運(yùn)算為兩向量長(zhǎng)度之積仰禽,再乘以兩向量夾角余弦:

dot(vec1,vec2) = cos(angle(vec1,vec2)) * vec1.length * vec2.length

換句話說(shuō),如果兩向量指向同一方向纺蛆,點(diǎn)積結(jié)果為:

1 * vec1.length * vec2.length

如果兩向量指向相反方向吐葵,則點(diǎn)積結(jié)果為:

-1 * vec1.length * vec2.length

如果兩向量互相垂直,則點(diǎn)積結(jié)果為0桥氏。

0 * vec1.length * vec2.length

點(diǎn)積也可以理解為向量a在向量b上的投影温峭,當(dāng)距離向量在梯度向量上的投影為同方向,點(diǎn)積結(jié)果為正數(shù)字支;當(dāng)距離向量在梯度向量上的投影為反方向凤藏,點(diǎn)積結(jié)果為負(fù)數(shù)奸忽。因此,通過(guò)兩向量點(diǎn)積揖庄,我們就知道該頂點(diǎn)的影響值是正還是負(fù)的栗菜。不難看出,頂點(diǎn)的梯度向量直接決定了這一點(diǎn)蹄梢。下面通過(guò)一副彩色圖疙筹,直觀地看下各頂點(diǎn)的影響值:

圖4:2D柏林噪聲的影響值

下一步,我們需要對(duì)4個(gè)頂點(diǎn)的影響值做插值禁炒,求得加權(quán)平均值(在3維空間則是8個(gè))而咆。算法非常簡(jiǎn)單(2維空間下的解法):

// Below are 4 influence values in the arrangement:
// [g1] | [g2]
// -----------
// [g3] | [g4]
int g1, g2, g3, g4;
int u, v;   // These coordinates are the location of the input coordinate in its unit square.  
            // For example a value of (0.5,0.5) is in the exact center of its unit square.

int x1 = lerp(g1,g2,u);
int x2 = lerp(g3,h4,u);

int average = lerp(x1,x2,v);

至此,整個(gè)柏林噪聲算法還剩下最后一塊拼圖了:如果直接使用上述代碼幕袱,由于是采用lerp線性插值計(jì)算得出的值暴备,雖然運(yùn)行效率高,但噪聲效果不好们豌,看起來(lái)會(huì)不自然涯捻。我們需要采用一種更為平滑,非線性的插值函數(shù):fade函數(shù)玛痊,通常也被稱為ease curve(也作為緩動(dòng)函數(shù)在游戲中廣泛使用):

圖5:ease curve

ease curve的值會(huì)用來(lái)計(jì)算前面代碼里的u和v汰瘫,這樣插值變化不再是單調(diào)的線性變化狂打,而是這樣一個(gè)過(guò)程:初始變化慢擂煞,中間變化快,結(jié)尾變化又慢下來(lái)(也就是在當(dāng)數(shù)值趨近于整數(shù)時(shí)趴乡,變化變慢)对省。這個(gè)用于改善柏林噪聲算法的fade函數(shù)可以表示為以下數(shù)學(xué)形式:

基本上,這就是整個(gè)柏林噪聲算法的原理了晾捏!搞清了算法的各個(gè)實(shí)現(xiàn)關(guān)鍵步驟后蒿涎,現(xiàn)在讓我們著手把代碼實(shí)現(xiàn)出來(lái)。

代碼實(shí)現(xiàn)

在本節(jié)開(kāi)始前我需要重申一遍惦辛,代碼實(shí)現(xiàn)是C#版本劳秋。相比Ken Perlin的Java版本實(shí)現(xiàn)做了小小的改動(dòng),主要是增加了代碼的整潔性和可讀性胖齐,支持噪聲重復(fù)(瓦片重復(fù))特性玻淑。代碼完全開(kāi)源,可免費(fèi)使用(考慮到這畢竟不是我原創(chuàng)發(fā)明的算法 - Ken Perlin才是Q交铩)

準(zhǔn)備工作

第一步补履,我們需要先聲明一個(gè)排列表(permutation table),或者直接縮寫(xiě)為p[]數(shù)組就行了剿另。數(shù)組長(zhǎng)度為256箫锤,分別隨機(jī)贬蛙、無(wú)重復(fù)地存放了0-255這些數(shù)值。為了避免緩存溢出谚攒,我們?cè)僦貜?fù)填充一次數(shù)組的值阳准,所以數(shù)組最終長(zhǎng)度為512:

private static readonly int[] permutation = { 151,160,137,91,90,15,                 // Hash lookup table as defined by Ken Perlin.  This is a randomly
    131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,    // arranged array of all numbers from 0-255 inclusive.
    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
};

private static readonly int[] p;                                                    // Doubled permutation to avoid overflow

static Perlin() {
    p = new int[512];
    for(int x=0;x<512;x++) {
        p[x] = permutation[x%256];
    }
}

p[]數(shù)組會(huì)在算法后續(xù)的哈希計(jì)算中使用到,用于確定一組輸入最終挑選哪個(gè)梯度向量(從前面所列出的12個(gè)梯度向量中挑選)馏臭。后續(xù)代碼會(huì)詳細(xì)展示p[]數(shù)組的用法溺职。

接著,我們開(kāi)始聲明柏林噪聲函數(shù):

public double perlin(double x, double y, double z) {
    if(repeat > 0) {                                    // If we have any repeat on, change the coordinates to their "local" repetitions
        x = x%repeat;
        y = y%repeat;
        z = z%repeat;
    }
    
    int xi = (int)x & 255;                              // Calculate the "unit cube" that the point asked will be located in
    int yi = (int)y & 255;                              // The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
    int zi = (int)z & 255;                              // plus 1.  Next we calculate the location (from 0.0 to 1.0) in that cube.
    double xf = x-(int)x;
    double yf = y-(int)y;
    double zf = z-(int)z;
    // ...
}

上面的代碼很直觀位喂。首先浪耘,對(duì)輸入坐標(biāo)使用求余運(yùn)算符%,求出[0,repeat)范圍內(nèi)的余數(shù)塑崖。聲明xi, yi, zi三個(gè)變量七冲。它們代表了輸入坐標(biāo)落在了哪個(gè)單元正方形里。我們還要限制坐標(biāo)在[0,255]這個(gè)范圍內(nèi)规婆,避免訪問(wèn)數(shù)組p[]時(shí)澜躺,出現(xiàn)數(shù)組越界錯(cuò)誤。這也產(chǎn)生了一個(gè)副作用:柏林噪聲每隔256個(gè)整數(shù)就會(huì)再次重復(fù)抒蚜。但這不是太大的問(wèn)題掘鄙,因?yàn)樗惴ú粌H能處理整數(shù),還能處理小數(shù)嗡髓。最后操漠,我們通過(guò)xf, yf, zf三個(gè)變量(也就是x,y,z的小數(shù)部分值),確定了輸入坐標(biāo)在單元正方體里的空間位置(就是前面所示的小藍(lán)點(diǎn))饿这。

Fade函數(shù)

現(xiàn)在我們需要用代碼表示前面所提到的fade函數(shù)(圖5)浊伙。正如上文所提,函數(shù)的數(shù)學(xué)表示:


代碼實(shí)現(xiàn)如下:

public static double fade(double t) {
                                                        // Fade function as defined by Ken Perlin.  This eases coordinate values
                                                        // so that they will ease towards integral values.  This ends up smoothing
                                                        // the final output.
    return t * t * t * (t * (t * 6 - 15) + 10);         // 6t^5 - 15t^4 + 10t^3
}

public double perlin(double x, double y, double z) {
    // ...

    double u = fade(xf);
    double v = fade(yf);
    double w = fade(zf);

    // ...
}

代碼所計(jì)算得出的u / v / w變量將在后面的插值計(jì)算中使用到长捧。

哈希函數(shù)

哈希函數(shù)的作用就在于給每組輸入計(jì)算返回一個(gè)唯一嚣鄙、確定值。哈希函數(shù)在維基百科的定義如下:

哈希函數(shù)是一種從任何一種數(shù)據(jù)中創(chuàng)建小的數(shù)字“指紋”的方法串结,輸入數(shù)據(jù)有任何細(xì)微的不同哑子,都會(huì)令輸出結(jié)果完全不一樣

下面代碼就是柏林噪聲算法所使用的哈希函數(shù)。它使用了早前我們聲明的p[]數(shù)組:

public double perlin(double x, double y, double z) {
    // ...

    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)];

    // ...
}

public int inc(int num) {
    num++;
    if (repeat > 0) num %= repeat;
    
    return num;
}

代碼的哈希函數(shù)肌割,對(duì)包圍著輸入坐標(biāo)(小藍(lán)點(diǎn))的周?chē)?個(gè)單元正方形的索引坐標(biāo)進(jìn)行了哈希計(jì)算卧蜓。inc()函數(shù)用于將輸入值增加1,同時(shí)保證范圍在[0,repeat)內(nèi)声功。如果不需要噪聲重復(fù)烦却,inc()函數(shù)可以簡(jiǎn)化成單純將輸入值增加1。由于哈希結(jié)果值是從p[]數(shù)組中得到的先巴,所以哈希函數(shù)的返回值范圍限定在[0,255]內(nèi)其爵。

梯度函數(shù)

我時(shí)常認(rèn)為Ken Perlin的最初版算法里的grad()函數(shù)寫(xiě)法過(guò)于復(fù)雜冒冬,令人費(fèi)解。我們只要明白grad()函數(shù)的作用在于計(jì)算隨機(jī)選取的梯度向量以及頂點(diǎn)位置向量的點(diǎn)積摩渺。Ken Perlin巧妙地使用了位翻轉(zhuǎn)(bit-flipping)技巧來(lái)實(shí)現(xiàn):

public static double grad(int hash, double x, double y, double z) {
    int h = hash & 15;                                    // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
    double u = h < 8 /* 0b1000 */ ? x : y;                // If the most significant bit (MSB) of the hash is 0 then set u = x.  Otherwise y.
    
    double v;                                             // In Ken Perlin's original implementation this was another conditional operator (?:).  I
                                                          // expanded it for readability.
    
    if(h < 4 /* 0b0100 */)                                // If the first and second significant bits are 0 set v = y
        v = y;
    else if(h == 12 /* 0b1100 */ || h == 14 /* 0b1110*/)  // If the first and second significant bits are 1 set v = x
        v = x;
    else                                                  // If the first and second significant bits are not equal (0/1, 1/0) set v = z
        v = z;
    
    return ((h&1) == 0 ? u : -u)+((h&2) == 0 ? v : -v); // Use the last 2 bits to decide if u and v are positive or negative.  Then return their addition.
}

下面代碼則是以另一種令人容易理解的方式完成了這個(gè)任務(wù)(而且在很多語(yǔ)言版本的運(yùn)行效率都優(yōu)于前面一種):

// Source: http://riven8192.blogspot.com/2010/08/calculate-perlinnoise-twice-as-fast.html
public static double grad(int hash, double x, double y, double z)
{
    switch(hash & 0xF)
    {
        case 0x0: return  x + y;
        case 0x1: return -x + y;
        case 0x2: return  x - y;
        case 0x3: return -x - y;
        case 0x4: return  x + z;
        case 0x5: return -x + z;
        case 0x6: return  x - z;
        case 0x7: return -x - z;
        case 0x8: return  y + z;
        case 0x9: return -y + z;
        case 0xA: return  y - z;
        case 0xB: return -y - z;
        case 0xC: return  y + x;
        case 0xD: return -y + z;
        case 0xE: return  y - x;
        case 0xF: return -y - z;
        default: return 0; // never happens
    }
}

以上的源碼可以點(diǎn)擊這里查看简烤。無(wú)論如何,上面的兩種實(shí)現(xiàn)并沒(méi)有實(shí)質(zhì)差別摇幻。他們都是從以下12個(gè)向量里隨機(jī)挑選一個(gè)作為梯度向量:
(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)

隨機(jī)挑選結(jié)果其實(shí)取決于前一步所計(jì)算得出的哈希值(grad()函數(shù)的第一個(gè)參數(shù))横侦。后面3個(gè)參數(shù)則代表由輸入點(diǎn)指向頂點(diǎn)的距離向量(最終拿來(lái)與梯度向量進(jìn)行點(diǎn)積)。

插值整合

經(jīng)過(guò)前面的幾步計(jì)算绰姻,我們得出了8個(gè)頂點(diǎn)的影響值枉侧,并將它們進(jìn)行平滑插值,得出了最終結(jié)果:

public double perlin(double x, double y, double z) {
    // ...

    double x1, x2, y1, y2;
    x1 = lerp(    grad (aaa, xf  , yf  , zf),           // The gradient function calculates the dot product between a pseudorandom
                grad (baa, xf-1, yf  , zf),             // gradient vector and the vector from the input coordinate to the 8
                u);                                     // surrounding points in its unit cube.
    x2 = lerp(    grad (aba, xf  , yf-1, zf),           // This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
                grad (bba, xf-1, yf-1, zf),             // values we made earlier.
                  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)+1)/2;                      // For convenience we bind the result to 0 - 1 (theoretical min/max before is [-1, 1])
}

// Linear Interpolate
public static double lerp(double a, double b, double x) {
    return a + x * (b - a);
}

利用倍頻實(shí)現(xiàn)更自然的噪聲

最后讓我們?cè)偎伎枷驴裼螅饲懊嫠v的計(jì)算榨馁,還有其他辦法可以令噪聲結(jié)果更加自然嗎?雖然柏林噪聲算法一定程度上模擬了自然噪聲帜矾,但仍沒(méi)有完全表現(xiàn)出自然噪聲的不規(guī)律性翼虫。舉個(gè)現(xiàn)實(shí)例子,現(xiàn)實(shí)地形會(huì)有大段連綿屡萤、高聳的山地珍剑,也會(huì)有丘陵和蝕坑,更小點(diǎn)的有大塊巖石死陆,甚至更小的鵝卵石塊招拙,這都屬于地形的一部分。那如何讓柏林噪聲算法模擬出這樣的自然噪聲特性翔曲,解決方法也很簡(jiǎn)單:我們可以使用不同的頻率(frequencies)和振幅(amplitudes)參數(shù)進(jìn)行多幾次柏林噪聲計(jì)算迫像,然后將結(jié)果疊加在一起劈愚。頻率是指采樣數(shù)據(jù)的間隔,振幅是指返回值的幅度范圍菌羽。

圖6:不同頻率和振幅參數(shù)下的柏林噪聲結(jié)果

將所有結(jié)果疊加在一起,我們就能得到以下結(jié)果:

圖7:圖6所有噪聲的疊加結(jié)果

很明顯猾蒂,這樣的噪聲結(jié)果更加令人信服。上面的6組噪聲被稱之為噪聲的不同倍頻(Octave)是晨。隨著倍頻增大肚菠,噪聲對(duì)于最終疊加噪聲的影響程度變小罩缴。當(dāng)然层扶,倍頻組數(shù)的增加,會(huì)線性地增加代碼執(zhí)行時(shí)間烙荷,在游戲運(yùn)行時(shí)使用噪聲算法镜会,再好不要使用超過(guò)幾組倍頻(比如终抽,當(dāng)你想在60fps下模擬火焰特效時(shí),最好不要這么干)匾旭。然而圃郊,做數(shù)據(jù)預(yù)處理時(shí),就很適合使用多組倍頻疊加來(lái)模擬更自然的噪聲(比如用于提前生成游戲地形等)描沟。

那我們應(yīng)該分別挑選多大的頻率和振幅來(lái)進(jìn)行噪聲計(jì)算呢吏廉?這個(gè)可以通過(guò)persistence參數(shù)確定。Hugo Elias對(duì)persistence的定義使用如下:

以上公式i的值取決于倍頻數(shù)量史辙,代碼實(shí)現(xiàn)也很簡(jiǎn)單:

public double OctavePerlin(double x, double y, double z, int octaves, double persistence) {
    double total = 0;
    double frequency = 1;
    double amplitude = 1;
    double maxValue = 0;  // Used for normalizing result to 0.0 - 1.0
    for(int i=0;i<octaves;i++) {
        total += perlin(x * frequency, y * frequency, z * frequency) * amplitude;
        
        maxValue += amplitude;
        
        amplitude *= persistence;
        frequency *= 2;
    }
    
    return total/maxValue;
}

小結(jié)

以上就是算法的全部?jī)?nèi)容佩伤,我們現(xiàn)在可以使用算法制造噪聲了。再次聲明耙蔑,你可以點(diǎn)擊這里查看完整源碼孤荣。


本文原文出自Understanding Perlin Noise,翻譯不易钱豁,如果你已經(jīng)看到了這里疯汁,就順手點(diǎn)個(gè)贊吧哈哈!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谤碳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子估蹄,更是在濱河造成了極大的恐慌,老刑警劉巖臭蚁,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件最铁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡冷尉,警方通過(guò)查閱死者的電腦和手機(jī)系枪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)雾棺,“玉大人衬浑,你說(shuō)我怎么就攤上這事∈龋” “怎么了助币?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵眉菱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我迈螟,道長(zhǎng)尔崔,這世上最難降的妖魔是什么褥民? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任消返,我火速辦了婚禮耘拇,結(jié)果婚禮上宇攻,老公的妹妹穿的比我還像新娘。我一直安慰自己嘉涌,他們只是感情好夸浅,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布帆喇。 她就那樣靜靜地躺著,像睡著了一般预皇。 火紅的嫁衣襯著肌膚如雪婉刀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天溯街,我揣著相機(jī)與錄音洋丐,去河邊找鬼。 笑死堤尾,一個(gè)胖子當(dāng)著我的面吹牛迁客,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粘室,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼卜范,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了锦爵?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沪袭,失蹤者是張志新(化名)和其女友劉穎冈绊,沒(méi)想到半個(gè)月后嗡害,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡十电,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年鹃骂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罢绽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寝殴,死狀恐怖明垢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抵蚊,我是刑警寧澤溯革,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布致稀,位于F島的核電站,受9級(jí)特大地震影響豺裆,放射性物質(zhì)發(fā)生泄漏臭猜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一羹应、第九天 我趴在偏房一處隱蔽的房頂上張望次屠。 院中可真熱鬧,春花似錦裸违、人聲如沸本昏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宿稀。三九已至,卻和暖如春矮烹,著一層夾襖步出監(jiān)牢的瞬間罩锐,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工嘹吨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留境氢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓问芬,卻偏偏與公主長(zhǎng)得像此衅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挡鞍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容