柏林噪聲分形&幻想大陸地圖生成

2023-06-02 18:00:50

序言

之前介紹過perlin噪聲的實現,現在應用實踐一下——程式化生成幻想大陸
這裡使用的是perlin噪聲倍頻技術(也稱分形噪聲),詳情傳送門:柏林噪聲演演算法
程式碼範例使用的是shadertoy的語法規則,shandertoy傳送門:ShaderToy

範例

#define amp 1.9
#define fre 1.
#define oct 5.

#define laun 2.
#define pers 0.8

#define zoom 5.

#define edge 1.0
#define delta_edge .2

#define snow        vec3(.9, .9, .9)
#define mountains   vec3(.4, .4, .2)
#define hills       vec3(.6, .6, .1)
#define plain       vec3(.1, .8, .2)
#define beach       vec3(.8, .8, .1)
#define shallow_sea vec3(.1, .1, .9)
#define deep_sea    vec3(.1, .1, .6)

#define v_snow =       .95
#define v_mountains    .90
#define v_hills        .80
#define v_plain        .70
#define v_beach        .55
#define v_shallow_sea  .50
#define v_deep_sea     .30

float rand(vec2 p){
    return fract(sin(dot(p ,vec2(12.9898,78.233))) * 43758.5453);
}

float noise(vec2 x)
{
    vec2 i = floor(x);
    vec2 f = fract(x);

    float a = rand(i);
    float b = rand(i + vec2(1.0, 0.0));
    float c = rand(i + vec2(0.0, 1.0));
    float d = rand(i + vec2(1.0, 1.0));
    vec2 u = f * f * f * (f * (f * 6. - 15.) + 10.);

    float x1 = mix(a,b,u.x);
    float x2 = mix(c,d,u.x);
    return mix(x1,x2,u.y);
}


void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	vec2 uv = (fragCoord.xy-0.5 * iResolution.xy) / iResolution.y; 
    vec2 u = fragCoord.xy / iResolution.xy;
     float d = min(min(u.x, edge - u.x), min(u.y, edge - u.y));

    float dw = smoothstep(0.0, delta_edge, d);
    
    float val = .0;
    uv *= zoom;
    

    for(float i = 0.; i < oct; i++)
    {
        float a = amp * pow(pers, i);
        float f = fre * pow(laun, i);
        val += a * noise(uv * f) / oct;
    }
    
    val *= dw;
    
    vec3 col = vec3(0.);
    if (val < v_deep_sea)
        col = deep_sea;
        
    if (val >= v_deep_sea && val < v_shallow_sea)
        col = shallow_sea;
        
    if (val >= v_shallow_sea && val < v_beach)
        col = beach;
        
    if (val >= v_beach && val < v_plain)
        col = plain;
        
    if (val >= v_plain && val < v_hills )
        col = hills ;
        
    if (val >= v_hills && val < v_mountains)
        col = mountains;
        
    if (val >= v_mountains)
        col = snow;    
    

    fragColor = vec4(col, 0.);
}

思路

生成地形輪廓

地形輪廓的生成主要依靠噪聲,來看倍頻相關程式碼(for迭代那部分)相關引數
主要引數

  • frequency 頻率
  • amplitude 振幅
  • octave 八度,即迭代次數

相信相關三角函數都學過,就不贅述了
輔助引數

  • lacunarity 隙度,修飾頻率,使得頻率隨每個八度以指數增長
  • persistent 持久度,與隙度類似

使用上述程式碼的引數,隨著迭代,每次迭代疊加的細節越來越多(頻率更高),但影響越來越小(振幅更小),具象一點的比喻就像:第一次迭代產生山峰的輪廓,第二次迭代產生山峰上巨石的輪廓,第三次迭代產生小石頭等的輪廓...

雕刻大陸

經過第一步我們的每一個uv都可以得到一個噪聲值,因為噪聲值是連續的,可以定義連續的區間為某個地形,這樣產生的地形也一定是連續的。比如我把[-∞,0.5)區間定義為海洋,[0.5, 0.55)定義為沙灘等,如程式碼那一堆地形相關的define。接下來就是不斷調整引數,使其引數在合理的區間變化(合理是指生成的大陸符合你的邏輯或審美),由於引數較多且關聯,雖然在一定區間內有些規律可循,還是有點難以預料,我稱之為——賽博煉丹。

大陸邊緣處理

我們生成的是一片完整的大陸,邊緣當然得是海!讓生成的噪聲乘以一個權重,改該權重在圖片邊緣部分的一個區間內遞減,這裡是delta_edge = 0.2的邊緣區間,如上述程式碼d和dw的計算。

附錄

簡化版perlin噪聲

float rand(vec2 p){
    return fract(sin(dot(p ,vec2(12.9898,78.233))) * 43758.5453);
}

float noise(vec2 x)
{
    vec2 i = floor(x);
    vec2 f = fract(x);

    float a = rand(i);
    float b = rand(i + vec2(1.0, 0.0));
    float c = rand(i + vec2(0.0, 1.0));
    float d = rand(i + vec2(1.0, 1.0));
    vec2 u = f * f * f * (f * (f * 6. - 15.) + 10.);

    float x1 = mix(a,b,u.x);
    float x2 = mix(c,d,u.x);
    return mix(x1,x2,u.y);
}


void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	vec2 uv = (fragCoord.xy-0.5 * iResolution.xy) / iResolution.y; 
    uv *= 4.;
    float val = noise(uv.xy) ;
    fragColor = vec4(val);
}

分形噪聲

#define amp 1.9
#define fre 1.
#define oct 5.

#define laun 2.
#define pers 0.8

#define zoom 5.

float rand(vec2 p){
    return fract(sin(dot(p ,vec2(12.9898,78.233))) * 43758.5453);
}

float noise(vec2 x)
{
    vec2 i = floor(x);
    vec2 f = fract(x);

    float a = rand(i);
    float b = rand(i + vec2(1.0, 0.0));
    float c = rand(i + vec2(0.0, 1.0));
    float d = rand(i + vec2(1.0, 1.0));
    vec2 u = f * f * f * (f * (f * 6. - 15.) + 10.);

    float x1 = mix(a,b,u.x);
    float x2 = mix(c,d,u.x);
    return mix(x1,x2,u.y);
}


void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	vec2 uv = (fragCoord.xy-0.5 * iResolution.xy) / iResolution.y; 

    
    float val = .0;
    uv *= zoom;
    

    for(float i = 0.; i < oct; i++)
    {
        float a = amp * pow(pers, i);
        float f = fre * pow(laun, i);
        val += a * noise(uv * f) / oct;
    }
    
    vec3 col = vec3(val);

    fragColor = vec4(col, 0.);
}