分形噪聲

原本想研究柏林噪聲,結果發現自己想研究的原來是分形噪聲,這就尷尬了,我想說其實百度到的大多數柏林噪聲的資料其實都是分形噪聲,除了這位同學:

[圖形學]-談談噪聲:http://blog.csdn.net/candycat1992/article/details/50346469

給你點個贊。


不過我自己還是從一維噪聲開始學吧,畢竟不會分形噪聲,其實寫博文的時候我沒成功碼出過分形噪聲的代碼,只是理清了一點理論罷了,也只有這樣的程序才適合作爲新手教程不是麼(。•ˇ‸ˇ•。)(沾沾自喜)


什麼是分形噪聲?

[出自維基百科 perlin 噪聲版塊]:https://zh.wikipedia.org/wiki/Perlin%E5%99%AA%E5%A3%B0

分形噪聲可以用來模擬自然界的自相似過程,包括海岸線,地形,海浪等。分形噪聲的原理是利用Perlin噪聲頻率受限的特性,通過不斷疊加更高頻率的perlin噪聲達到自相似的效果。

參考圖,亦來自維基,本篇文中所有代碼都以實現這兩張樣圖爲目標:

圖1爲不同頻率的分形噪聲:


圖2爲合併後的分形噪聲,由座標值好像可以看出它其實沒有歸一??:


不過這種東西,都是能夠舉一反三的,即然能用柏林噪聲分形,自然普通的噪聲也可以,現在我還不會柏林噪聲,所以還是拿上篇的不知名噪聲來試驗吧,首先還是創建一組白噪聲:

function generateWhiteNoise1D(width:int):Array
{
	var whiteNoise:Array = [];
	for (var i:int = 0; i < width; i ++ )
	{
		whiteNoise[i] = Random.random();
	}
	return whiteNoise;
}
//隨機種子給出6
Random.seed(6);
//噪聲數據長度
var width:int = 10;
//生成一維白噪聲
var whiteNoise:Array = this.generateWhiteNoise1D(width);
然後再將白噪聲畫出來,這次直接用graphics而不是用像素畫了:

/*
 * 由於波長的存在,爲了完整地繪製包括最後一個噪聲數據的曲線
 * 故需要對 X 進行重新計算
 */ 
function noise1D(baseNoise:Array, x:int):Number
{
	if (x > baseNoise.length - 1)
	{
		x = x % baseNoise.length;
	}
	return baseNoise[x];
}
function interpolate(a:Number, b:Number, t:Number):Number
{
	var ft:Number = t * Math.PI;
	var f:Number = (1 - Math.cos(ft)) * 0.5;
	return a * (1 - f) + b * f;
}
/*
 * 此處應當接受振幅與波長作爲參數,用以畫出不同的噪聲曲線
 * 默認振幅256,波長100
 */
function drawLine(noise:Array, color:uint, amplitude:int, wavelength:int):void
{
	var offsetx:int = 20;
	var offsety:int = 300;
	//繪畫噪聲曲線
	this.graphics.lineStyle(1, color);
	//
	for (var i:int = 0; i < noise.length; i ++ )
	{
		var a:Number = this.noise1D(noise, i);
		var b:Number = this.noise1D(noise, i + 1);
		for (var j:int = 0; j < wavelength; j ++)
		{
			var y:Number = this.interpolate(a, b, j / wavelength);
			//第一個噪聲數據作爲曲線原點
			if (i == 0 && j == 0)
			{
				this.graphics.moveTo(i * wavelength + j + offsetx, y * amplitude + offsety);
			}
			else
			{
				this.graphics.lineTo(i * wavelength + j + offsetx, y * amplitude + offsety);
			}
		}
	}
}
//繪製一維噪聲圖,振幅256,波長100
this.drawLine(whiteNoise, 0, 256, 100);
這是效果圖,看起來很舒服有木有:


由於分形噪聲是由多條不同振幅和頻率的噪聲疊加出來的,故我們必須繼續生成多組不同的噪聲數據,不過這裏先要引進一個新的變量,叫做持續度(persistence)


