iT邦幫忙

2024 iThome 鐵人賽

DAY 17
0
Modern Web

p5.js 的環形藝術系列 第 17

[Day 17] p5.js 實戰演練(六) –– 煙霧動畫實作(一)

  • 分享至 

  • xImage
  •  

這次的文章是新的創作應用單元,我們要來實作環狀煙霧的效果:

Imgur

主要用的是上一個單元 p5.js 基礎教學(八) –– noise 函數 使用的函數:

  • noise()
  • vertext()

這個環狀煙霧在視覺呈現上和前一個單元的彩帶動畫非常相似,其實各位可以把它理解為,把彩帶環成一圈就可以變成環狀煙霧了,程式上也是這樣實作的。

但彩帶頭尾的接法還是必須好好思考一下,畢竟 noise 函數在頭跟尾的座標值並不是「平滑」相連的平面。

直角座標與極座標

前一個單元的彩帶動畫,我們等於是在畫布上實作了一個 noise 平面函數,y 座標為該平面函數的 noise 函數數值,x 座標和深度值為該 noise 函數的位置座標。

Imgur

我們還拿了 noise 函數的二維灰階圖去做表格比較:

動畫類型 位置座標 noise 數值
彩帶 x 座標和深度值 y 座標
二維路面(二維灰階圖) x, y 座標 灰度值

但這些都是在直角座標上呈現出來的 noise 平面,如果要把整個平面彎起來讓頭尾相連,也就是說讓這個 noise 數值,能夠以環形的效果呈現在畫布上,那我們就必須引入「極座標」的概念。

Imgur

在一個二維空間中標記一個點,我們可以用直角座標或是極座標系統來表示這個點的位置,直角座標是就我們最常見到的 (x, y) 標記形式,極座標的話,我們常用 (r, θ) 這兩個代號做表示,其中:

  • r 為該點到原點 (0, 0) 的距離,我們可以稱之為半徑。
  • θ 為該點與 (0, 0) 相連的直線和正向 x 軸的夾角。

這兩個系統都可以很好的表示二維平面上的每一個點位置,比較特別的是 θ 的範圍不像 rxy 必須是正負無限大,θ 只要在 0 ~ 2*pi 之間就足以標記平面上的所有的點。

Imgur

直角座標和極座標如何轉換呢?比如說在直角座標的 (3, 3) 這個位置轉變成極座標就變成 (3*2^0.5, pi/4)

所以回到我們的 noise 平面函數,如果我們要呈現環狀的 noise 平面,我們可以將 r 座標設為該平面函數的 noise 函數數值,θ 座標和深度值設為該 noise 函數的位置座標。

動畫類型 位置座標 noise 數值
彩帶 x 座標和深度值 y 座標
二維路面(二維灰階圖) x, y 座標 灰度值
環狀煙霧 θ 座標和深度值 r 座標

理解概念後我們就可以開始實作了。

程式實作

這是原本的彩帶動畫程式:

function setup() {
    createCanvas(600, 600);
    background(0);
}

function draw() {
    background(0);
    noFill();
    strokeWeight(2);

    for (let i = 0; i < 50; i++) {
        stroke(255 * i / 50);
        beginShape();
        for (let x = 0; x <= 600; x += 20) {
            let n = float(noise(x * 0.001, i * 0.01, frameCount * 0.01));
            let y = float(600 * n);
            vertex(x, y);
        }
        endShape();
    }
}

然後試著用極座標 r, theta 取代直角座標變數 x, y 的角色,並且依照前面的表格對應,將彩帶環成一圈,最後再調整一些細節參數。

function setup() {
    createCanvas(600, 600);
    background(0);
}

function draw() {
    background(0);
    noFill();
    strokeWeight(2);

    // 將座標原點移至中心點
    translate(width/2, height/2);

    for (let i = 0; i < 30; i++) {
        stroke(255 * i / 30);
        beginShape();
        // for (let x = 0; x <= 600; x += 20) {
        // 變數 x 改成 theta
        for (let theta = 0; theta < 2*PI; theta += 2*PI/180) {
            // let n = float(noise(x * 0.001, i * 0.01, frameCount * 0.01));
            let n = float(noise(theta * 0.5, i * 0.02, frameCount * 0.005));
            // let y = float(600 * n);
            // vertex(x, y);
            // 變數 y 改成變數 r
            let r = float(n * 200);
            vertex(r * cos(theta), r * sin(theta)); // 將極座標變數轉換成 xy 變數放到 vertex 函數
        }

        // 記得 endShape 要 CLOSE
        endShape(CLOSE);
    }
}

這是程式呈現出來的結果:

Imgur

這和我們之前預測的差不多,我們沒有辦法「平滑」的相接彩帶的頭部跟尾部,因為在頭部和尾部的 (r, theta) 並不相鄰,所以 noise 數值也彼此不相關。

那這裡我們就必需使用一些數學技巧,將兩側巧妙的連接起來。

使用 smoothstep 函數

smoothstep 是計算機圖學中用來進行插值的函數,主要用來生成一系列平滑過渡的值,我們要用這個函數在彩帶的頭部跟尾部來完成平滑相接。

smoothstep 函數的數學表示法為

Imgur

以下為函數圖:

Imgur

可以看到這個函數從 0 到 1 的過渡是非常平滑的,如果是用一般的線性插值:

Imgur

以下為函數圖:

Imgur

可以看出在 x = 0x = 1 的位置會有一個明顯的折點,就不是所謂的平滑過渡了。

那我們要怎麼把 smoothstep 函數用來進行頭尾相接呢?我的做法是,取最後 theta 在 11/12 * 2pi ~ 2pi 之間的部分,以 smoothstep 數值的比例與 theta 為 0 時的 noise 數值進行混合。

若我們以這種形式平滑的混合頭部跟尾部的 noise 數值,最後我們會得到以下結果:

Imgur

但因為今天的篇幅已經夠多了,所以先給讀者們一個回家作業,花時間想一下如何導入 smoothstep 函數改善既有程式,明天再公佈解答。


上一篇
[Day16] p5.js 基礎教學(八) –– noise 函數
下一篇
[Day 18] p5.js 實戰演練(七) –– 煙霧動畫實作(二)
系列文
p5.js 的環形藝術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言