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、談談噪聲

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