什麼是持續度?(persistence)
我對持續度的認識來自於 Memo 的這篇譯文:http://www.cnblogs.com/Memo/archive/2008/09/08/1286963.html
這裏說:持續是用來表示噪聲在不同頻率下的振幅的,儘管這個詞和它真實意義有些歧異。
網上很多人的代碼裏都在用這個詞,但沒有任何一個人在解釋它究竟是什麼東西,自然所有他們公開的代碼,我也一個都沒有跑成功過,對此只能微微一笑了,依然還是自力更生吧,Memo 譯文裏不同 persistence 不同倍頻的例圖給了我很好的靈感,我覺得簡單地說,持續度影響着噪聲在不同倍頻下振幅和波長的變化,持續度低,振幅衰減越快,波長也就越小。比如:持續度爲1的噪聲在倍頻變化時永遠不會衰減;持續度爲0.5的噪聲每次倍頻疊加,振幅都會衰減50%,直到無窮小;以此類推,持續度爲0的噪聲振幅在倍頻第一次進行疊加時就會衰減爲0,波長也一個道理,這就好辦了,開始碼字:

//持續度
var persistence:Number = 0.5;

這裏有箇中間數據,我叫它octaveNoise,它的數據取決於倍頻:

//倍頻
var octaveCount:int = 6;
var octaveNoise:Array = [];
//生成不同倍頻的噪聲
for (var octave:int = 0; octave < octaveCount; octave ++ )
{
	octaveNoise[octave] = this.generateOctaveNoise1D(octave);
}

定義一下噪聲生成函數:

function generateOctaveNoise1D(octave:int):Array
{
	var octaveNoise:Array = [];
	//頻率
	var frequency:int = Math.pow(2, octave);
	//振幅,波長與其等值
	var amplitude:Number = Math.pow(this.persistence, octave);
	trace("frequency:", frequency, ", amplitude:", amplitude);
	//生成噪聲
	return octaveNoise;
}


運行後得到打印信息如下:



這個信息表示頻率越高的噪聲振幅和波長越小,而波長的減少則會使繪製同一時間週期的噪聲曲線需要更多的的噪聲數據,故 generateOctaveNoise1D 函數將添加一個新的參數爲 baseWidth ,用來表示原始噪聲的數據長度,倍頻疊加的同時,對應噪聲曲線的數據長度也將會翻倍累加,baseWidth記得要傳值:

function generateOctaveNoise1D(octave:int, baseWidth:int):Array
{
	//...
	//由於頻率的變化,故噪聲數據長度也會倍增
	var width:int = baseWidth * frequency;
	trace("frequency:", frequency, ", amplitude:", amplitude, ", width:", width);
	//...
}

然後繼續填充 generateOctaveNoise1D 函數:

//...
//生成噪聲
for (var i:int = 0; i < width; i ++ )
{
	//嘗試將噪聲值設定在-1到1之間,這樣它們的縱座標會在一條線上對齊
	//這裏不要忘記振幅的變化係數 amplitude
	octaveNoise[i] = (Random.random() - 0.5) * 2 * amplitude;
}
//使用不同的顏色繪製噪聲曲線
this.drawLine(octaveNoise, 0x013579 * octave, 256, 100 * amplitude);
//...

順便再在 drawLine 函數中增加幾行代碼來繪畫時間軸:

//...
//繪畫時間軸this.graphics.lineStyle(1, color);this.graphics.moveTo(offsetx, offsety);this.graphics.lineTo(offsetx + noise.length * wavelength, offsety);//繪畫噪聲曲線
//...

註釋掉剛纔白噪聲的曲線,然後再運行,效果如下:

打印下 drawLine中的參數信息,並與之前的打印信息進行對比:


從打印信息可以看出,所有曲線指定的振幅均爲 256,而從時間軸上的曲線表現也可以分析出,由於倍頻的影響,所以倍頻越高的噪聲,實際振幅越小;並且同一時間週期內,倍頻越高的噪聲波長越小。

所以在這裏,應該說實際應用當中,wavelength值的設定可能需要關心一下,我的運氣很好,因爲100的波長在倍頻疊加5次之後,剛好衰減爲3,如果再疊加一次,波長就會衰減爲1.5,出現小數了,其實可以看下將波長改小一點,看看會有什麼後果,修改下 generateOctaveNoise1D 中的代碼,同時改一下基礎振幅和基礎波長:

//...
//使用不同的顏色繪製噪聲曲線,修改基礎振幅爲32,基礎波長爲20
this.drawLine(octaveNoise, 0x013579 * octave, 32, 20 * amplitude);
//...

看下結果:


居然沒事,只是變成了迷你版,那就沒什麼好擔心的了,現在我們只需要將所有不同倍頻的噪聲曲線合併,爲了更加直觀,合併並記得還原基礎波長和振幅,合併的方法其實和畫曲線的方法是一致的,對曲線求導計算出每個單元座標點上的噪聲值,然後進行累加。

爲了方便起見,我還是直接在 drawLine 中實現了,23333333,我是不是很機智?(。•ˇ‸ˇ•。),定義一個全局變量來存儲分形噪聲,再把基礎波長和振幅都改爲用全局變量存儲,方便調用:

//分形噪聲
var fractalNoise:Array = [];
var amplitude:int = 256;
var wavelength:int = 100;
for (var i:int = 0; i < width * this.wavelength; i ++ )
{
	this.fractalNoise[i] = 0;
}

然後修改 drawLine 中的代碼:

//...
//第一個噪聲數據作爲曲線原點
this.fractalNoise[i * wavelength + j] += y * amplitude;
//...

最後繪製分形噪聲,別問我爲什麼波長和振幅都要傳1,好像是因爲我直接在 drawLine累加曲線數據導致的吧 O__O"…

//生成不同倍頻的噪聲
//...
//繪製分形噪聲
this.drawLine(fractalNoise, 0, 1, 1);


咱還可以將分形噪聲曲線歸一,然後對比兩條線曲:

//...
//繪製分形噪聲
//...
//歸一化
for (var j:int = 0; j < width * this.wavelength; j ++ )
{
	this.fractalNoise[j] /= 6;
}
//繪製曲線
this.drawLine(fractalNoise, 0, 1, 1);

效果圖:


不預評價,至於修改三個全局變量(persistence, amplitude, wavelength)帶來的不同效果,我就不上圖了,反正你們自己修改運行下就能看到了。

最後忘了一個平滑噪聲函數,在這裏補充下吧,直接上代碼:

//一維噪聲平滑函數
function smoothNoise1D(baseNoise:Array, x:Number):Number
{
	return noise1D(baseNoise, x) * 0.5 + noise1D(baseNoise, x - 1)  * 0.25 + noise1D(baseNoise, x + 1) * 0.25;
}

因爲平滑可能會取到負座標,所以需要修改下 noise1D 函數

function noise1D(baseNoise:Array, x:int):Number
{
	while (x < 0)
	{
		x += baseNoise.length;
	}
	//...
}

然後修改繪製函數,缺失的代碼請自己腦補:

//這裏補充一下剛剛展示合併的曲線和歸一化曲線時屏蔽倍頻噪聲曲線的參數,倍頻曲線代碼中調用 drawLine 時設置爲 false 則不顯示
//添加參數 smooth,因爲最終繪製曲線時不需要平滑,只有倍頻噪聲生成時才需要進行平滑
function drawLine(noise:Array, color:uint, amplitude:int, wavelength:int, drawLine:Boolean = true, smooth:Boolean = true):void
{
	//...
	for (var i:int = 0; i < noise.length; i ++ )
	{
		var a:Number = smooth ? this.smoothNoise1D(noise, i) : this.noise1D(noise, i);
		var b:Number = smooth ? this.smoothNoise1D(noise, i + 1) : this.noise1D(noise, i);
		for (var j:int = 0; j < wavelength; j ++)
		{
			//...
			this.fractalNoise[i * wavelength + j] += y * amplitude;
			if (drawLine == false)
			{
				continue;
			}
			//...
		}
	}
}

最後,不太清楚怎麼上傳源碼,所以抱歉啦,這裏丟個百度網盤的傳送門吧,若有錯誤,歡迎指正。

http://pan.baidu.com/s/1min3hBa



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章