分形噪声

原本想研究柏林噪声,结果发现自己想研究的原来是分形噪声,这就尴尬了,我想说其实百度到的大多数柏林噪声的资料其实都是分形噪声,除了这位同学:

[图形学]-谈谈噪声: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



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