在〈Worley 雜訊(一)〉中看到的雜訊生成方式,類似 Steven Worley 在 1996 年撰寫的〈A Cellular Texture Basis Function〉提到的方式,因而稱為 Worley 雜訊。
問題在於,對於每個像素,都要計算與每個點的距離,計算上會花很長的時間,若能事先排除太遠處的點,就可以省下不少計算的時間,只不過點又要有隨機性,如何能事先排除太遠處的點?
如果你想產生的 Voronoi 圖形,每個細胞的大小不至於相差太大的話,可以將畫布分割為網格,然後在每個網格中隨機散佈一個點。
程式實作上並不難,決定好網格寬度,可以基於二維陣列索引來產生這些點:
function cellPoints(cellWidth, seed) {
randomSeed(seed);
const rows = floor(height / cellWidth);
const columns = floor(width / cellWidth);
const points = [];
for(let r = 0; r < rows; r++) {
points.push([]);
for(let c = 0; c < columns; c++) {
points[r][c] = createVector(random(cellWidth) + c * cellWidth, random(cellWidth) + r * cellWidth);
}
}
return points;
}
就某個像素來說,跟它有可能是最近距離的點,就是其所處的網格九宮格內,因此只要針對九個點計算就可以了,因為〈Worley 雜訊(一)〉中已經實作過 worleyNoise
,這邊實作時就只需要收集這九個點,然後呼叫 worleyNoise
:
class Worley {
constructor(cellWidth, seed = random(255)) {
this.cellWidth = cellWidth;
this.seed = seed;
this.cellPoints = cellPoints(cellWidth, seed);
}
noise(x, y) {
const xi = floor(x / this.cellWidth);
const yi = floor(y / this.cellWidth);
const neighbors = [
[-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);
return worleyNoise(neighbors, x, y);
}
}
...略
來看看 100 個點的效果:
如果想產生〈Worley 雜訊(一)〉中最後的 Voronoi 色塊範例,只要重構一下,增加個 nearest
方法:
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) {
return worleyNoise(this.neighbors(x, y), x, y);
}
nearest(x, y) {
return nearest(this.neighbors(x, y), x, y);
}
}
... 略
function nearest(points, x, y) {
let mDist = Infinity;
let mPoint;
for(let i = 0; i < points.length; i++) {
const dist = p5.Vector.sub(points[i], createVector(x, y)).mag();
if(dist < mDist) {
mDist = dist;
mPoint = points[i];
}
}
return mPoint;
}
來看看 Voronoi 色塊的範例:
就目前來說,是找出像素與最接近點的距離並作為雜訊傳回,若尋找的是其他的值呢?這可以形成有趣的圖案,或者作為邊緣尋找的依據,這在下一篇文件再來討論吧!
當你的點散佈並不是以網格的方式畫分時,還是可以使用 worleyNoise
函式,例如,若你的點是在黃金螺線上的話,透過 worleyNoise
計算雜訊來畫圖,就能夠產生以下的圖形:
哇喔!Voronoi 與黃金螺線的神秘關係!你可以試著自己寫程式產生以上的圖案看看,若是多條黃金螺線的話就會更有趣,我曾經用相同的概念建立 3D 模型 VORONOI & FIBONACCI 2: