Perlin Noise变种及无缝纹理生成

一、Perlin Noise变种
   通过前面几篇文章,我们已经成功实现了1D,2D,3D,4D Perlin 噪声,Perlin噪声实现不是很复杂,但是我们也应该注意到,随着维度的增多,实现的复杂度也在大幅度增加,包括permutation表的检索和插值都会变得越来越难以控制,而且性能瓶颈表现得也是越来越明显。当然,我们平时很少用到超过4D以上的Perlin噪声,事情还不没有想象的那么困难。在Ken Perlin的论文中,他实现了几种对噪声变形应用,我们一并学习之。

(一)turbulence脉流
   在分形叠加的过程中,如果我们不是直接取噪声值,而是取噪声的绝对值,这时就会因为在0值变化处出现不连续性,形成一些尖锐细纹状条纹,在图像上表现出来就像毛细血管一样。脉流的实现就是对各Octive取绝对值,生成的图形如下。 

(二)方向脉流(Oriented turbulence)
   方向脉流是利用下面公式对脉流进行一个方向应用。 
sin(x+|noise(p)|+12|noise(2p)|+14|noise(4p)|+...)
sin(x+|noise(p)|+12|noise(2p)|+14|noise(4p)|+...)

sin(y+|noise(p)|+12|noise(2p)|+14|noise(4p)|+...)
sin(y+|noise(p)|+12|noise(2p)|+14|noise(4p)|+...)

方向脉流是在之前turbulence公式的基础上使用了一个关于x分量、y分量的正弦函数,这个公式可以让脉流在表面沿着x方向,y方向形成一个条纹状的结构。其生成的图形如下: 


  通过对x,y系统的控制,我们可以生成不同宽幅的条纹。
二、无缝纹理生成
   在利用噪声生成纹理时,我们往往希望生成的纹理是无缝的(tileable,Seamless),这样我们就可以生成连续的地形、云朵,而不会在边界处出现跳跃,在大范围应用时,这种无缝纹理会非常有用。

(一)、利用Perlin Noise内在周期性生成无缝纹理
   在生成Perlin噪声时,我们首先初始化了一张permutation表,目的是利用这张表来索引梯度值,虽然经过多次计算,但对于给点的输入,会产生一个固定的输出,又因为这张表里只有256个元素,所以当输入值大于256时,肯定会出现重复,这也是Perlin噪声生成时的内在周期性,为什么平时我们看起来生成的纹理没有重复,一方面是因为我们频率不够高,另一方面是因为Perlin噪声生成时不仅能处理整数,也能处理小数,这大大平抑了短周期带来的影响。但如果我们把频率调高,还是能看出来重复,下面是我输入了0到1000,我们可以看到周期出现了。 


   既然Perlin噪声生成是带有内在周期性的,那我们就可以利用他来生成我们想要的无缝纹理,原理就是控制对permutation表取值来现实不同周期的调整,我们利用一个TileRepeat参数对输入的值进行模除,将其值周期化,同时,我们将对permutation表索引加1的操作也调整到TileRepeat周期内(因为涉及到模除,所以TileRepeat只能取正整数)。关键代码如下:
if (TileRepeat > 0)
{
    x = x % TileRepeat;
    y = y % TileRepeat;

----------------------------------------
public int Increase(int num)
{
    num++;
    if (TileRepeat > 0) num %= TileRepeat;
    return num;
}

   通过对输入值和索引值增量的处理,我们就可以利用TileRepeat来控制周期了,期生成的图像如下: 


   至此我们已经能够根据我们的要求生成无缝的纹理,现在生成的纹理会无缝的铺满整个可用的平面,跟画布大小没有关系,这有时不是我们想要的,我们可能就只需要单重复纹理,而自己来处理平铺问题,那这时我们就需要进行周密的计算来确定画布的大小以确保纹理正好满周期的画在画布上。例如:如果我们取TileRepeat=5,Frequency=0.01,如果我们想要获得一个整周期纹理,那么画布Width=500,同理,如果想要获得两个整周期纹理,那么画布Width=1000,以此类推,高度也同样处理。下面是生成的2x2和1x1纹理,这样我们就可以随心所欲的应用生成的纹理了。 

(二)、采用高维纹理生成低维无缝纹理
   另一种生成无缝纹理的方法是采取高维生成低维的办法,先来看张图: 


   在这张2D图中,如果我们沿着红色圆环采样,那么可以得到1D的无缝纹理,因为圆的周期性保证了采样后数据的连续性,同样,对于2D纹理,我们可以使用高阶噪声来围绕其中一个轴或者两个轴来采样。
1、x轴可平铺无缝纹理
   我们以X轴可平铺的无疑纹理生成为例,如下图所示: 


   我们用立方体表示3D Perlin噪声,我们在其内部画一个圆柱体如绿线所示,然后我们用红色框沿箭头向下切开圆柱体的一边,向左右两面展开圆柱体的表面,将得到右侧所示的长方形(即我们需要的2D纹理),可以想象得到,长方形左右两条边是可以沿绿色箭头方向旋转重合到一起(它们都来自前面的切口),由此保证了左右两边是可以无缝对接到一起的。基于这个基础,我们就可以得到在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轴上可平铺的无缝纹理。 

2、xy双轴可平铺无缝纹理
  实现双轴可平铺的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、谈谈噪声

发布了62 篇原创文章 · 获赞 25 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_14914623/article/details/90345392