到目前為止,傳回的雜訊值都是離最接近點的距離,其實也可以取不同的距離傳回,例如,第二接近點、第三接近點等,若用這些雜訊值來作為灰階值,會構成不同的圖案。
Steven Worley 的〈A Cellular Texture Basis Function〉就提到,第二接近距離減去第一接近距離,也就是被稱為 F2 - F1 的方式,圖案會有突起的效果,例如:
其他的距離值也可以嘗試看看,例如,若計算距離時,不是採取直線距離呢?像是曼哈頓距離?
function manhattan(x1, y1, x2, y2) {
return abs(x1 - x2) + abs(y1 - y2);
}
為了可以指定距離的計算方式,來修改一下 worleyNoise
可以接受距離計算函式:
function worleyNoise(points, x, y, distance = dist) {
let mDist = Infinity;
for(let i = 0; i < points.length; i++) {
mDist = min(mDist, distance(points[i].x, points[i].y, x, y));
}
return mDist;
}
然後 Worley
的 noise
方法也可以接受距離函式:
class Worley {
constructor(cellWidth, seed = random(255)) {
this.cellWidth = cellWidth;
this.seed = seed;
this.cellPoints = cellPoints(cellWidth, seed);
}
neighbors(x, y) {
const xi = floor(x / this.cellWidth);
const yi = floor(y / this.cellWidth);
return [
[-1, -1], [0, -1], [1, -1],
[-1, 0], [0, 0], [1, 0],
[-1, 1], [0, 1], [1, 1]
].map(idx => {
const r = this.cellPoints[idx[1] + yi];
return r === undefined ? r : r[idx[0] + xi];
}).filter(p => p);
}
noise(x, y, distance = dist) {
return worleyNoise(this.neighbors(x, y), x, y, distance);
}
nearest(x, y, distance = dist) {
return nearest(this.neighbors(x, y), x, y, distance);
}
}
來看看畫出來的效果:
現在有個問題,有辦法只繪製細胞的邊緣嗎?目前的 worleyNoise
實作行不通,因為它計算最接近點的距離,而這會構成圓的勢力範圍,若你打算基於某個距離閥值來畫邊緣,結果只會有圓的空洞產生:
其實上圖也是一種風格,將閥值條件反過來,就可以構成這種圖案:
如果你依 Steven Worley 的做法,採 F2 - F1 的話,因為圖案會有突起的效果,邊緣會比較明顯,效果會好一些:
之所以會有這樣的結果,原因在於在兩個細胞的邊界處,F2 - F1 一定會是 0,像素越靠近其中一點,F2 - F1 的值會有較大的變化;然而,這並不是很精確的做法,畢竟只是單純地計算距離差,而不是去計算像素與細胞邊界的距離。
只不過,若要更準確地描繪邊界,計算像素與細胞邊界的距離會是必要的,在 2012 年, Inigo Quilez 撰寫的〈voronoi edges〉中提到利用兩個最接近點,計算它們的中點後,取像素與中點的投影,確實地計算出細胞邊界的距離。
為此,可以修改 worleyNoise
如下:
function worleyNoise(points, x, y, option = {distance : dist}) {
const distance = option.distance || dist;
const edge = option.edge;
if(edge) {
const sorted = points.map(p => [p, distance(p.x, p.y, x, y)])
.sort((e1, e2) => e1[1] - e2[1]);
return edge(sorted, x, y);
}
let mDist = Infinity;
for(let i = 0; i < points.length; i++) {
mDist = min(mDist, distance(points[i].x, points[i].y, x, y));
}
return mDist;
}
function border(points, x, y) {
const p1 = points[0][0];
const p2 = points[1][0];
const mp = p5.Vector.add(p1, p2).mult(0.5);
return createVector(x, y).sub(mp).dot(p5.Vector.sub(p1, mp));
}
function f2f1(points, x, y) {
const p1 = points[0][0];
const p2 = points[1][0];
const mp = p5.Vector.add(p1, p2).mult(0.5);
return mag(p2.x - x, p2.y - y) - mag(p1.x - x, p1.y - y);
}
option
的 edge
可以接受函式,該函式的參數可接受排序後的點,表示像素距離的遠近,據以計算邊界距離,上頭提供了兩個實作,border
與 f2f1
,你也可以試著用其他的實作來看看是否會產生什麼特別的圖案。
如果採取 border
的話,會產生的圖案是:
可以跟更上頭的 F2 - F1 圖案比較看看,會發現突起更為明顯,若小於某個閥值才繪製像素,就會產生更精確的邊界:
以下是完整的程式實作: