iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
Modern Web

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

[Day 14] p5.js 實戰演練(四) –– 結晶動畫實作(一)

  • 分享至 

  • xImage
  •  

之前我有一個作品 結晶(Crystallization),就是以前一單元 p5.js 基礎教學(七) –– 繪製不規則形狀 學習到的 vertexbeginShapeendShape 函數為基礎,並搭配迴圈、隨機函數等等技巧組合而成:

Imgur

所以這次的實戰演練單元,我們就來詳細講解該作品的實作思路與脈絡。

觀察環狀分佈的頂點連接模式

我們可以先觀察,若多個頂點均勻分佈在某個半徑固定的圓周上,連接後會呈現出怎樣的視覺效果:

function setup() {
    createCanvas(windowWidth, windowHeight);
    stroke(0);
}

function draw() {
    background(255, 30);
    translate(width/2, height/2);
  
    var point_num = 24;
    var radius = 150;
	
    beginShape(TRIANGLES);

    for (let i = 0; i < point_num; i++) {
        vertex(
            radius * cos(i * 2 * PI / point_num), 
            radius * sin(i * 2 * PI / point_num)
        );
    }

    endShape();
}

Imgur

看起來非常顯而易見,就跟我們上一個單元的範例是差不多的結果。

這個範例我們的頂點是沿著圓周按照順序標記的,那如果我們是跳著標記頂點,會呈現怎樣的結果呢?

function setup() {
    createCanvas(windowWidth, windowHeight);
    stroke(0);
}

function draw() {
    background(255, 30);
    translate(width/2, height/2);
  
    var point_num = 24;
    var radius = 150;
	
    beginShape(TRIANGLES);
	
    var j = 0; // 從角度 0 開始標記頂點
    var d = 5; // 每次跳 5 格標記頂點

    for (let i = 0; i < point_num; i++) {
        // i 變成單純計數的變數
        j += d;
        j %= point_num;
		
        vertex(
            radius * cos(j * 2 * PI / point_num), 
            radius * sin(j * 2 * PI / point_num)
        );
    }

    endShape();
}

Imgur

這個視覺效果看起來就有趣不少,然後我們再添加一些顏色並設置適當的透明度:

function setup() {
    createCanvas(windowWidth, windowHeight);
    stroke(0);
}

function draw() {
    background(255, 30);
    translate(width/2, height/2);
  
    var point_num = 24;
    var radius = 150;
	
    colorMode(HSB, 360, 100, 100, 100);
    stroke(90, 30, 80);
    fill(90, 30, 80, 10); // 將透明度設為 10,讓顏色有覆蓋疊加的效果
    colorMode(RGB);
	
    beginShape(TRIANGLES);
	
    var j = 0; // 從角度 0 開始標記頂點
    var d = 5; // 每次跳 5 格標記頂點

    for (let i = 0; i < point_num; i++) {
        // i 變成單純計數的變數
        j += d;
        j %= point_num;
		
        vertex(
            radius * cos(j * 2 * PI / point_num), 
            radius * sin(j * 2 * PI / point_num)
        );
    }

    endShape();
}

Imgur

可以觀察到如果我們分別將變數 d 設為 234,因為跳的格數和總頂點數 point_num24),並不互質,所以我們跳不到所有的頂點,因此建議還是將 point_num 設為一個質數。

Imgur

引入隨機函數

接下來我們要引用隨機函數,讓起始標記點 j 以及跳躍的格數 d 隨機化,看看有什麼樣的效果。

function setup() {
    createCanvas(windowWidth, windowHeight);
    stroke(0);
}

function draw() {
    background(255, 30);
    translate(width/2, height/2);
  
    var point_num = 29; // 將 point_num 設為質數
    var radius = 150;
	
    colorMode(HSB, 360, 100, 100, 100);
    stroke(90, 30, 80);
    fill(90, 30, 80, 10); // 將透明度設為 10,讓顏色有覆蓋疊加的效果
    colorMode(RGB);
	
    beginShape(TRIANGLES);
	
    var j = int(random() * point_num); // 將 j 隨機化
    var d = int(random() * point_num); // 將 d 隨機化

    for (let i = 0; i < point_num; i++) {
        // i 變成單純計數的變數
        j += d;
        j %= point_num;
		
        vertex(
            radius * cos(j * 2 * PI / point_num), 
            radius * sin(j * 2 * PI / point_num)
        );
    }

    endShape();
}

Imgur

因為每一幀 random 函數都會任意生成一個數值,所以每一幀的連結方式都會有快速的變動,這樣的動畫實在是太快了,不是我們想要的方式。

假設我們想要將速度放慢,改成每 12 幀再取一個隨機值,應該要怎麼做呢?

會發現 random()randomSeed() 好像無法直接解決這個問題(針對每 12 幀再取一次隨機值),有一個解決方案是我們可以再設一個計數器每數到 12 再去取一個隨機值,但這樣好像有點醜陋,所以這裡引入一個特殊的技巧,我們可以設計一個隨機函數 my_random

function my_random(x) {
    return fract(sin(x*425.121)*437.53123);
}

我們嘗試設計一個隨機函數,給一個輸入,函數會給我們一個「看似」隨機的輸出,反正人腦解析不出規律就好了,不需要真的隨機輸出。

原理是建構一個 sin 函數,並且讓這個函數的頻率和震幅拉的非常大,最後再取其正小數部分,這樣我給定一個 x 值,我們就不太能預測其 y 值到底在 0~1 的哪一個部分,那就相當於建構出一個「看似」隨機的函數了。

為什麼我們要建構一個必須給定 x 參數的隨機函數呢?因爲這樣我們就可以傳 int(frameCount/12) 進去了,這個數值每過 12 幀才會變動一次,也就是說,過 12 幀才會變動一次隨機值:

function setup() {
    createCanvas(windowWidth, windowHeight);
    stroke(0);
}

function my_random(x) { // 將 sin 函數的頻率和震幅拉大,再取其正小數部分
    return fract(sin(x*425.121)*437.53123);
}

function draw() {
    background(255, 30);
    translate(width/2, height/2);
  
    var point_num = 29; // 將 point_num 設為質數
    var radius = 150;
	
    colorMode(HSB, 360, 100, 100, 100);
    stroke(90, 30, 80);
    fill(90, 30, 80, 10); // 將透明度設為 10,讓顏色有覆蓋疊加的效果
    colorMode(RGB);
	
    beginShape(TRIANGLES);
	
    var random_seed = int(frameCount/12); // 每過 12 幀數字才會變化
	
    var j = int(my_random(random_seed) * point_num); // 將 j 隨機化
    var d = int(my_random(random_seed) * point_num); // 將 d 隨機化

    for (let i = 0; i < point_num; i++) {
        // i 變成單純計數的變數
        j += d;
        j %= point_num;
		
        vertex(
            radius * cos(j * 2 * PI / point_num), 
            radius * sin(j * 2 * PI / point_num)
        );
    }

    endShape();
}

Imgur

現在連接方式的隨機變動已經完成,剩下的等下一個單元在慢慢說明。


上一篇
[Day 13] p5.js 基礎教學(七) –– 繪製不規則形狀
下一篇
[Day 15] p5.js 實戰演練(五) –– 結晶動畫實作(二)
系列文
p5.js 的環形藝術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言