Worley 雜訊(二)


在〈Worley 雜訊(一)〉中看到的雜訊生成方式,類似 Steven Worley 在 1996 年撰寫的〈A Cellular Texture Basis Function〉提到的方式,因而稱為 Worley 雜訊。

問題在於,對於每個像素,都要計算與每個點的距離,計算上會花很長的時間,若能事先排除太遠處的點,就可以省下不少計算的時間,只不過點又要有隨機性,如何能事先排除太遠處的點?

如果你想產生的 Voronoi 圖形,每個細胞的大小不至於相差太大的話,可以將畫布分割為網格,然後在每個網格中隨機散佈一個點。

Worley 雜訊(二)

程式實作上並不難,決定好網格寬度,可以基於二維陣列索引來產生這些點:

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 計算雜訊來畫圖,就能夠產生以下的圖形:

Worley 雜訊(二)

哇喔!Voronoi 與黃金螺線的神秘關係!你可以試著自己寫程式產生以上的圖案看看,若是多條黃金螺線的話就會更有趣,我曾經用相同的概念建立 3D 模型 VORONOI & FIBONACCI 2

Worley 雜訊(二)