Perlin Noise 可以用來表現自然界中無法用簡單形狀來表達的物體的形態,比如火焰、煙霧、表面紋路等。要生成 Perlin Noise 可以使用工具離線生成,也可以使用代碼運行時生成。最簡單常用的離線生成工具就是 Photoshop 了,新建畫布,然后直接選擇雲彩濾鏡即可。而這里要介紹的是使用代碼生成 Perlin Noise,使用代碼運行時生成有幾點好處。首先就是 Perlin Noise 紋理不會占用空間,如果大量使用到了 Perlin Noise Texture,這就不失為一個減少包大小的好方法,比如說地形的高度圖是通過 Perlin Noise 生成的,同時又有很多張地圖。還有個好處就是,運行時生成的是可以根據需要產生不同的變化的,而工具離線生成的就做不到。
以下提到的生成方法以及代碼都是來自於http://devmag.org.za/2009/04/25/perlin-noise/,在此基礎上加上我加入了自己的理解。
下面就來介紹下使用代碼生成 Perlin Noise 的幾個步驟:
生成一張隨機雜點的紋理
隨機雜點紋理是生成 Perlin Noise 的基礎,以后所有要用到的數據都是從這張紋理衍生出來的。
private float[,] GenerateWhiteNoise(int width, int height)
{
// 這里的隨機數種子很重要
// 如果想要每次生成的 Perlin Noise 都一樣,只要設置一個相同的值即可
// 這里使用隨機種子,所有每次都會生成不同的 Perlin Noise
Random.seed = Random.Range(int.MinValue, int.MaxValue);
float[,] noise = new float[width, height];
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
noise[i, j] = Random.value;
}
}
return noise;
}
使用不同的采樣率對隨機雜點紋理進行采樣
上圖中每一小塊是使用不同的采樣率對隨機雜點紋理進行采樣的結果。采樣率越小,獲得的細節越少,采樣率越大,獲得的細節越多。左上角的采樣率是最小的,所以細節越少。右下角的采樣率最大,所以細節越多。我們將使用這幾張圖來合成最終的 Perlin Noise。在合成過程中,每一張圖的合成比重是不一樣的,細節越少的占用更多的合成比重,細節越多的占用更少的合成比重。以上圖來舉例,也就是說,左上角的那張采樣紋理決定了 Perlin Noise 最終的大致形狀,以后的所有紋理逐一疊加來提供細節,但是比重越來越小。
// 生成指定采樣級別的 Noise
private float[,] GenerateSmoothNoise(float[,] whiteNoise, int octave, int width, int height)
{
float[,] smoothNoise = new float[width, height];
// 采樣步長
int samplePeriod = (int)Mathf.Pow(2, octave);
// 采樣頻率
float sampleFrequency = 1.0f / samplePeriod;
for (int i = 0; i < width; i++)
{
// 最左點位置
int sampler_l = (i / samplePeriod) * samplePeriod;
// 最右點位置
int sampler_r = (sampler_l + samplePeriod) % width;
// 根據實際點與最左最右的距離,計算水平混合系數
float horizontal_blend = (i - sampler_l) * sampleFrequency;
for (int j = 0; j < height; j++)
{
// 最上點位置
int sampler_t = (j / samplePeriod) * samplePeriod;
// 最下點位置
int sampler_d = (sampler_t + samplePeriod) % height;
// 根據實際點與最上最下的距離,計算垂直混合系數
float vertical_blend = (j - sampler_t) * sampleFrequency;
// 左上和右上根據水平混合系數進行插值
float top = Interpolate(whiteNoise[sampler_l, sampler_t], whiteNoise[sampler_r, sampler_t], horizontal_blend);
// 左下和右下根據水平混合系數進行插值
float bottom = Interpolate(whiteNoise[sampler_l, sampler_d], whiteNoise[sampler_r, sampler_d], horizontal_blend);
// 最總數值為 top down 根據垂直混合系數進行插值
smoothNoise[i, j] = Interpolate(top, bottom, vertical_blend);
}
}
return smoothNoise;
}
// 在兩個數值間進行插值
private float Interpolate(float x0, float x1, float alpha)
{
return x0 * (1 - alpha) + alpha * x1;
}
合成最終的 Perlin Noise
使用面生成的所有采樣紋理來合成最終的 Perlin Noise。
public float[,] GeneratePerlinNoise(float[,] whiteNoise, int octaveCount, int width, int height)
{
// 不同采樣級別的 Noise
List<float[,]> smoothNoise = new List<float[,]>();
// 不同采樣級別的 Noise 的疊加系數
float persistance = 0.8f;
// 生成不同采樣級別的 Noise
for (int i = 0; i < octaveCount; i++)
{
smoothNoise.Add(GenerateSmoothNoise(whiteNoise, i, width, height));
}
// 最終生成的 Perlin Noise
float[,] perlinNoise = new float[width, height];
// 不同采樣級別的 Noise 的疊加比重
float amplitude = 1.0f;
// 所有采樣級別的 Noise 的疊加總比重
float totalAmplitude = 0.0f;
// 開始混合所有采樣級別的 Noise
for (int octave = octaveCount - 1; octave >= 0; octave--)
{
amplitude *= persistance;
totalAmplitude += amplitude;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
perlinNoise[i,j] += smoothNoise[octave][i,j] * amplitude;
}
}
}
// 歸一化最終的 Perlin Noise
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
perlinNoise[i,j] /= totalAmplitude;
}
}
return perlinNoise;
}
這里有一點是需要注意的,我們很多時候想要生成的 Perlin Noise 需要是一張循環紋理(可以平鋪在表面上,在銜接處不會產生不自然的效果)。原始資料中指出,關鍵點就在 Mathf.Pow(2, octave)
這里,以及提供的 width
和 height
需要是2的冪次方。其實也可以是 Mathf.Pow(3, octave)
,width
和 height
是3的冪次方(一般都使用2的冪次方)。其原因就在生成采樣紋理時,進行插值的部分。如果每一行(列)的最后一步插值可以正好是下一個 Tile 的第一個點,那么就會生成一個循環的 Perlin Noise。
應用:殘蝕效果
_ | _ |
---|---|
![]() |
![]() |
不同的 Perlin Noise 會產生不同的殘蝕效果,我們可以多生成幾張 Perlin Noise 紋理,經過對比來達到想要的效果。也可以把幾張 Perlin Noise 紋理進行疊加,產生更豐富的效果。一般來說,直接將 Perlin Noise 紋理疊加到 Diffuse 紋理上是不會產生這么銳利的邊緣過度的,所以需要對 Perlin Noise 漸變過度進行放大處理,這個放大操作可以預處理在 Texture 上,也可以在 Shader 中。
應用:動態體積霧
原始FogVolume | Perlin Noise FogVolume | Perlin Noise FogVolume GIF |
---|---|---|
![]() |
![]() |
![]() |
體積霧的實現原理可以看這里,效果就是左圖。可以看到霧並沒有層次,也無法表現出在空中飄動的效果。使用 Perlin Noise 疊加后,霧就有了層次感(中間圖),對 Perlin Noise Texture 進行 uv 動畫后,霧也產生了飄動的效果(右圖),並且由於我們的 Perlin Noise 是可平鋪的,所以 uv 動畫在邊緣不會產生突變。