通过前面几篇文章,我们已经成功实现了1D,2D,3D,4D Perlin 噪声,Perlin噪声实现不是很复杂,但是我们也应该注意到,随着维度的增多,实现的复杂度也在大幅度增加,包括permutation表的检索和插值都会变得越来越难以控制,而且性能瓶颈表现得也是越来越明显。当然,我们平时很少用到超过4D以上的Perlin噪声,事情还不没有想象的那么困难。在Ken Perlin的论文中,他实现了几种对噪声变形应用,我们一并学习之。
在分形叠加的过程中,如果我们不是直接取噪声值,而是取噪声的绝对值,这时就会因为在0值变化处出现不连续性,形成一些尖锐细纹状条纹,在图像上表现出来就像毛细血管一样。脉流的实现就是对各Octive取绝对值,生成的图形如下。
方向脉流是利用下面公式对脉流进行一个方向应用。
在利用噪声生成纹理时,我们往往希望生成的纹理是无缝的(tileable,Seamless),这样我们就可以生成连续的地形、云朵,而不会在边界处出现跳跃,在大范围应用时,这种无缝纹理会非常有用。
在生成Perlin噪声时,我们首先初始化了一张permutation表,目的是利用这张表来索引梯度值,虽然经过多次计算,但对于给点的输入,会产生一个固定的输出,又因为这张表里只有256个元素,所以当输入值大于256时,肯定会出现重复,这也是Perlin噪声生成时的内在周期性,为什么平时我们看起来生成的纹理没有重复,一方面是因为我们频率不够高,另一方面是因为Perlin噪声生成时不仅能处理整数,也能处理小数,这大大平抑了短周期带来的影响。但如果我们把频率调高,还是能看出来重复,下面是我输入了0到1000,我们可以看到周期出现了。
if (TileRepeat > 0)
{
x = x % TileRepeat;
y = y % TileRepeat;
}
----------------------------------------
public int Increase(int num)
{
num++;
if (TileRepeat > 0) num %= TileRepeat;
return num;
}
通过对输入值和索引值增量的处理,我们就可以利用TileRepeat来控制周期了,期生成的图像如下:
另一种生成无缝纹理的方法是采取高维生成低维的办法,先来看张图:
我们以X轴可平铺的无疑纹理生成为例,如下图所示:
float x1 = 20, x2 = 10f;
float y1 = 20, y2 = 10f;
float dx = x2 - x1;
float dy = y2 - y1;
double s = x / (float)Width;
double t = y / (float)Height ;
float nx = (float)(x1 + Math.Cos(s * 2 * Math.PI) * dx / (2 * Math.PI));
float ny = (float)(x1 + Math.Sin(s * 2 * Math.PI) * dx / (2 * Math.PI));
float nz = (float)t * 16;
return (float)p3D.Perlin(nx, ny, nz);
我们解释一下代码,x1,y1定义了采样圆的圆心坐标, dx / (2 * Math.PI) 定义了采样圆的半径,s,t定义的是采样步进值,Width,Height 为画布宽和高,s * 2 * Math.PI保证了可以精确得到一个完整的周期,nx,ny就是采样圆的x,y坐标值,正弦、余弦值的周期性也保证了纹理的周期性。需要说明的是在计算nx,ny值的时候,x1,y1,x2,y2的值的选取是可以任意的,但需要保证最后得到nx,ny值为正值(负值晶格坐标没有定义),t * 16是为了使最后生成的图像在y轴上不被拉伸变形。计算后的nx,ny,nz就是3D Perlin噪声的坐标,通过上面的代码可以生成在x轴上可平铺的无缝纹理。
实现双轴可平铺的2D纹理需要用到4D噪声,这比x轴可平铺在概念上来说更难理解一些,更由于高阶空间无法可视化,但只要明白,用两个正交的圆柱在4D空间中去采样得到的就是2D无缝纹理,圆柱的周期性和两个圆柱的正交性保证了得到的x,y值也是正交的。理解起来很难,但在代码上来看却几乎是一样的。
float x1 = 1f, x2 = 7f;
float y1 = 1f, y2 = 7f;
float dx = x2 - x1;
float dy = y2 - y1;
double s = x / (float)Width;
double t = y / (float)Height;
float f = 0.511165f ;
float nx = (float)(x1 + Math.Cos(s * 2 * Math.PI) * dx / (2 * Math.PI)) * f;
float ny = (float)(y1 + Math.Cos(t * 2 * Math.PI) * dy / (2 * Math.PI)) * f;
float nz = (float)(x1 + Math.Sin(s * 2 * Math.PI) * dx / (2 * Math.PI)) * f;
float nw = (float)(y1 + Math.Sin(t * 2 * Math.PI) * dy / (2 * Math.PI)) * f;
return (float)p4D.Perlin(nx, ny, nz, nw);
与x轴可平铺一样,x1,y1定义了采样圆的圆心坐标, dx / (2 * Math.PI) 定义了采样圆的半径,s,t定义的是采样步进值,Width,Height 为画布宽和高,s * 2 * Math.PI保证了可以精确得到一个完整的周期,nx,ny就是采样圆的x,y坐标值,正弦、余弦值的周期性也保证了纹理的周期性,f 是一个调节因子,可以调整噪声产生的频率。需要说明的是在计算nx,ny值的时候,x1,y1,x2,y2的值的选取是可以任意的,但需要保证最后得到nx,ny值为正值(负值晶格坐标没有定义),上面的代码其实是在nx,nz和ny,nw面上分别定义了一个圆柱。计算后的nx,ny,nz,nw就是4D Perlin噪声的坐标,通过上面的代码可以生成在x轴、y轴上均可平铺的无缝纹理。
在本节中,我们一是实现了Perlin 脉流,二是通过两种方面实现了无缝的可平铺2D纹理,第一种方法主要是利用 了Perlin噪声生成时本身的周期性,第二种方法则进行了高阶采样生成低阶无缝纹理。相比较,第一种方法的实时性能更好,但需要计算好生成的纹理的宽高,第二种方法可以精确生成指定高宽的无缝纹理,但第二种方法因为要进行高阶采样,所以效率上会有所损失。本节我们只关注了2D无缝纹理,对于更高的3D无缝纹理处理方式是一样的,但3D无缝纹理操作上还是要复杂得多,特别是如果选用第二种方法的话需要先生成6D噪声。注:本文采用的第一种方式,有人说会影响Octives时的使用,这个读者可以验证,在使用第二种方法生成2D xy双轴可平铺无缝纹理时,我们发现,输入的nx,ny,nz,nw值域必须要在一个晶格内,这也是我们加了f这个频率调节因子的最初原因,初步分析,最大的可能是我们在Perlin4D实现时梯度值与距离值的点乘操作不匹配,稍晚点我们会认真思考这个问题。
Perlin噪声脉流,这是基于Unity2017.1.1f1_Cg实现的。
无缝2DPerlin噪声,基于VS2015_C#实现的。
1、http://wiki.unity3d.com/index.php/Tileable_Noise
2、http://ronvalstar.nl/creating-tileable-noise-maps
3、http://www.jgallant.com/procedurally-generating-wrapping-world-maps-in-unity-csharp-part-2/#wrap2
4、谈谈噪声