前兩篇屬於互動類的動態圖形, 分別使用滑鼠與鍵盤控制圖形之繪製, 本篇則是要測試自動類的動態圖形, 主要是透過隨機 (亂數) 函式 random() 與雜訊函式 noise().
3. 利用隨機函式畫動態圖 :
Javascript 的隨機函式 Math.random() 會傳回一個值域為 0~1 之間, 精度為小數點後 16 位之偽隨機數, p5.js 以將其包裝為更好用的內建函式 random(), 並提供其他相關的函式如下表 :
random([min=0, max=1]) | 傳回介於 min (含, 預設 0) 與 max (不含) 之間的隨機浮點數 |
random(arr) | 從陣列 arr 的元素中隨機挑一個傳回 |
randomSeed(seed) | 設定隨機種子 seed 數值使 random() 傳回固定序列隨機數 |
randomGaussian([mean, std]) | 傳回平均值 mean 標準差 std 之常態或高斯分布隨機數 |
p5.js 的 random() 函式的參數是可有可無的, 用法如下 :
- random() :
不傳入任何參數時, 傳回值為 0~1 之間的浮點數亂數. - random(max) :
只傳入一個參數時為 max, 傳回值 0~max 間之浮點亂數. - random(min, max) :
傳入兩個參數時, 前為 min 後為 max, 傳回值 min~max 間之浮點亂數.
也可以將一個陣列傳入 random(), 則傳回值是隨機從陣列中挑選的一個元素. 函式 randomGussian() 則是根據指定平均值與變異數之常態 (高斯) 機率分布來傳回隨機數, 其參數也是可有可無的, 用法如下 :
- randomGaussian() :
不傳入任何參數時, 表示使用平均值=0, 標準差=1 的常態分布密度函數. - randomGaussian(mean) :
只傳入一個參數時, 表示使用平均值=mean, 標準差=1 的常態分布密度函數. - randomGaussian(mean, std) :
傳入兩個參數時, 表示使用平均值=mean, 標準差=std 的常態分布密度函數.
而 randomSeed(seed) 則是用來設定隨機種子, 有設定隨機種子為固定常數的話, 每次重新執行程式將傳回相同序列的偽隨機數; 反之沒設的話每次執行得到的偽隨機數序列會不同.
例如 :
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var arr=[0,1,2,3,4,5,6,7,8,9];
function setup() {
createCanvas(350, 120);
background(1, 70, 100);
fill('orange');
frameRate(1); //將圖框速率設為每秒 1 框
textSize(16);
}
function draw() {
background(1, 70, 100);
text('random():', 10, 20);
text(random(), 90, 20);
text('random(0,10):', 10, 40);
var rnd1=random(0,10); //傳回 0~10 間的隨機浮點數
text(rnd1, 120, 40);
text('parseInt(random(0,10)):', 10, 60); //取整數部分
text(parseInt(rnd1), 180, 60);
text('random([0,1,...10]):', 10, 80);
text(random(arr), 150, 80); //從 [0, 1, 2, ...10] 陣列中隨機挑一個元素傳回
text('randomGaussian():', 10, 100);
text(random(randomGaussian()), 150, 100); //常態分佈隨機數
}
</script>
</body>
</html>
此例使用 text() 在畫布上顯示 random() 與 randomGaussian() 的亂數, 注意, 在 setup() 中刻意用 frameRate() 函式將圖框率設為每秒一框, 這樣才不會太快來不及觀察數字的跳動, 結果如下 :
下面是將上面程式加上 randomSeed() 固定隨機種子的結果 :
測試 3-2 : 使用 randomSeed() 固定隨機序列 [看原始碼]
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var arr=[0,1,2,3,4,5,6,7,8,9];
function setup() {
createCanvas(350, 120);
background(1, 70, 100);
fill('orange');
frameRate(1);
textSize(16);
randomSeed(2); //設定隨機種子, 固定隨機序列
}
function draw() {
background(1, 70, 100);
text('random():', 10, 20);
text(random(), 90, 20);
text('random(0,10):', 10, 40);
var rnd1=random(0,10);
text(rnd1, 120, 40);
text('parseInt(random(0,10)):', 10, 60);
text(parseInt(rnd1), 180, 60);
text('random([0,1,...10]):', 10, 80);
text(random(arr), 150, 80);
text('randomGaussian():', 10, 100);
text(random(randomGaussian()), 150, 100);
}
</script>
</body>
</html>
此例除了添加 randomSeed() 指另外, 程式碼與上面範例完全一樣, 但每次重新整理網頁時, 這些亂數出現的序列數值完全一樣, 通常用來作為示範隨機實驗之用, 學習者可以得到與示範者雷同的結果, 下面是此程式第二個隨機序列結果 :
下面是用亂數畫直線的例子 :
測試 3-3 : 使用亂數畫直線 (1) [看原始碼]
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var arr=[0,1,2,3,4,5,6,7,8,9];
function setup() {
createCanvas(400, 200);
strokeWeight(5); //線條粗細=5px
frameRate(1); //圖框率每秒 5 框
}
function draw() {
background(1, 70, 100);
for (var i=20; i < width-20; i += 5) { //掃描 x 軸, 步階=5px
var r=random(256); //亂數取得 0~255 之紅色碼
var g=random(256); //亂數取得 0~255 之綠色碼
var b=random(256); //亂數取得 0~255 之藍色碼
stroke(r, g, b); //用亂數設定線條顏色
line(i, 20, i, 180); //沿 x 軸 y=180 畫直線
}
}
</script>
</body>
</html>
此例使用 random(256) 產生 0~255 (不含 256) 的 R, G, B 三原色色碼用來設定畫筆的隨機顏色, 然後沿著 y=180 的 x 軸繪製高度 160 px 的直線, 同樣用 frameRate(1) 將圖框率降為每秒一框以免變化太快, 結果如下 :
下面範例則是在畫布上繪製橢圓, 同樣利用 random() 產生隨機色碼與橢圓寬徑與高徑,
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var arr=[0,1,2,3,4,5,6,7,8,9];
function setup() {
createCanvas(400, 300);
noStroke();
frameRate(1);
}
function draw() {
background(1, 70, 100);
for(var x=20; x<=width-20; x += 20){ //沿 x 軸遞增圓心位置
for(var y=20; y<=height-20; y +=20){ //沿 y 軸遞增圓心位置
var r=random(256); //亂數取得 0~255 之紅色碼
var g=random(256); //亂數取得 0~255 之綠色碼
var b=random(256); //亂數取得 0~255 之藍色碼
fill(r, g, b); //填滿橢圓顏色
var w=20 * random(); //亂數取得 0~1 隨機值計算橢圓寬徑
var h=20 * random(); //亂數取得 0~1 隨機值計算橢圓高徑
ellipse(x, y, w, h); //繪製橢圓
}
}
}
</script>
</body>
</html>
此例同樣用 random(256) 取得亂數 RGB 色碼, 用來傳給 fill() 填滿橢圓顏色; 另外橢圓之寬徑與高徑也各自用一個 random() 取得 0~1 的亂數乘以 20 作為該橢圓之寬徑與高徑, 結果如下 :
可見每一個圖框中橢圓大小與顏色都不同, 圖框變換時也隨機變換造型.
上面範例刻意將圖框率放慢以便觀察變化, 下面則以預設每秒 60 圖框來看看用亂數畫直線的跳動情形, 為了簡化起見, 此例只畫一條直線 :
測試 3-5 : 使用亂數畫直線 (2) [看原始碼]
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var x=0;
function setup() {
createCanvas(400, 300);
stroke('yellow');
strokeWeight(2);
}
function draw() {
background(1, 70, 100);
x=random()*width; //用亂數計算隨機 x 座標
line(x, 0, x, height); //繪製垂直線
}
</script>
</body>
</html>
結果如下 :
還有一種稱為雜訊或噪音 (noise) 的隨機序列, 其隨機特徵是體現於每次程序的重新執行與無限大的座標空間, 而非程序內的函式呼叫, 沿著座標軸變化所得到的噪音值可用來繪製平滑變化的動態圖.
下面這個是我從書上看到改寫的有趣的範例, :
測試 3-6 : 沿 x 軸抖動的直線 [看原始碼]
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
function setup() {
createCanvas(400, 300);
}
function draw() {
background(1, 70, 100);
stroke('orange');
for (var x = 20; x < width; x += 20) {
var mx = mouseX / 10; //依據滑鼠 x 座標計算 x 軸偏移量
var dx1=random(-mx, mx); //利用正負偏移量取得 x 軸隨機偏移量 (起點)
var dx2=random(-mx, mx); //利用正負偏移量取得 x 軸隨機偏移量 (終點)
line(x + dx1, 20, x - dx2, height - 20); //利用 x 軸隨機偏移量繪製直線
}
}
</script>
</body>
</html>
此例以滑鼠 x 座標為計算基準, 除以 10 作為偏移量, 然後以正負偏移量用 random() 分別計算直線起訖點的隨機偏移量, 然後以此掃描 x 軸繪製直線, 因此滑鼠在最左邊 x 接近 0, 垂直線很穩定; 當滑鼠越向右, 偏移量的上下範圍越大, 使得直線因為圖框掃描看起來越斗越大, 結果如下 :
下面則是沿 y 軸抖動的版本 :
測試 3-7 : 沿 y 軸抖動的直線 [看原始碼]
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
function setup() {
createCanvas(400, 300);
}
function draw() {
background(1, 70, 100);
stroke('orange');
for (var y = 20; y < height; y += 20) {
var my = mouseY / 10; //依據滑鼠 y 座標計算 y 軸偏移量
var dy1=random(-my, my); //利用正負偏移量取得 y 軸隨機偏移量 (起點)
var dy2=random(-my, my); //利用正負偏移量取得 y 軸隨機偏移量 (終點)
line(20, y + dy1, width - 20, y - dy2); //利用 x 軸隨機偏移量繪製直線
}
}
</script>
</body>
</html>
此例只是把上例的 x, y 對調而已, 結果如下 :
下面是從畫布中央開始隨機畫圓形的範例 :
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var speed=2;
var x;
var y;
function setup() {
createCanvas(400, 300);
background(1, 70, 100);
stroke('red');
fill('cyan');
x=width/2;
y=height/2;
}
function draw() {
if(x > width - 10) {
xmin=5 * speed;
xmax=speed;
}
else if(x < 10) {
xmin=speed;
xmax=5 * speed;
}
else {
xmin=speed;
xmax=speed;
}
if(y > height - 10) {
ymin=5 * speed;
ymax=speed;
}
else if(y < 10) {
ymin=speed;
ymax=5 * speed;
}
else {
ymin=speed;
ymax=speed;
}
x += random(-xmin, xmax);
y += random(-ymin, ymax);
d=random(20);
circle(x, y, d);
}
</script>
</body>
</html>
此例原始構想來自書上, 但書上的範例有缺陷, 隨機圓可能衝出畫布而消失, 即使加上 constrain() 函式來限制也可能讓圓像鬼打牆一樣沿邊跑, 故此處加上 if else 敘述來限制, 當圓心 x 座標靠近畫布右邊界小於 10 px 時就將 random() 的左邊限擴大 5 倍; 反之靠近畫布左邊小於 10 px 時則將 random() 的右邊限擴大五倍, 對於 y 軸也是如此限制, 這樣就不會出界也不會沿邊打轉了, 結果如下 :
4. 利用雜訊函式畫動態圖 :
雜訊 (噪音) 的概念與亂數類似, 它是無限多個像波一樣平滑變化的隨機數, 其變化序列較自然和諧與平滑, 不像 randon() 的隨機序列轉變那麼生硬突兀, 其演算法由 Ken Perlin 於 1980 年代所發明, 稱為 Perlin 雜訊, 由於其變化特性較符合自然界的現象, 常用於計算機圖學或動畫製作中.
p5.js 的雜訊相關函式如下表 :
noise(x, [y, z]) | 傳回指定座標軸之 Perlin 噪音值 (0~1) |
noiseSeed(seed) | 設定雜訊種子以固定傳回的雜訊序列 |
Perlin 雜訊在理論上是定義於無限的 n 維空間, 但 p5.js 僅提供 1D (x 軸), 2D (x, y 軸), 至多 3D (x, y, x 軸) 的雜訊序列.
每次傳入相同的座標呼叫 noise() 時, 它將傳回相同的 0~1 之雜訊值; 但若重新執行程式則會得到不同的雜訊值. 如果想要在重新執行程式時得到相同的雜訊序列, 可先呼叫 noiseSeed() 設定雜訊種子.
由於 noise() 傳回的值是 0~1 的浮點數, 故實務上需要將其放大, 通常會使用映射函式 map() 來達成此目的, 此函式參數如下 :
map(x, amin, amax, bmin, bmax)
其中 x 是要映射的變數, amin 與 amax 是原來的大小範圍, bmin 與 bmax 則是映射後的範圍. 對於 noise() 函式來說就是 map(noise(), 0, 1, bmin, bmax), 例如要將雜訊映射至色碼 (0~255) 的話, 就是
map(noise(), 0, 1, 0, 255).
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var arr=[0,1,2,3,4,5,6,7,8,9];
function setup() {
createCanvas(350, 120);
background(1, 70, 100);
fill('orange');
frameRate(1);
textSize(16);
}
function draw() {
background(1, 70, 100);
text('noise(0.1):', 10, 20);
text(noise(0.1), 90, 20); //1D 雜訊 (x 軸)
text('noise(0.1):', 10, 40);
text(noise(0.1), 90, 40);
text('noise(100, 200):', 10, 60); //2D 雜訊 (x, y 軸)
text(noise(100, 200), 130, 60);
text('noise(1, 2, 3):', 10, 80);
text(noise(1, 2, 3), 110, 80); //3D 雜訊 (x, y, z 軸)
text('noise(100, 200, 300):', 10, 100);
text(noise(100, 200, 300), 170, 100);
}
</script>
</body>
</html>
此例測試了 1D/2D/3D 的雜訊值, 結果如下 :
連續呼叫 noise(0.1) 都傳回同樣的值, 且 draw() 迴圈不斷執行這些值都不會變, 可見在同一個程序中傳入相同參數時 noise() 函式會傳回固定的值, 不像 random() 每次呼叫都會傳回不同的值, 但如果重新載入網頁執行新的程序, 則這些值就會改變, 雜訊的隨機性在此.
如果用 noiseSeed() 設定雜訊種子, 則隨機序列會被固定, 只要除入 noise() 的參數不變, 即使重整網頁啟動新的程序也會得到一樣序列的值, 例如 :
測試 4-2 : 使用 noiseSeed() 固定隨機序列 [看原始碼]
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var arr=[0,1,2,3,4,5,6,7,8,9];
function setup() {
createCanvas(350, 120);
background(1, 70, 100);
fill('orange');
frameRate(1);
textSize(16);
noiseSeed(1); //設定雜訊種子
}
function draw() {
background(1, 70, 100);
text('noise(0.1):', 10, 20);
text(noise(0.1), 90, 20);
text('noise(0.1):', 10, 40);
text(noise(0.1), 90, 40);
text('noise(100, 200):', 10, 60);
text(noise(100, 200), 130, 60);
text('noise(1, 2, 3):', 10, 80);
text(noise(1, 2, 3), 110, 80);
text('noise(100, 200, 300):', 10, 100);
text(noise(100, 200, 300), 170, 100);
}
</script>
</body>
</html>
此例與前一個範例的差別是在 setup() 中多了一個 noiseSeed() 設定雜訊種子, 所以重載網頁還是會得到相同的值 (因為傳入參數相同), 關掉網頁重新開啟也是不變, 結果如下 :
接著來看看 noise() 的自然噪音效果與 random() 的隨機效果有何差別. 下面範例改用 noise() 來繪製上面測試 3-4 以 random() 畫的亂數 x 座標直線 :
測試 4-3 : 使用雜訊值沿 x 軸畫直線 [看原始碼]
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var dx=0;
function setup() {
createCanvas(400, 300);
stroke('yellow');
strokeWeight(2);
}
function draw() {
background(1, 70, 100);
x=noise(dx)*width;
line(x, 0, x, height);
dx += 0.01;
}
</script>
</body>
</html>
此例設定了一個沿 x 軸增量的變數 dx, 傳入 noise() 取得 x 軸的雜訊值 (0~1), 將其乘以 width 後得到直線的 x 座標用來繪製垂直線, 然後在每個 draw() 迴圈中將 dx 增量 0.01, 因此傳入 noise() 的值會是 0, 0.01, 0.02, 0.03, .... 這樣的序列, 這些值會傳回不同的雜訊值, 從而得到不同的 x 座標, 結果如下 :
可見 noise() 的變化較平滑, 不像 random() 那樣突兀, 適合用來在動畫中模擬動作的推移.
下面這個測試改編自官網範例, 利用滑鼠座標位置與雜訊繪製隨滑鼠移動而變化之圖形 :
測試 4-4 : 利用滑鼠座標與雜訊值繪製隨滑鼠移動而變化之圖形 [看原始碼]
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>p5.js test</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
<script>
var r=0.02; //坐標軸縮放比率
function setup() {
createCanvas(400, 300);
}
function draw() {
background('black');
for (var x=0; x < width; x++) { //走訪 x 軸
var n=noise((mouseX + x) * r, mouseY * r); //計算 2D 雜訊
stroke(n*255);
line(x, mouseY + n * 80, x, height); //畫直線
}
}
</script>
</body>
</html>
此例設定了一個全域變數 r 來調整座標軸縮放比率, 以計算 2D 雜訊值 n, 並利用 n 來設定畫筆灰階顏色, 由於 noise() 會對相同輸入座標傳回同樣的值, 因此滑鼠不動, 圖形也不會動, 結果如下 :
沒有留言:
張貼留言