Perlin 雜訊

March 28, 2022

如果想產生一條隨機的曲線,最基本的想法是利用隨機函式,在每個 x 處產生一個隨機的 y 值吧!

rands 與 rand 函式

不過,這樣的線連接起來後,比較像個折線,不連續而且無規律。

use <polyline_join.scad>

width = 10;
height = 5;
step = 0.1;

ys = rands(0, height, width / step);
points = [for(i = [0:len(ys) - 1]) [i * step, ys[i]]];

polyline_join(points)
    circle(.05);

rands 是 OpenSCAD 原生提供的函式,可以指定隨機範圍以及要產生的隨機個數,傳回的 list 包含了隨機數,如果你覺得它使用上不夠直覺的話,可以使用 dotSCAD 的 rand 函式:

use <polyline_join.scad>
use <util/rand.scad>

width = 10;
height = 5;
step = 0.1;

points = [for(i = [0:width / step]) [i * step, rand(0, height)]];

polyline_join(points)
    circle(.05);

兩者都是畫出類似以下的圖案:

Perlin 雜訊

一維 Perlin 雜訊

randsrand 函式產生的值不會是連續的,想想看,自然界有許多看似隨機,然而卻又連續的現象,例如岳崚起伏,看似不規則,然而高低之間又有一定的連續性,如果想要在程式中模擬這種隨機又連續的現象,可以考慮 Perlin 雜訊

來看看一維 Perlin 雜訊的基本原理,你可以在每個整數 x 點產生一個隨機值,然而這個隨機值並不作為 y 值,而是作為穿越該點的一條線之斜率,而該線是曲線在該點的切線:

Perlin 雜訊

曲線穿過每個整數 x 點,因此該點 y 值是 0,兩個 x 點間的 y 值,透過 Ken Perlin 設計的內插函式計算而得,如果使用 dotSCAD,知道這些就夠了,因為 dotSCAD 提供實現了 Perlin 雜訊的相關函式,若對如何插值有興趣,可以參考〈Simplex noise demystified〉,上圖其實也是從該文件中取得,在〈NumPy 與 Perlin 雜訊〉,也有談到如何實現。

dotSCAD 的 nz_perlin1 函式,可以指定 x 座標與亂數種子,得到對應 x 的 Perlin 雜訊 y 值,y 值會是 -1 到 1,例如:

use <polyline_join.scad>
use <util/rand.scad>
use <noise/nz_perlin1.scad>

seed = rand(0, 255);

width = 10;
height = 5;
step = 0.1;

points = [
    for(i = [0:width / step]) 
    let(x = i * step)
    [x, height * nz_perlin1(x, seed)]
];

polyline_join(points)
    circle(.05);

這會產生以下的曲線:

Perlin 雜訊

可以看到曲線比較連續了,step 越小就會越連續,記得 step 不能是整數,因為方才的 Perlin 雜訊原理談到,x 座標為整數處的 y 值是 0,如果你的 step 是整數,最後只會得到一條 y = 0 的線。

如果你已經有一組 x 值,也可以使用 nz_perlin1s 來產生一組對應的 y 值,例如方才的範例,也可以改寫為以下:

use <polyline_join.scad>
use <noise/nz_perlin1s.scad>
use <util/zip.scad>

width = 10;
height = 5;
step = 0.1;

xs = [for(i = [0:width / step]) i * step];
ys = nz_perlin1s(xs) * height;

polyline_join(zip([xs, ys]))
    circle(.05);

dotSCAD 的 zip 函式,可以將指定的 list,按各索引逐一配對,就上例而言,就是將 xsys 組成 [x, y] 的 list 傳回。

二維 Perlin 雜訊

一維的 Perlin 雜訊通常用來建立隨機的連續曲線,Perlin 雜訊可以擴展為二維,給定 x 與 y,可以產生 z 雜訊值,可作為高度、灰階度或彩度,例如作為高度的話,可以用來模擬地形,可以使用 dotSCAD 的 nz_perlin2 可以接受 x、y 與亂數種子,得到 -1 到 1 的對應 z 值(如果你有一組二維的座標點,也可以使用 nz_perlin2s):

use <util/rand.scad>
use <noise/nz_perlin2.scad>
use <surface/sf_thicken.scad>

seed = rand(0, 255);

x_length = 5;
y_length = 5;
height = 1.25;
step = 0.05;

points = [
    for(j = [0:y_length / step]) 
    let(y = j * step)
    [
        for(i = [0:x_length / step])
        let(x = i * step)
        [x, y, height * nz_perlin2(x, y, seed)]
    ]
];

sf_thicken(points, .2);

這會產生以下的模型:

Perlin 雜訊

三維 Perlin 雜訊

Perlin 雜訊可以擴展至三維,對於三維的 Perlin 雜訊,可以指定 x、y、z 得到雜訊值,那麼這雜訊可以什麼用呢?那就要問你了,你為什麼需要隨機但又連續的雜訊值?也許是透明度?

use <util/rand.scad>
use <noise/nz_perlin3.scad>

seed = rand(0, 255);
range = [0:.2:5];
noised = [
    for(z = range, y = range, x = range)
        [x, y, z, nz_perlin3(x, y, z, seed)]
];    

for(nz = noised) {
    color("Gray", alpha = (nz[3] + 1) / 4)
    translate([nz.x, nz.y, nz.z])
        cube(.2);
}

OpenSCAD 的 color 模組可以著色與設置透明度,這會得到以下的結果:

Perlin 雜訊

OpenSCAD 的 color 模組就只是單純的著色,顏色不會真的成為渲染後模型的一部份,那麼來將雜訊看成是密度之類的,只有在密度大於某個值時,才放上方塊呢?如果你有玩過 Minecraft 之類的遊戲,有沒有想過當中的洞穴是怎麼生成的呢?原理就類似以下的程式碼:

use <util/rand.scad>
use <noise/nz_perlin3.scad>

seed = rand(0, 255);
range = [0:.2:5];
noised = [
    for(z = range, y = range, x = range)
        [x, y, z, nz_perlin3(x, y, z, seed)]
];    

for(nz = noised) {
    if(nz[3] > 0.2) {
        translate([nz[0], nz[1], nz[2]])
            cube(.2);
    }
}

這會產生以下的圖案:

Perlin 雜訊

還能有什麼應用呢?如果對於球面上的每個點取雜訊值,換算為點往內或往外的輻射距離,就可以做成一個泥巴球之類的模型,這就留給你來挑戰看看了:

Perlin 雜訊

分享到 LinkedIn 分享到 Facebook 分享到 Twitter