互動藝術程式創作入門 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/互動藝術程式創作入門/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Wed, 05 Jul 2023 04:48:10 +0000 zh-TW hourly 1 https://wordpress.org/?v=6.2.2 https://creativecoding.in/wp-content/uploads/2022/03/cropped-cct-logo-icon-2-32x32.png 互動藝術程式創作入門 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/互動藝術程式創作入門/ 32 32 p5 js互動藝術程式創作 – 初階應用實戰教學!(下篇) https://creativecoding.in/2022/12/26/p5js-workshop-clab-2/ Mon, 26 Dec 2022 03:12:37 +0000 https://creativecoding.in/?p=3435 老闆在第一屆「Processing 臺灣國際社群日」活動中,受邀為設計工作坊擔任講師,針對 p5.js互動藝術程式創作入門的主題進行為期兩天的分享。分別為第一天的基礎練習與第二天的生程式藝術實作,本篇文章內容為第二天的實作練習,希望能讓同學能學習到以基礎的方式,將創意想像以自己的技術實現!

這篇文章 p5 js互動藝術程式創作 – 初階應用實戰教學!(下篇) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
臺灣當代文化實驗場 C-LAB 在 2021 年 10 月 19 日到 10 月 24 日,邀請全球 Processing 使用者共同參與台灣第一屆「Processing 臺灣國際社群日」,活動中集結不同藝術家及設計師的觀點,帶領大眾以多方的視角,進入未來新媒體藝術的全新想像。
老闆在這一連串的活動中,除了擔任對談講者外(參考文章:創意程式設計:Processing/p5.js教學與趨勢觀察——王連晟、吳哲宇台美連線對談),也受邀為設計工作坊擔任講師,此次就是針對 p5.js互動藝術程式創作入門的主題進行為期兩天的分享,分別為第一天的基礎練習與第二天的生程式藝術實作,希望在兩天的時間內,讓同學能學習到生程式藝術創作的基礎,將自己的創意想像以技術實作呈現!在工作坊正式開始前,老闆提供下列素材讓學員進行課前準備,包含:p5.js 的簡短介紹與 Hahow 上的課程示範。
工作坊會從 p5.js 及工具介紹、p5.js 的開發入門、基本圖形繪製以及變數解說循序漸進。
本篇文章為第二天的生程式藝術實作,依據前一天的基礎練習再更進一階,影片總長兩個小時左右,那我們就事不宜遲開始進入工作坊啦!

上篇這邊走:

課程開始

首先,延續第一天的工作坊進度,老闆先大致瀏覽了昨日有進行創作練習的同學們的作品。有同學提出在創作時的疑問,例如,當設定圖形為筆刷時,雖然圖形是連續性的出現,但當滑鼠動作較快時,圖形就無法連續性的出現,這個問題能如何解決呢?

其實這個問題反向思考來說,就是要讓筆刷連在一起,所以老闆先設定 line(),將已經經過的滑鼠位置,與滑鼠正在進行的位置串起,形成如同在繪圖軟體中,筆刷繪製的效果。

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

function draw() {
  stroke(255)
  line(pmouseX, pmouseY, mouseX, pmouseY)
}

也可以再依據線條想要調整粗細、顏色變化,或是當滑鼠按下右鍵後再執行筆刷等等變化。

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

function draw() {
  stroke(255, frameCount%255, 100)
  strokeWeight(10)
  if(mouseIsPressed){
  line(pmouseX, pmouseY, mouseX, pmouseY)
}

第一階段 – 重複迴圈

短暫回顧完第一天的內容後,老闆進行第二天的主要內容的講解,包含以迴圈與互動、增加不同色彩或是複雜圖形的編輯做分享。先從迴圈與互動,建構創作規則上面開始。老闆以知名藝術家,草間彌生的作品說明建構創作規則的原因。草間彌生的知名創作,大多都建立在「觀察事物的形體」、「重複線條的位置」以及「大量的重複」等手法,去構成她的創作系統。或是莫內及梵谷,拆解其創作手法,就會發現使用連續性的線條和錯落的顏色,去重複堆疊出作品。而上述列出的方法,對程式創作來說,是低成本就能執行的技術,也因此我們能藉由這樣的方式去快速獲得一件具有「美術手法」的創作作品。以迴圈來說,可以設計出相同圖形但是不同排列,具有設計感或是抽象感的作品,像臺灣的傳統花磚,就是利用重複圖案,但是利用不同角度的鏡射,排列出具有韻律感的設計作品。

台灣傳統花磚(圖片來源
使用重複技巧的程式作品(作品連結

那我們也開始迴圈實作吧!

老闆先以最單純的圓圈重複做範例,在還沒學習到如何使用重複迴圈技法時,我們可能就是使用重複貼上同一組程式碼,再在位置上做些許變化執行。但這樣並不便利,也因此又能使用迴圈 for() 來解決此項問題,先來說明 for 的基本語法結構。

for (計數變數的起始狀態; 結束條件; 每次結束後變數如何變化) {
  概念相同,需要重複執行的事情
}

實際帶入數字撰寫示範說明:

function setup() {
  createCanvas(windowWidth, windowHeight);
   background(100);
}

function draw() {
  background(0)
  for(let i=0; i<10; i+=1){
    ellipse(width/2+i*50, height/2, 300, 300)
  }
}

在 i 這個變數下,根據前面設定的數字,起始到結束重複執行。

創作過程中,老闆也提供一些設計小技巧,像是顏色選擇。在創作中,顏色佔了很大的一部分,但當我們對於選色上想要有更快速的方式就是應用設計網站。老闆提供一個叫做 COOLORS 的網站給同學做參考,可以選擇自已想要的主要顏色,點按空白鍵就能產生多種配色選項。

COOLORS 網站首頁(網站連結

那要如何經由程式,去排列出不同顏色卻執行重複動作的流程呢?

老闆以陣列舉例,去執行顏色上的排列。在前一天的工作坊中,也有提到陣列以中括號執行即可。我們先列出需要的顏色,再將其存取起來。當形成一個完整陣列後,再套入重複圖形的顏色當中。

let colorList = ["#ffffff","#2e86ab","#d0cdd7","#ffa62b","#273469"]

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(0)
  noStroke()
  for(let i=0; i<50; i+=1){
    fill(colorList[i%5])
    circle(width/4+i*(mouseX/10), height/2, 300 -i*6)
  }
}

在程式創作的好處就是能夠快速地進行大量編輯,例如如果要替換配色,只要在陣列中變換色票即可,又或是,想要變化圖形的位置,我們可以從觀察程式中哪裡有重複進行編輯,以下示範:

先設置一個 function() ,將會重複使用到的程式包在裡面,再將設置的代表文字,套入 draw() 中,簡化重複的程式,增加多樣化編輯。

let colorList = ["#ffffff","#2e86ab","#d0cdd7","#ffa62b","#273469"]

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

function draw() {
  noStroke()
  drawCircles(random(width), random(height))
}

function drawCircles(posX, posY){
  for(let i=0; i<50; i+=1){
    fill(colorList[i%5])
    circle(posX+i*(mouseX/10), posY, 300 -i*6)
  }
}

不想制式的都從同一個顏色開始,可以在顏色上使用 random,讓顏色隨機出現,也需要 int 套入在設定前,讓出現的數字得以四捨五入為整數。

for(let i=0; i<count; i+=1){
  fill(colorList[int(i+random(10))%colorList.length])
  circle(posX+i*span, posY, 300 -i*(300/count))
}

增加了隨機的亂數,作品是否又多增加了不同的詩意呢?老闆在這邊提出了有趣的看法,他認為,程式雖然可大量重複的行為,但再加入無法預測的亂數後,出其不意的呈現,就會在視覺上趨向傳統所謂的藝術感。

迴圈教學到這邊,休息十分鐘讓同學提問以及練習。有同學提問,為何在此次練習中,老闆使用 let 而不是 var 做函數參數呢?老闆在這邊鼓勵大家,雖然在 javascipt 當中是以 var 做使用,但 let 會更加嚴謹。舉例說明,用在函數定義範圍時,let 就是在大括號的範圍內具有作用。但如果是 var 的話,是以 function 為範圍,所以定義較大,判斷上較不容易。在這十分鐘,老闆也持續的做小技巧教學。例如,要如何隨機對應不同顏色組合,做圖形上的顏色變化,我們必須先設定不同的 colorList 設定顏色組合,再依據需求變更。

延伸閱讀:使用 let / var / const 宣告變數的差異(鐵人賽:ES6 開始的新生活 let, const – 卡斯伯

let ColorList = ["#ffffff","#2e86ab","#d0cdd7","#ffa62b","#273469"]
let ColorList2 =["#c9cba3","#ffe1a8","#e26d5c","#723d46","#472d30"]

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

function draw() {
  noStroke()
  drawCircles(random(width), random(height), mouseX/5, 50, ColorList2)
}

function drawCircles(posX, posY, span, count=50){
  for(let i=0;i<count;i+=1){
    // 從清單中選取顏色
    let colorIndex = int(i+random(10))%ColorList2.length
    fill(ColorList2[colorIndex])
    circle(posX+i*span, posY, 300 -i*(300/count))
  }
}

可以依照上方陣列設定,變化要使用 ColorList 或是 ColorList2 執行效果。另外,設定畫布的大小可以讓畫面更接近畫作感覺。

再來進入到使用變數紀錄狀態,如同一個簡化邏輯的過程,就像是假如今天我們設置了三種不同筆刷效果供使用,要如何快速切換這些效果呢?最快的方式又是設定變數。設定筆刷 paintMode,並且設定筆刷在不同模式時的效果,像是隨機大小變化,就能使用 random 進行。

let colorList = "32373b-4a5859-f4d6cc-f4b860-c83e4d".split("-").map(clr=>"#"+clr)
let colorList2 = "0c090d-e01a4f-f15946-f9c22e-53b3cb".split("-").map(clr=>"#"+clr)
let colorList3 = "6622cc-a755c2-b07c9e-b59194-d2a1b8-fff".split("-").map(clr=>"#"+clr)
let colorList4 = "2f2d2e-41292c-792359-d72483-fd3e81-fff".split("-").map(clr=>"#"+clr)

let paintMode = 0

function setup() {
  createCanvas(1000,1000);
  background(0);
  paintMode = int(random(2))
  // print(colorList[2])
}

function draw() {
  // background(0)
  noStroke()
  if (paintMode==0){
    drawCircles(
      random(width),random(height),
      mouseX/5, 50, random([colorList, colorList2])
    )
  } else if (paintMode==1) {
    drawRects(
      random(width), random(height),
      mouseX/5, 50, random([colorList3, colorList4])
    )
  }
}

function mousePressed(){
  paintMode++
  paintMode = paintMode %2
}

function drawCircles(posX, posY, span, count=50, useColorList){
  for(let i=0; i<count; i+=1){
    let colorIndex = int(i+random(10) )%useColorList.length
    fill(useColorList[colorIndex])
    circle(
      posX+i*span, posY,
      300 -i*(300/count)
    )
  }
}

function drawRects(posX, posY, span, count=50, useColorList){
  for (let i=0; i<count; i+=1) {
    let colorIndex = int(i+random(10) )%useColorList.length
    fill(useColorList[colorIndex])
    rectMode(CENTER)
    rect(
      posX+i*span, posY,
      300 -i*(300/count)
    )
  }
}

應用重複的紋理與符號,改變物件的大小、方向或是出現的頻率,讓畫面的效果更加豐富。

第二階段 – 色彩

在 p5.js 裡常用的色彩系統有 RGB 與 HSB 兩種,RGB 是指顏色紅、綠與藍三種交疊後產生,HSB 是由色相、飽和度與亮度三種維度組成。應用 p5.js 的 Color 物件,並填上以像是一個數值、RGB 數值、填入顏色名稱、填入色票號碼或是宣告色彩模式後再填入色碼等多樣化模式都是得以進行的。了解基本設定後,就來進行實際操作示範吧!

先由圖形的顏色變化開始,假設要執行顏色漸變的圓形,可以先設定色彩系統 HSB,再依據可能使用滑鼠讓顏色變化的技巧,進而使顏色做漸進變化。

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

function draw() {
  colorMode(HSB)
  fill(mouseX%360, 100, 100)
  ellipse(mouseX, mouseY, 500, 500);
}

多方應用 random,使其在顏色或是圖形大小變化上,都能做一定範圍的控制。

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

function draw() {
  colorMode(HSB)
  noStroke()
  fill(random(0,50), random(50,100), 100)
  circle(mouseX, mouseY, random(300));
}

或者是控制在特定範圍內,即使是 random 的效果也能控制想要的方向,像是指定色相的呈現,顏色偏移在指定的範圍內。

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

function draw() {
  colorMode(HSB)
  noStroke()
  let startHue =random(0,150)
  for(let i=0; i<20; i++) {
    fill(startHue+i*5, random(50,100), 100)
    circle(mouseX+i*20, mouseY, 200-i*10);
  }
}

另一種常見的顏色使用功能為  blendMode(模式名稱),與 p5.js 當中的疊色模式十分相近, 帶入不同的模式時,疊色的效果就會不同,以 SCREEN 作範例,就是亮系的疊色效果,老闆調整變化速度與圓形顆數示範疊色效果,過程中也可以應由透明度的調整變化出不同的感覺。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
  frameRate(5)
}

function draw() {
  colorMode(HSB)
  noStroke()
  let startHue = random(0, 150)
  blendMode(SCREEN)
  for(let i=0; i<1; i++){
    let currentStartHue = (startHue+mouseY)%360
    fill(currentStartHue+i*5, random(50,100), 100,1)
    circle(mouseX+i*100, mouseY, 200-i*10);
  }
}

或是設定變數,讓在不同位置圖形有指定的顏色,限制色相的範圍,使其在生成時顏色為固定效果,以及加上填塞與單純線條的選項,讓作品更多活潑變化性。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
  // frameRate(5)
}

function draw() {
  colorMode(HSB)
  noStroke()
  let startHue = random(0, 150)
  blendMode(SCREEN)
  // blendMode(MULTIPLY)
  for(let i=0;i<1;i++){
    let xx = random(width) + i*100
        yy= random(height)
    let currentStartHue = (startHue+yy/3)%360
    if(random()<0.5){
      fill(currentStartHue+i*5, random(50,100), 100, 1)
      noStroke()
    } else {
      stroke(currentStartHue+i*5, random(50,100), 100, 1)
      noFill()
    }
    circle(xx, yy, random(100));
  }
}

但,當在使用重複多種圖案顯示時,有些圖案並沒有顯示在畫布上,這時可以應用 pushpop,將圖案分別開來指定顯示,或是設定 frameRate() 圖形的出現速度變化,控制一秒中出現幾次。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
  // frameRate(5)
}

function keyPressed(){
  if (key == "1") {
    frameRate(5)
  } else if (key == "2") {
    frameRate(30)
  } else if (key == "3") {
    frameRate(60)
  } else if (key == "4") {
    frameRate(120)
  }
}

function draw() {
  colorMode(RGB)
  background(0, 1)
  push()
    colorMode(HSB)
    noStroke()
    blendMode(SCREEN)
    let startHue =random(0,50)
    // blendMode(MULTIPLY)
    for(let i=0; i<1; i++){
      let xx = random(width)+i*100
          yy = random(height)
      let currentStartHue = (startHue+yy/3)%360
      if (random() < 0.5) {
        fill(currentStartHue+i*5, random(50,100), 100, 1)
        noStroke()
      } else {
        stroke(currentStartHue+i*5, random(50,100), 100, 1)
        noFill()
      }
      circle(xx,yy, random(50,500));
    }
  pop()
}

練習過程中,老闆也陸續回答同學的發問。

【問題一】請問 Openprocessing Editor 與 p5.js Editor 有所不同嗎?

回答:其實兩者的使用方式以及效能都是大同小異的,皆為提供給創作者即時顯示創作效果的地方。但老闆覺得在整體設計上,openprocessing 對於初學者來說較友善且容易上手,但每個人的感受不一,建議同學都能去嘗試看看自己比較適合哪種應用介面。

【問題二】請問 p5.js 可以做到如同 Team Lab 或是 梵谷光影展那樣的內容嗎?

回答:可以,但在 3D 的呈現上,因為函式庫的內容還沒有那麼完整,在建構呈現效果以及動畫上可能就需要再多加著墨。

【問題三】請問對於每行的分號加或不加有什麼心得或是建議嗎?

回答:老闆在撰寫時習慣不加,因為在 p5.js 撰寫時目前並不是必要的。但是如果同學們擔心的話,在網路上也都能找到自動幫你每行加分號的執行系統。

【問題四】請問 p5.js 有類似於 opencv 進行影像處理或是人臉辨識的進階 library 嗎?

回答:有的,但並不是官方的,名為 ml5.js。在臉部的輪廓或是 pixel 的呈現都是能夠抓到的。

臉部輪廓抓取 api 介面。(截圖自 ml5.js

老闆也有執行過類似的創作,抓取人體位置輪廓,以線條和原點呈現。(作品參考)也建議如果有更多函示庫應用的相關資訊想了解,可以觀看上一篇的影片介紹喔!(影片連結

第三階段 – 畫布操作

此階段以畫布操作,使同學在畫複雜圖形的時候,能以簡化的方式執行,快速且不需多樣計算,重點即是如何簡化繪製圖形的位置。

老闆先以要在畫布上畫多重圓形為舉例,比較先前執行與畫布操作後的差別。這邊應用到三角函數,因為需要使用三角函數去執行圖形的位置,而三角函數的取得需要使用到角度 degree() 功能去計算,今天以要得到一個在中心點圓形的45度角運行的圓形,以下為設定了角度、半徑與函數設定的程式。

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

function draw() {
  background(0)
  fill(255)
  let r = 200
  let ang =frameCount/100
  let x = r*cos(ang)
  let y = r*sin(ang)
  ellipse(width/2, height/2, 100)
  ellipse(width/2+x, height/2+y, 100)
}

此類的設定模式,會出現兩種問題。一,算式偏多,應該可以更加簡化。二,如果要畫多個圓形,就必須重複設定位置。對於這一系列的設定,老闆先解釋角度後說明如何應用,為何畫布的操作便是可以簡化此應用的方式。

我們在操作時,是為了要設定以一個圖形為中心,並依據此的某個特定角度,使用 translate() 進行其他圖形繪製。假設,今天要為中心的圖形,其實位在左上角,那我們移轉畫布就是讓左上角的圖形變成畫布的中央。(移轉畫布解說參考

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

function draw() {
  background(0)
  fill(255)
  let r = 300
  let ang = frameCount/100
  let x = r*cos(ang)
  let y = r*sin(ang)
  translate(mouseX, mouseY)
  ellipse(0, 0, 100)
  ellipse(x, y, 400)
  ellipse(x, y, 300)
  ellipse(x, y, 200)
  ellipse(x, y, 100)
  fill(255, 0, 0)
  rect(200, 200, 50, 50)
}

此種移轉畫布的方式,讓圖形的總體位置相關參數,並不會跟程式邏輯混和在一起。偏移結束後,接下來進到畫布的旋轉。當設定為同種圖形在不同的位置重複出現,使用 translate()roatate() 繪製出多個圖形。

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

function draw() {
  fill(255)
  let r = 300
  let ang = frameCount/100
  translate(width/2, height/2)
  rotate(ang)
  translate(200, 0)
  rect(0, 0, 200, 200)
  fill(255, 0, 0)
  rect(200, 200, 50, 50)
}

嘗試多做點不同變化,像是旋轉的幅度以及圖形的大小隨著旋轉有所改變。

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

function draw() {
  push()
    fill(255)
    let r = 300-frameCount/2
    let ang = frameCount/100
    let currentScale = 1-frameCount/500
    translate(width/2, height/2)
    scale(currentScale)
    rotate(ang)
    translate(r, 0)
    rect(0, 0, 200, 200)
  pop()
}

在顏色排列上,除了 random 的隨機排列外,也能使用 noise() 執行持續性的變化。什麼是 noise?其代表著連續性的、可預測性的亂數。依據 perlin 噪聲圖上的不同位置,去影響每個設定,給相同點的時候,出來的結果會是一樣的,所以就可以嘗試將 random 更換成 noise 去觀察結果的差異。

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

function draw() {
  noStroke()
  push()
    colorMode(HSB)
    fill(noise(frameCount/50)*100,100,100)
    let r = 300-frameCount/2
    let ang = frameCount/20
    let currentScale = 1 - frameCount/500 + random(0.1, 0.5)
    translate(width/2, height/2)
    scale(currentScale)
    rotate(ang)
    translate(r, 0)
    rect(0, 0, 200, 200)
  pop()
}

將 noise 與 random 共同使用,讓顏色變化不死板,有多樣性的呈現。使用 hue 色相作為變數,去引導顏色為漸進的變化。

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

function draw() {
  push()
    colorMode(HSB)
    let useHue = (random(20)+noise(frameCount/50)*100 +frameCount/3)%360
    fill(useHue,100,100)
    let r = 300-frameCount/2
    let ang = frameCount/20
    let currentScale = 1 -frameCount/500 +random(0.1,0.2) +noise(frameCount/50)/2
    translate(width/2, height/2)
    scale(currentScale)
    rotate(ang)
    translate(r, 0)
    rect(0, 0, 200, 200)
  pop()
}

今日工作坊總結

在本次工作坊的下篇,我們總共學習到了三大項主題,包含迴圈的建構創作規則、變化色彩使用與留下痕跡,到最後的進階應用,畫布的變化。上述這些應用,老闆在做後一個示範中,將上述的功能都包含在裡面。由左到右畫一連串的長方形,這些動作是可以被累加的。先使用 translate 將圖形重複的執行,再設定變數以及出現的規則,將圖形依照滑鼠位置做變化。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(100);
}

function draw() {
  // ellipse(mouseX, mouseY, 20, 20);
  fill(random(255), 200, 200)
  translate(0, height/2)
  rectMode(CENTER)
  for(let i=0; i<50; i++){
    rotate(map(mouseY, 0, height, -0.5, 0.5))
    translate(50,0)
    scale(0.95)
    rect(0, 0, 500, 500)
  }
}

雖然快接近結束,同學們還是十分積極的詢問問題,像是,如果想針對資料進行統計的視覺化呈現,有什麼相關的 lib 或 sample 可以參考嗎?比如地區人口統計?針對此問題,老闆認為目前 p5.js 的函數庫在視覺化上的效果還不是最齊全的,可能會使用其他函數庫如 D3.js 來進行,但也還是可以參考先前的相關課程,針對 p5.js 在視覺化上的 api 應用教學,來嘗試進行(參考文章)。

最後,也不免俗的與同學分享老闆在互動藝術程式創作的其他課程,或是影片推廣給大家。包含 Creative Coding TW – 互動程式創作台灣站 的專業文章網站、社群軟體上的即時資訊分享 老闆 來點寇汀吧。 Boss,CODING please(臉書)、老闆來點寇汀吧 Boss, Coding Please (臉書)、老闆,來點寇汀吧。Boss, CODING please(Youtube 頻道),當然還有想要進一步正式開始創作,歡迎加入 互動藝術程式創作入門課程 開始學習!本次分享就到這邊結束啦,歡迎各位一起踏入程式與藝術交織的世界!

此篇直播筆記由幫手熊柑協助整理

這篇文章 p5 js互動藝術程式創作 – 初階應用實戰教學!(下篇) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
p5 js互動藝術程式創作 – 初階應用實戰教學!(上篇) https://creativecoding.in/2022/12/26/p5js-workshop-clab-1/ Mon, 26 Dec 2022 03:10:20 +0000 https://creativecoding.in/?p=3403 老闆在第一屆「Processing 臺灣國際社群日」活動中,受邀為設計工作坊擔任講師,針對 p5.js互動藝術程式創作入門的主題進行為期兩天的分享。分別為第一天的基礎練習與第二天的生程式藝術實作,本篇文章內容為第一天的基礎練習,希望能讓同學能學習到以基礎的方式,將創意想像以自己的技術實現!

這篇文章 p5 js互動藝術程式創作 – 初階應用實戰教學!(上篇) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
臺灣當代文化實驗場 C-LAB 在 2021 年 10 月 19 日到 10 月 24 日,邀請全球 Processing 使用者共同參與台灣第一屆「Processing 臺灣國際社群日」,活動中集結不同藝術家及設計師的觀點,帶領大眾以多方的視角,進入未來新媒體藝術的全新想像。
老闆在這一連串的活動中,除了擔任對談講者外(參考文章:創意程式設計:Processing/p5.js教學與趨勢觀察——王連晟、吳哲宇台美連線對談),也受邀為設計工作坊擔任講師,此次就是針對 p5.js互動藝術程式創作入門的主題進行為期兩天的分享,分別為第一天的基礎練習與第二天的生程式藝術實作,希望在兩天的時間內,讓同學能學習到生程式藝術創作的基礎,將自己的創意想像以技術實作呈現!在工作坊正式開始前,老闆提供下列素材讓學員進行課前準備,包含:p5.js 的簡短介紹與 Hahow 上的課程示範。
工作坊會從 p5.js 及工具介紹、p5.js 的開發入門、基本圖形繪製以及變數解說循序漸進。本篇文章為第一天的基礎練習介紹,影片總長兩個小時左右,那我們就事不宜遲開始進入工作坊啦!

什麼是 Creative Coding ?

老闆以自我介紹作為開場,從大學的電機工程背景到至紐約就讀新媒體藝術相關研究所,而開始了一連串生成式藝術的創作。對於什麼是 Creative Coding,老闆說自己也是在不太理解的狀況下開始的。是用程式創作藝術嗎?那到底程式創作出來的藝術,最終呈現出來的效果又會是如何呢?這些想必是初接觸生成式藝術的人都會提出的問題,因此老闆提供不同的案例分享,作為答覆的參考。
以目前最常看到的例子來說,視覺上,像是原先平面的設計海報以動態的方式呈現增加趣味性,或是模擬傳統媒材(如水墨)等等,藉由制定規則,讓每一次不同互動產生出的作品都有不一樣的呈現,這使每件藝術作品都是獨一無二的,並且在參與度提升的情況下,成為對互動者更特別的藝術存在。還有在互動網頁上,像是老闆成立的墨雨設計工作室,就有許多應用相關技術的案例,例如與台灣啤酒合作的網頁。或是現在有很多網頁,當使用者將滑鼠向下移動要瀏覽整個畫面時,也會有不同的動畫呈現,整個瀏覽體驗就像是一個完整的時間軸。

老闆作品 Slient Sky 參考範例(連結
台灣啤酒合作案例(連結

在執行設計創作時,老闆也提出自己建立規則的範例供大家參考,以下方作品為例,從中心散開的圓心,其規則並不複雜。先從中心設定隨機數量生成的粒子,粒子會向外成長擴散,並且在成長的過程中變化成不同的顏色,以及施加不同的外力讓粒子扭曲變形。綜合以上這四項變化,就會得出每一次都是不同作品的互動創作。

視覺平面的傳達外,像是在美術館裡的互動式藝術,或是在街角呈現的大型裝置都是可以用 p5.js 執行的。

p5.js 是什麼?

Processing 為一種開源式語言,最初的目的是希望能以視覺化的方式幫助非程式設計師學習程式語言,而後演變成在電子藝術或是互動式設計上都能發現其應用。

Processing 網站首頁(截圖自 Processing網站首頁 )

p5.js 即是將 Processing 以 Javascript 語言做應用,使其可在網路瀏覽器上進行直接創作,免費及開放資料庫的特性,降低了學習門檻,也吸引更多人進入創意程式設計 Creative Coding 的領域。

p5.js 網站首頁(截圖自 p5.js網站首頁 )

此外還有像是 MaxMSP,以 Creative Coding 應用在音樂的視覺化效果呈現。其撰寫時與 p5.js 的不同在於,p5.js 一般會以程式碼顯示,但在 MaxMsp 是直接以視覺的圖案的方式做呈現,將語言命令可視化成一塊一塊的圖形,再依位置的安排去創造不一樣的效果,串接出不同的流程,進行音樂的創作。

MaxMSP 網站首頁(取自 MaxMSP 網站首頁)

或是 Sonic Pi,一個使用文字創作音樂的程式設計環境。

Sonic Pi 網站首頁(取自 Sonic Pi 網站首頁 )

Touch Designer 也屬於視覺為基礎的語言,採用圖形化的介面讓使用者創作,實現多媒體特效。

Touch Designer 網站首頁(取自 Touch Designer 網站首頁 )

不同的創作環境,都說明 Creative Coding 不論在視覺上,或是聲音上都有豐富的創作應用。而 p5.js 代表的就是更全面的環境,整合了從3D 到聲音都有涵蓋。有同學提到,既然 p5.js 與 processing 的語法幾乎相容,那還有什麼狀況下選擇 processing 比較好呢?老闆回覆是,其實 processing 在過去的優勢現在幾乎在 p5.js 上都能進行,建議剛入門的同學以好操作上手的 p5.js 開始創作入門,這樣到後面如果真的因為 pocessing 可能在效能上有相對好一些,要回 processing 上進行創作,入門時間也不會過長。歸類以下 p5.js 的優勢,簡化了語法、減少了無程式背景的創作者的入門門檻,以及多項資料庫可使用或是簡易自行建立。

課程開始

首先,登入進 Openprocessing ,直接點擊連結便能開始以創作,或是也可以追蹤老闆及不同的創作者,參考不同的創作概念。進入個人創作頁面後,便能看到 「Create a Sketch」,每建立一個 Sketch 都代表一個新的創作實驗場域。點進去創作介面會先看到基本程式裡分成兩個部分, setupdraw 兩種不同的規則。前者代表在畫布上的設定,一次性的準備動作,後者代表不斷在畫布上重複著畫上新東西的動作。在創作同時,我們也可以在右方欄位做 layout 設定,讓撰寫程式同時在同一版面看到作品呈現。

一、顏色置換示範

推薦使用 Chrome 瀏覽器的擴充功能「 ColorZilla 」,可以挑選想要的顏色並顯示出色票。當有了色票後,便可以依上色元素不同,在 background() 或是 fill() 做更換。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255,181,35);
}

function draw() {
  circle(mouseX, mouseY, 20);
}

二、繪製圖形、圖形大小與顏色變更示範(筆刷變換)

基本變化像是圖形中不填色 noFill()、去掉圖形外框 noStroke(),或是 strokwWeight() 控制線框粗細。另外,當你要暫停一行程式的功能,請按下 ctrl 鍵與 /,就能看到反灰並且暫停功能的程式碼。

基本圖形控制可以至 p5.js 資料庫中做多方搜尋,以本次範例橢圓形 ellipse 為主,有設定位置是特別指定或是以滑鼠 mouseX、mouseY 代替,與形狀的長寬大小設定。

進入到顏色階段,針對圖形總共有兩個部份可以做編輯,分別是圖形內的顏色以及圖形線條的顏色。fill() 提供圖形內的顏色,stroke()則負責圖形線條的顏色,在顏色選取上,能使用顏色色票、顏色名稱(請記得要加上雙引號,例如:”white”)以及 rgb 顏色(CSS Colors 網站)。如果想要使顏色變化根據互動而有更多元的呈現,可以將 rgb 色彩數值的其中一項更改成滑鼠位置,如此一來顏色就會根據滑鼠位置變化。

滑鼠效果除移動中會有變化外,在 if 與 else 間進行條件判斷,就能在不同情況中呈現不同的效果,下方是根據滑鼠點按之間的效果呈現。

function draw() {
  noStroke()
  fill(mouseX/4,mouseY/4,200)
  print(mouseX/4)
  
  if (mouseIsPressed){
    ellipse(mouseX, mouseY, 100,100);
  } else {
    stroke(255)
    noFill()
    rect(mouseX,mouseY,100,100)
  }
}

如果要增加顏色多樣性,可以使用 random() 設定需要的顏色數值,讓不同的顏色隨機出現。也另外補充個撰寫程式小技巧,可以在選擇多組相同數字時,使用 ctrl 鍵加上 D 進行多重選取減少時間。

上述是由變換位置去進行筆刷的變更,那藉由時間來變更的部分,可以使用 frameCount() 編輯。以示範案例中的橢圓形舉例來說,假使要讓橢圓形的 Y 軸隨著時間來進行筆刷大小的變化,在相對應的 Y 軸編輯位置放上 frameCount 就可以執行,下方範例:

function draw() {
  noStroke()
  fill(mouseX/4,mouseY/4,200)
  print(mouseX/4)

  if (mouseIsPressed){
    ellipse(mouseX, mouseY, 200,frameCount);
  } else {
    stroke(255)
    noFill()
    rectMode(CENTER)
    rect(mouseX,mouseY,100,100)
  }
}

講解到這邊後,老闆給線上參與的同學進行五分鐘的線上練習,題目為:請設計出一個隨機大小為 0 到 50 的圓形筆刷。與此同時,也開放同學在製作時有任何問題能當場詢問,以下問題歡迎參考。

【問題一】 在 p5.js 裡面是否有質感筆刷可以應用或是製作呢?像是油漆筆刷般的效果?

答:有許多方式可以處理,目前先以比較便捷的方式做示範。

先至網路上找尋相關筆刷的圖樣,比如此次是製作油漆類型,那就搜尋相近的筆刷圖案,先下載圖片。

下載其中自己比較喜歡的圖片後,再至 openprocessing 的編輯頁面上傳圖片檔案。便可以開 使進行質感操作啦!

首先,需要先使用 var 宣告與 preload function 預載入,設置質感 texture 的出現,再設置筆刷 image 進行應用,程式與呈現效果如下:

var paintTexture

function preload(){
  paintTexture =loadImage("20190806101953yop172.png")
}

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

function draw() {
  noStroke()
  fill(mouseX/4,mouseY/4,200)
  print(mouseX/4)

  if (mouseIsPressed){
    image(paintTexture,mouseX,mouseY)
  } else {
    stroke(255)
    noFill()
    rectMode(CENTER)
    rect(mouseX,mouseY,100,100)
  }
}

【問題二】 如何分享在 openprocessing 上創作的作品呢?

答:將作品儲存後,可以使用網址直接分享,或是 openprocessing 作品頁面右上方的分享功能都可以使用喔。

接下來進到線上練習的分享時間!

經過五分鐘的練習後,同學應用上述老闆教學說明到的不同應用進行創作。老闆也針對這些分享作品提供不同的建議,或是告訴其他同學這些不同創作是怎麼製作的。例如,有作品使用透明度進行不同編輯,在這邊透明度的應用分為兩種:第一種為圖案顏色的透明度,此類型是在圖案填色 fill() 中,除了 rgb 填寫外,再增加一個代表透明度的數字,變成 rgba 模式,例如 fill(255,255,255,10),代表顏色具有百分之十的透明度;另一種情況為圖形筆刷在背景留下軌跡與否,在 background() 編輯時,設定透明度,讓背景每秒覆蓋的顏色不會完全覆蓋,如此一來就能留下上一秒的軌跡。

三、變數解說教學

進入應用變數的環節,老闆以設計橢圓形為範例說明。如何快速的將兩個不同的,寬與高等比例放大呢?我們需要設置一個櫃子來存放將這些即將運用到的變數,在需要的時候就可以快速抓取使用。運用 let 設置存放變數,舉例來說 let r=50,後續在 draw 裡面,使用到 r 時程式就會帶入 50 這個數字。

延伸閱讀:使用 let / var / const 宣告變數的差異(鐵人賽:ES6 開始的新生活 let, const – 卡斯伯

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

function draw() {
  noStroke()
  fill(mouseX/4,mouseY/4,200)
  print(mouseX/4)

  if (mouseIsPressed){
    let r =random(50,200)
    ellipse(mouseX, mouseY, r,r);
    image(paintTexture,mouseX,mouseY)
  } else {
    stroke(255)
    noFill()
    rectMode(CENTER)
    rect(mouseX,mouseY,100,100)
  }
}

四、如何繪製形狀

經由上述的基礎設定、顏色選填、變數教學與基本圖形設定解說後,接下來進行繪製形狀的教學。形狀除了先前提到的圓形或是方形外,還有線條、3/4圓甚至是自己建立的幾何形狀,其實都能應用函數的變化去繪製出。老闆先以線條舉例,函數式為 line(x1,y1,x2,y2),分別代表連成線條的不同點,而線條粗細是以 strokeWeight() 調整。那該如何得知座標呢?可以先透過設定文字,顯示出滑鼠在座標上移動的位置。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(255)
  strokeWeight(10)
  line(0,0,500,500)
  textSize(50)
  text(mouseX + "," + mouseY,mouseX,mouseY)
}

老闆也提到如何將圖案融合文字,呈現對話框般的資訊。先設定隨機出現的長方形,並且將方角設定成圓角,使其更具有對話框的設定。並且運用變數設定,使文字與對話框在相同的設定下隨機出現。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  // background(255)
  strokeWeight(2)
  let x = random(width)
      y = random(height)
  rect(x,y,200,50,20)
  text("HELLO",x,y+40)
}

或者使用特定條件呈現更多層次的畫面,像是加上 if()else() 的二元判斷變化,以及使用變數讓不同文字隨機出現在不同的對話框中。執行的同時,老闆也會分享一些在寫程式的快捷鍵和小撇步,假使不想只選取單一文字,而是同行多個文字選取,可以根據游標位置,按下 alt 和 shift 再向右或左按下鍵盤左右鍵。或是因為撰寫項目太多,想要進行分類時,可以將無效文字標註在程式中,以防後續搞混或忘記。另外,建立好形狀繪製的基礎後,我們也可以控制出現的頻率。其中一種作法是使用  frameRate() 來編輯。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
  frameRate(10)
}

function draw() {
  strokeWeight(2)
  let x = random(width)
      y = random(height)

  if(random()<0.5){
    fill(0)
    rect(x,y,200,50,20)
    textSize(30)
    fill(255)
    text(random(["HELLO","TAIWAN","CREATIVE","CODING"]),x+20,y+40)
  } else {
    // 指定文字大小
    textSize(30)
    // 白底黑字
    let myText = random(["CLAB","TAIWAN","當代文化實驗場"])
    let w=textWidth(myText)
    // 對話框
    fill(255)
    rect(x,y,200,50,20)
    fill(0)
    // 文字
    text(myText,x+20,y+40)
  }
}

瞭解圖形與文字的隨機變化後,繼續進行隨機自行繪製形狀的產生,從三角形隨機顏色變化為例,以及自行繪製的圖形示範。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  strokeWeight(random(5))
  line(0,0,width/2,height/2)
  fill(random(0,255),random(0,255),200)
  triangle(0,0,width/2,height/2,mouseX,mouseY)
}

在隨機變化中,老闆習慣設定顏色要在哪種色調或色系間做變化進行設計,建議以這樣的方式進行才比較不易出現奇怪的顏色配置問題。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  strokeWeight(random(5))
  line(0,0,width/2,height/2)
  fill(random(50,100),random(100,150),200)
  triangle(0,0,width/2,height/2,mouseX,mouseY)
}

再來是自行繪製的圖形解說,老闆以王冠繪製示範。多邊形製作需要使用到 beginShape() 代表起始位置、 endShape() 代表終點位置,以及 vertex() 說明在起始與終點間會經過哪些點。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(255)
  // strokeWeight(random(5))
  strokeWeight(50)
  line(0,0,width/2,height/2)
  // line(width/2,height/2,mouseX,mouseY)
  // ellipse(mouseX, mouseY, 20,20);
  fill(random(50,100),random(100,150),200)
  // triangle(0,0,width/2,height/2,mouseX,mouseY)
  fill("#ffcc00")
  beginShape()
    vertex(200,200)
    vertex(200,600)
    vertex(750,600)
    vertex(750,200)
    vertex(600,350)
    vertex(470,200)
    vertex(350,350)
  endShape(CLOSE)
  // 繪製座標文字
  fill("red")
  textSize(50)
  text(mouseX+","+mouseY,mouseX,mouseY)
  // circle(mouseX, mouseY, 20);
}

或者是互動式的顏色應用,將王冠與背景顏色或是新增圖形都設定成會依據滑鼠位置的改變進行更動。使用預設函數改變線條粗細、圖案顏色或是模式等等,去繪製圓形、方形,甚至是多邊形,提供作者與互動者不同的作品溝通模式。像是在皇冠上,老闆進行顏色、大小和特別圖形繪製,主要是根據 mouseXmouseY 的設定。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(mouseX/4,100,50)
  strokeWeight(20)
  line(0,0,width/2,height/2)
  fill(random(50,100),random(100,150),200)
  fill(255,mouseX/4,0)
  beginShape()
    vertex(200,200)
    vertex(200,600)
    vertex(750,600)
    vertex(750,200)
    vertex(600,350)
    vertex(470+mouseX/10,200)
    vertex(350,350)
  endShape(CLOSE)
  fill(255)
  circle(325,480,100)
  circle(600,480,100+mouseY/10)
  fill(0)
  circle(325,480,30)
  circle(600,480,30)
  line(355,550,500,520+mouseX/30)
  // 繪製座標文字
  fill(255)
  textSize(50)
  text(mouseX + "," + mouseY, mouseX, mouseY)
}

說明完後,又進到同學們自行練習的時間啦!

那也一樣,老闆趁這時解答同學問題。其中有一位同學詢問到霓虹效果該如何呈現,老闆拿出先前做過類似效果的作品說明示範(作品連結)。主要使用 shadow() 的疊色模式編輯,總體上分為兩個步驟,一個是指定形狀中的顏色,另一個是周圍光暈的 dawingContext() 代表周圍光暈的顏色及顯現的程度。老闆以王冠作品進行實際操作, 將王冠周圍的光暈,從最原始的單一色調設定,進階到隨滑鼠移動改變顏色,到最後是點按滑鼠鍵進行光暈顏色變更,多樣化的創作方式供同學參考。

var lightColor

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(mouseX/4,100,50)
  strokeWeight(20)
  line(0,0,width/2,height/2)
  fill(random(50,100),random(100,150),200)

  // 畫王冠
  fill(255,mouseX/4,0)
  drawingContext.shadowColor = lightColor;
  drawingContext.shadowBlur =30;
  beginShape()
    vertex(200,200)
    vertex(200,600)
    vertex(750,600)
    vertex(750,200)
    vertex(600,350)
    vertex(470+mouseX/10,200)
    vertex(350,350)
  endShape(CLOSE)

  // 畫眼睛
  fill(255)
  circle(325,480,100)
  circle(600,480,100+mouseY/10)
  fill(0)
  circle(325,480,30)
  circle(600,480,30)
  line(355,550,500,520+mouseX/30)

  // 繪製座標文字
  fill(255)
  textSize(50)
  text(mouseX + "," + mouseY, mouseX, mouseY)
  // circle(mouseX, mouseY, 20);
}

function mousePressed(){
  lightColor = color(
    random([
      "#F2C400",
      "#F9C784",
      "#8AE1FC"
    ])
  )
}

後續特別應用,例如想讓 frameCount() 的變化是限制在特定範圍內,可以使用 constrain() 限制 frameCount() 隨機大小的範圍,或是使用 sincos 設計。 sin 是指一到負一之間,譬如 sin(frameCount)*50+200,就可以得知是設定在 負五十加兩百到五十加兩百間。或是,如果要設定由左到右的數字大小,使用 map 設定初始範圍與限制範圍進行有意識的創作編輯。

結語

主要實作教學結束後,老闆也傳授了一些編輯使用上的常用事項。例如,openprocessing 封面設定是可以藉由上方編輯按鈕,進行特定封面圖案設定,如下圖圖示,在此頁面按下右上方 edit 編輯即可操作。

也有同學問到,mouse 的座標是否可以連結到外部感應器的點位?可以開啟編輯頁面左方需連結感應的部分,但如何應用就需要後面再多做分享。或是,想要將 p5.js 的效果呈現在網頁上,能如何操作呢?老闆說明,最快的方式是使用 iframe,先至分享區域做壓縮檔下載、直接使用 emded code 程式碼崁入,或是使用 GitHub 將程式碼轉換成 index 檔案後,到 setting 的 GitHub Pages 上傳,並且再選擇 main 之後存檔,就能藉由網址看到自己已 p5.js 創作的互動程式以網頁的方式呈現。上述精彩的課程教學以及同學們多樣化的提問,就是說明互動藝術程式創作還有好多應用面向可以進行探討,與繼續發掘它不同的樣貌。閱讀到這邊,相信你也對互動藝術程式抱持著更高的興趣了吧!就讓我們接續收看下半場的精彩解說吧!

但,如果你已經迫不急待要開始進行創作,加入 互動藝術程式創作入門課程 開始進行深度學習吧!還有不要忘了 追蹤老闆 Twitter 和訂閱 老闆,來點寇汀吧。Boss, CODING please. Youtube頻道隨時補充新媒體藝術的養份,讓我們一起探索這多元的世界!

此篇直播筆記由幫手熊柑協助整理

這篇文章 p5 js互動藝術程式創作 – 初階應用實戰教學!(上篇) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
科技藝術初階入門看這篇準沒錯!! 新媒體、科技與程式藝術創作入門講座 https://creativecoding.in/2022/10/24/generative-art-basic/ Mon, 24 Oct 2022 03:16:34 +0000 https://creativecoding.in/?p=3001 對科技藝術總是抱著忐忑不安的心情,以至於還不敢踏進來嗎?本篇藉由科技藝術初階入門看這篇準沒錯!! 新媒體、科技與程式藝術創作入門講座直播影片內容作撰寫,藉由跨領域分享與實作教學,讓你跟我們一起安心入門。

這篇文章 科技藝術初階入門看這篇準沒錯!! 新媒體、科技與程式藝術創作入門講座 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
對科技藝術總是抱著忐忑不安的心情,以至於還不敢踏進來嗎?本篇整理自科技藝術初階入門看這篇準沒錯!! 新媒體、科技與程式藝術創作入門講座直播影片,應 ITSA – 教育部智慧創新跨域人才培育計畫的邀請,老闆以新媒體藝術家的角度,分享目前程式藝術界的發展脈絡和創作入門,也藉此引領有興趣的同學們入門。上半場以科技藝術 Creative Coding 做開場介紹,適合初學者快速通盤了解科技藝術目前在市場上面的應用,以及如何使用如 MaxMsp 或是 p5.js 等現有工具進行創作連結,將基本程式使用延伸到創作。下半場以 p5.js和 MaxMsp 的介紹與創作教學為主軸,包括如何應用與未來趨勢分享,如果想要了解更詳細的創作撇步,歡迎至老闆的互動程式藝術創作課程喔!

科技藝術初階入門看這篇準沒錯!! 新媒體、科技與程式藝術創作入門講座

什麼是 Creative Coding?

通常大家比較常將 Coding 認知為解決問題的一種工具,以比較制式的方式去解決設定好的問題。而 Creative Coding 在新媒體藝術領域來說,是結合設計、工程、數學、動態、程式邏輯與硬體的一種創作形式,嘗試在不同事物間建造一座溝通的橋樑。只要擁有程式的基礎概念、能夠靈活運用以及美感具備,就能創作出有趣的作品。以池田亮司,這位來自日本的聲音和視覺新媒體藝術家舉例,其藝術創作多是以資料轉化為視覺藝術模式進行,並投射在物件上,使觀眾在進入藝術展間時,有種身處異空間的錯視感;或是知名沉浸式內容製作公司 TeamLab,以商業型展覽為主,藉由展品與觀眾間的互動模式呈現作品;除了在展覽上的呈現外,像是演唱會或是公共裝置藝術也是現今常被應用的管道。

「Floating in the sky – VR experience」- 吳哲宇(取自吳哲宇個人網站

老闆以「Floating in the sky – VR experience」介紹其創作理念,藉由個人的想法作為出發點,使用了 UNREAL 遊戲引擎、MaxMsp和自行製作的音樂, 結合軟體與硬體間的互動所呈現的有趣 VR實境作品。還有像是「Net Device – The Gambling Lamp」及「Explode – Motion Capture Performance」這幾項作品,帶出不同事物之間,皆能以程式來進行對話,甚至最終變成視覺化的藝術創作。

Creative Coding 對概念的視覺化,強調用不同領域及不同的觀點將自身的角度陳述出來,像詩意運算就是早期在做 Creative Coding 的藝術家所提出來的概念,藉由程式撰寫所表現出的質感、顏色或是律動的模式,如 Tyler Hobbs高偉俊介 或是 Matt Deslauriers,這些具有燈光藝術與裝置藝術背景的創作者作品,在與藝術創作時所提到的「心流」有極大的相似之處。

現在 Creative Coding 最主要的發展方向強調的是,所有人皆能上手的程式語言撰寫,建立現有的程式資料庫,讓沒有程式背景的人在有足夠認知的能力下,也能快速反應的應用方式。而在創作時還是建議,作品本身觀看時,可以不用解釋也能理解其想表達的事物。

從網頁教學到 Creative Coding 教學

HaHow好學校的網頁設計課程
〈動畫互動網頁程式入門 (HTML / CSS / JS)〉
HaHow好學校的網頁設計課程
〈動畫互動網頁程式入門 (HTML / CSS / JS)〉

最初,老闆是以視覺平面設計師的角度教授動畫互動網頁程式入門 (HTML/CSS/JS),在課程中使用 Codepen 或是 OpenProcessing,希望引導設計師們不只是使用程式,更希望能創造出想要的網頁視覺樣貌。以 Vue.js 舉例,一般具工程背景的同學大部分拿它做出網頁的資料框架,但老闆將其應用,做出互動式網頁鋼琴作品

為了將互動式網頁進階與特效做結合,老闆開設了第二門課程,動畫互動網頁特效入門(JS/CANVAS)。在網頁設計的教學歷程中,發現從顏色、平面設計或是為了動態設計而應用到的三角函數等數學(延伸閱讀 : 來用可怕的三角函數做網頁吧! -Part 2科幻時鐘(直播筆記)),這些其實也都是 Creative Coding 應用內容。

其實,使用 Coding 的邏輯也能套用在不同的傳統藝術作品上,舉例來說,許多印象派的畫作皆由不同的筆觸重複堆疊而成,而 Creative Coding 也能使用相同的概念去創作,再應不同的創作設計出不同想法,將客觀物體經由本身的主觀認知詮釋成新的作品。也因此開設了第三門〈互動藝術程式創作入門 (Creative Coding)〉,使作品不限於視覺、聲音、投影或裝置藝術等等,是跨足所有互動相關開發的核心概念。(延伸閱讀 : 章節一 Creative Coding程式創作是甚麼

目前老闆仍持續應用 p5.js 和  OpenProcessing 做創作及教學,前期也有在 C-LAB 工作坊作做分享(延伸閱讀:創意程式設計:Processing/p5.js教學與趨勢觀察——王連晟、吳哲宇台美連線對談)。

p5.js網站首頁(取自p5.js網站首頁)

p5.js 的由來是一位在紐約新媒體藝術家, Lauren Lee McCarthy 所建立的,將開源式語言 Processing 以 Javascript 語言做應用,只要能夠跑瀏覽器就能創作,像是現今流行的 Sketch 和 Figma 等設計工具,就是應用了瀏覽器虛擬化的特點,不用再多安裝任何軟體,在網頁上即可進行。而 p5.js 也藉由其免費及開放資料庫的特性,降低了學習門檻,吸引更多人進入 Creative Coding 的領域。

在上半場的分享中有許多踴躍發言,以下為紀錄同學與老闆的問答:

Q:如何在實作上使用 UNREAL,操作以上所提到的效果並搭配音樂?
A:可以在每節旋律上擷取重拍的位置做效果,或是依據旋律的大小聲做變化。

Q:如何調適創作時的低潮?
A:在創作時必然有自己或他人不喜歡的作品,但還是鼓勵可以從每一次不同的產出中獲得不同的養分,變成下一個的創作靈感。老闆提供自身經驗說到創作的想法來自將日常所發生的點滴記錄下來,或者是平日感興趣的問題也能作為深入探討的目標。

Q:如何使用硬體 EYESY 來做生成式藝術?
A:藉由音樂的輸入,與程式的應用,傳導出生成式藝術。

接續上半場的應用介紹,下半場老闆二話不說實際展示了 Live coding 線上 Demo 給同學們。

p5.js 線上 Demo

本次 Demo 為介紹基礎應用,以下介紹會使用到的 API 及創作成果,使用 Openprocessing 做撰寫,各位直接點擊連結便能開始創作。

應用 API 介紹

  • createCanvas(width, height):創建畫布,參數中分別輸入寬跟高的大小。
  • background(colorCode):制定背景色,可依照文件輸入色碼參數或是基本顏色英文名稱。
  • ellipse(x, y, width, height):在 (x, y) 上繪製一個寬高 (width, height) 的橢圓形。
  • circle(x,y,d):在 (x, y) 上繪製 d 大小的圓形。
  • mouseX():滑鼠沿著左右移動。
  • mouseY():滑鼠沿著上下移動。
  • frameCount():控制每一秒產生的 frame 格數。
  • random():亂數,沒有傳參數時,會來回的隨機浮點數。
  • fill():填入圖形顏色,依照 colorMode 選擇的填色模式填入對應的參數。
  • stroke():圖形邊框顏色,依照 colorMode 選擇的填色模式填入對應的參數。
  • strokeWeight():邊框粗細,依照填入的數字大小。
  • if-else:判斷多種不同條件執行。

基本程式裡分成兩個部分,setup 與 draw。前者代表在畫布上的設定,後者代表不斷在畫布上重複著畫上新東西的動作。而 function 則是指動作的集合。以下先依序講解如何應用基本 API 設定。

一、背景顏色置換示範
function setup() {
  createCanvas(windowWidth, windowHeight);
  background(100); //色碼
}

function setup() {
  createCanvas(windowWidth, windowHeight);
  background("red"); //顏色名稱
}

第一步,先建置畫布,設定畫布的大小及顏色。尺寸使用 createCanvas(width, height) 在參數中分別輸入寬跟高的大小。而畫布顏色使用 background(),並在括號裡依照文件填入色碼(上)或是基本顏色名稱(下)。

二、繪製圖形、圖形大小與顏色變更示範
function draw() {
  fill('black')
  if(mouseIsPressed){
    fill('red')
  }
  stroke('white')
  strokeWeight(2)
  circle(mouseX,mouseY,mouseX+random(50))
}

第二步,畫布上的圖形建置。圓形隨著滑鼠的位置出現,大小會隨著滑鼠在 X 軸上的移動加上到50間的亂數做變化,圓形外框為白色的並且粗細設定為2。在按下滑鼠鍵時,圓形將會被填上紅色,如沒有便是黑色。

三、示範成果
function setup() {
  createCanvas(windowWidth, windowHeight);
  background('black');
}

function draw() {
  fill('black');

  if(random()<0.2){
    fill('white')
  }

  if(mouseIsPressed){
    fill('red')
  }

  stroke('white')
  strokeWeight(2)
  rect(mouseX,mouseY,mouseX/2+random(50))}
}

在這邊,我們先設置一個與視窗長寬相符的黑色畫布,並且在上方生成在不同情況下會有不同外觀改變的長方形,正常情況下,為黑色方形,外框為白色的並且粗細設定為2,而大小會隨著滑鼠在 X 軸上的移動除以2加上到50間的亂數做變化。在亂數小於0.2時,就會變換為白色方形,當按下滑鼠鍵時,則會是紅色方形。將上述 API 整合使用,便會出現以下的Demo範例。

Demo

應用以上基本教學,加上自己思索想要的圖樣,開始小試身手看看!

什麼是 MaxMsp?

MaxMsp 網站首頁(取自 MaxMsp 網站首頁)

MaxMsp 本身具有程式語言的概念,以 Creative Coding 應用在音樂的視覺化效果呈現。其撰寫與 p5.js 的不同在於,一般會以程式碼顯示,但在 MaxMsp 是直接以視覺的圖案的方式做呈現,將語言命令可視化成一塊一塊的圖形,再依位置的安排去創造不一樣的效果,串接出不同的流程。

MaxMsp 實際應用(截圖自本講座影片)

如何開始撰寫呢?在打開版面後,我們能運用兩側及上下方的按鈕來做指令,或是在空白處左鍵點擊兩下叫出長方形空白格,並在空白格內打入文字指令。

以下為本次示範有使用到之按鈕與指令名稱:

  • delay:接收訊號與要延遲多久,假設在delay處設定1000,等於在觸發第一個動作之後,隔一秒才會觸發接在delay後的動作。
  • cycle:在這邊就代表著數學中三角函數的 sin,代表正弦曲線。
  • toggle:控制物件或是介面的開與關
  • scope:類似於製波器
  • dac:將數位的訊號變成類比的訊號
  • ~ :代表輸出時是類比的訊號,為一連串的音訊作呈現
  • saw:鋸齒波
  • tri:三角波
  • slider:藉由在slider上面的滑動製造出聲音
  • kslider:虛擬鋼琴鍵盤
  • mtof:轉換音符對應到的頻率
  • function:設定 ADSR
  • metro:節拍控制
  • sub patch:子函數
  • midiinfo:可讓 midi 裝置在 Maxmsp 裡呈現
  • polyin:可一次取得多個聲音輸入

如何製造出音符?

藉由訊息傳遞數字的不同,連結上 slider 後,再藉由觸發的時間點差異去形成一顆音符。再音高上面,則可以使用虛擬鋼琴鍵盤 kslider,將鍵盤上的音符轉換成頻率,透過視波器去看再圖形上面的呈現。

對於聲音的控制,最主要的兩點為音高與音量大小,傳統在音樂合成上有 ADSR 作為進行的代表。ADSR 字母分別代表了四個不同意思,分別是Attack 、Decay 、Sustain、Release,中文意思為起音、衰減、延音、釋放。

ADSR 圖像(取自維基百科

起音:從無聲音開始上升到最大音量所用的時間。

衰減:最大音量下降到指定的維持音量所需的時間。

延音:主要聲音持續的時間。

釋放:延音到無聲音所需時間。

經由 function 撰寫 ADSR 後,我們得以此控制音量的大小,再加上節拍的控制及前面的波長頻率設定,就完成了基礎 MaxMsp 範例,將音符呈現。

MaxMsp 實際應用(截圖自本講座影片)

再更進階要取得多個音符一次輸入的話,將上述一個音符所應用到的程式函數化,使用 sub patch 濃縮至一個圖形中。如要外接 MIDI 音樂數位介面,且多個音符同時呈現,只要設置 midiinfo 讓 MIDI 音符輸入,再以 polyin 取得同個模組多個聲音,就能製造出好玩的音樂。老闆也分享自身經驗,在前期街觸到 MaxMsp 的 noise 功能後,發現與節拍還有音符的結合創造出非常有趣的東西,因此也鼓勵同學能多方嘗試,玩出興趣也激盪出不同的火花。

新媒體藝術家與新媒體藝術未來趨勢發展

來到講座的最後,老闆分享對於新媒體藝術的未來觀點,新媒體藝術在之後還是會持續存在,不過重點是應用的軟體或平台可能會有所改變,而身為創作者,我們要如何去融會貫通各種不同的應用,以及背後的創作理念呈現的思考方式,都是可以多加著墨以應變未來變化。

例如,元宇宙,將所有3D物件都無資料庫化,以類區塊鏈的方式,將所有人的東西都在網路世界做一個副本,虛實整合,所以不論是虛擬世界及現實世界都能執行交易。也帶領傳統藝術家經由生成式藝術觸發更多不同的靈感。老闆建議,如果要嘗試開始進入新媒體藝術的同學可以從 p5.js 開始下手,或是經由觀看多位不同新媒體藝術家的作品了解應用。

Q. 數位的東西得以快速實踐以及被改變,何以說明所謂的原創?
A. NFT 其實就是以數位正本的概念去運做的,所有人在網路上的交易紀錄都能被認得與證明,清楚的說明所有權的歸屬,這也才造就價值,以限量性和稀有性。

建議從最終想要達到甚麼樣的目標,去開始思考,能從何種媒材下手運用,再來進入到可以使用哪些資源開始學習與實踐。

結語

看完影片和文章後也對科技藝術躍躍欲試了嗎?加入互動藝術程式創作入門課程開始學習吧!還有不要忘了追蹤老闆 Twitter 和訂閱老闆,來點寇汀吧。Boss, CODING please. Youtube 頻道隨時補充新媒體藝術的養份,讓我們一起探索這多元的世界!

此篇直播筆記由幫手熊柑協助整理

這篇文章 科技藝術初階入門看這篇準沒錯!! 新媒體、科技與程式藝術創作入門講座 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
圓圓圈圈:利用迴圈呈現重覆的美 https://creativecoding.in/2022/07/28/cc-ch5-circle/ Thu, 28 Jul 2022 11:32:00 +0000 https://creativecoding.in/?p=2951 「重複」,在藝術表現或日常中很常見的概念,在Creative Coding的領域內更是大量被使用的表現手法,跟大家分享如何在程式創作內加入「重複」概念及實作。

這篇文章 圓圓圈圈:利用迴圈呈現重覆的美 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
在進入正題之前,先請大家觀察下方的圖片。花磚雖然不是台灣傳統建築的「專屬記憶」,但飄洋過海以後落地生根,逐漸在這裡發展出自己的特色。在單一磚片上設計出花紋,透過大量且重複地排列,形成獨特的空間氛圍,也連結成我們小時候的回憶。

「重複」──在藝術表現或日常中很常見的概念,或存在於大自然的晶體結構中、或存在於阿嬤家的廁所廚房內。以音樂而言,在同一首曲子中常可聽到反覆出現的旋律;以文學而言,詩歌中也常出現反覆的句子或單字等;以視覺而言,經由單一的圖案或形體,上下左右不斷的重複予人單純、規律的感受。

在Creative Coding的領域內重複更是大量被使用的表現手法,像是改變物件大小、方向、時間、頻率,或是重複粒子、文字、符號、紋理、以及動作等,善用重複可以讓畫面看起來有整體感。今天就要跟大家分享如何在程式創作內加入重複的概念,以及如何用利用「迴圈」快速製造相同的物件。

各式各樣的汽水罐整齊排列,形成一個奇妙的汽水秩序世界。 
Soda Fantasy @ Che-Yu wu
各式各樣的汽水罐整齊排列,形成一個奇妙的汽水秩序世界。
Soda Fantasy @ Che-Yu wu

目標介紹

  1. 瞭解程式中達成重複的「for 迴圈」概念與常見的應用方式
  2. 瞭解如何在重複中套用規則與創造變化
  3. 嘗試不同的重複概念應用與變化
  4. 結合變數、使用者操作創作重複的作品

迴圈可以重複執行同一組程式碼,幫我們處理不斷重複的事情。 for 迴圈 – 需要以程式碼執行次數作為迴圈的條件。

// for 的基本語法結構
for(計數變數的起始狀態; 結束條件; 每次結束後變數如何變化)
  {
    概念相同,需要重複執行的事件
  }

// 舉例
for(var i=0 ; i < 3 ; i++){
  console.log(i)
}

根據上面的概念,如果我們要在畫布上每 100px水平間隔畫一個圓且重複三次,可以有下面兩種寫法:

// 寫法一:手動複製貼上=人工迴圈
ellipse(0,100,30,30)
  ellipse(100,100,30,30) // 相較於上面,右移動了 100 px
  ellipse(200,100,30,30) // 相較於上面,又右移動了 100 px

// 寫法二:使用程式語言的 for 迴圈
for(var i=0; i<3 ; i++){
  ellipse(100*i, 100, 30, 30)  // 計數變數 i 只要小於 3 時都會做這件事,直到不滿足條件則停下並跳出該程式區塊
}

俗話說的好,能交給別人做的事就不要自己做,善用迴圈可以幫助我們快速處理類似的事件。除了單層的for迴圈以外還有巢狀個for迴圈結構:

for(var i=0; i<2; i++){
  for(var j=0; j<2; j++){    // 在每一個 i 中,都會這層迴圈完再跳往下一個 i
    ellipse(50*i, 50*j, 40)  // 分別於 (50*i, 0) 和 (50*i, 50) 畫圓,畫完之後再往下一個 i 繼續重複
  }
}

在上述的例子中,可以把巢狀迴圈想像成時鐘的分針跟秒針,秒針要跑完一圈,分針才會前進一格。同樣地,外層迴圈i要等內層迴圈j全部跑完才會再+1,所以當j是0和1的時候,分別在(50i, 0) 和 (50i, 50) 的地方畫圓,接著j又會從0開始印,直到外層迴圈結束。(延伸閱讀:[JS] 迴圈筆記

以上了解迴圈的概念後,我們試著用迴圈來創作屬於自己的作品吧。由於Ju編最近去了一個有趣的音樂表演活動Rangeselector,由台灣另類電子搖滾樂團眠腦主演,串連影像、空間動態感測技術所編製的新型態live session,除了隨著節奏跳動的幾何粒子等動態畫面外,觀眾也可以透過移動位置來和展場的視覺產生互動。聽完這場很不一樣的live現場後,覺得或許也可以將自己喜歡的音樂當成主題,來進行Creative Coding的創作。

這次的靈感來源為台灣樂團The Fur. – Friday Love,復古的曲風配上輕快活潑的歌詞,行版的節奏讓人忍不住想跳起舞來,就像一顆又一顆的彩球。藉著這首甜蜜的歌曲,來個類專輯封面的創作吧!

首先我們挑選一組具有復古感的色票,這邊選用的以粉色、紅色系為主,配上米白和橘咖等輔色。既然是專輯封面,那歌名是一大重點,為了凸顯歌名我們試試看在Illustrator製作標準字並輸出,所以在規劃作品草稿時可分為靜態的標準字圖片和動態生成的彩球兩個部分。

復古的配色常以暗濁的暖色調為主,明度和純度都比較低。

如何在OpenProcessing導入圖片呢?我們可以把圖片上傳到第三方平台Imgur,然後把圖片當作背景來使用。

接著來準備下方空白處的彩球,定義一個陣列colors把挑選好的色票放進去,再準備一個circles的空陣列。我們要做一組4*4共16顆的彩球,這邊定義5個變數x, y, d, num, col,分別代表彩球在x軸和y軸的產生位置、彩球的大小、個數和顏色,利用巢狀迴圈的結構產生共16顆:

function setup() {
  createCanvas(600,600);
    let seg = 4;
    let w = width / seg;
    for (let i = 0; i < seg; i++) {
      for (let j = 0; j < seg; j++) {
        let x = i * w + w / 2; //x軸產生位置
        let y = j * w + w / 2; //y軸產生位置
        let d = random(0.5, 1) * w; //彩球大小
        let num = int(random(1, 4)); //彩球個數
        let col = random(colors); //彩球顏色
        for (let k = 0; k < num; k++) {
          circles.push({
          x: x,
          y: y,
          d: d * 0.7,
          c: col
        });
        movers.push(new Mover(x, y, d * 0.5));
      }
    }
  }
}

把準備好的彩球畫出來,注意圖層的順序必須畫在background上方,為了避免跟畫面上的其他元素重疊,位置和大小也要稍微調整一下。

function draw() {
  background(img);
  translate(width / 2, height / 2); // 定位在畫布中間
  scale(0.5); //範圍大小
  translate(-width / 2, -height / 4); // 定位在畫布偏下

  for (let c of circles) {
    fill(c.c); // 用變數c的顏色來填充
    circle(c.x, c.y, c.d); // 畫在變數x,y的位置、變數d的大小
  }
  noStroke();
}

現在我們有一顆又一顆的基本彩球了,看起來是不是很像糖果呢?為了替畫面增加一些動態性,我們在彩球旁邊增加幾顆移動的小彩球,這邊需要分為三種function:定義小彩球參數的constructor、畫小彩球的show、移動小彩球的move。在OpenProcessing的語法庫中,有一些常見的數學常數可以做使用,比如下方用到的PI,可以很快速地引入使用。

let movers = [];

class Mover {
  constructor(x, y, r) {
    this.x = x;
    this.y = y;
    this.r = r;
    this.cs = this.r * 0.4; //第一種大小 
    this.cs0 = this.r * 0.4; //第二種大小
    this.t = random(100);
    this.off = 0;
    this.tStep = random(0.01, 0.05);
    this.ang = random(PI); 
    this.aStep = random(-1, 1) * 0.01;
    this.col1 = random(colors); //第一種顏色
    this.col2 = random(colors); //第二種顏色
    while (this.col1 == this.col2) {
      this.col1 = random(colors);
    }
  }

  show() {
    push();
    translate(this.x, this.y);
    rotate(this.ang); //旋轉的角度
    stroke(255); //加上白色邊框以凸顯小球
    fill(this.col1); //
    if (this.cs0 * 0.15 < this.cs) {
      fill(this.col2); 
      circle(this.off, 0, this.cs);
    } //錯開大球和小球的顏色
    pop();
  }

  move() {
    this.off = map(sin(this.t), -1, 1, -1, 1) * this.r; //彩球正面的角度
    this.cs = map(cos(this.t), -1, 1, this.cs0, 0); //彩球背面的角度
    this.t += this.tStep; //彩球的速度 
    this.ang += this.aStep; //彩球的圓周速率
  }
}

記得在 setup 的地方把 movers 推入 Mover,這樣我們就有移動的小彩球囉!

movers.push(new Mover(x, y, d * 0.5));

這樣就完成啦,希望大家喜歡這次的分享唷,一起試試看如何利用迴圈創作吧!

成品請往這邊走 👉🏻 https://openprocessing.org/sketch/1560267

歡迎加入互動藝術程式創作入門(Creative Coding)線上課程,課程中你可以認識程式與互動藝術產業應用,開啟對工程跟設計的想像,學會使用 p5.js 開發互動介面,整合繪圖、音訊、視訊、文字、3D、互動與機器創作完整的作品,並將創作輸出應用在個人品牌或網站、主視覺或海報,甚至互動裝置、遊戲與教材製作等場景,讓你對進修的資源與路線更有方向。

有興趣的朋友歡迎加入我們的臉書社團,第一時間接收活動報名消息,希望不久的將來,就能看到你跟大家分享你的生成式藝術創作囉!

此篇文章由 Jeudi Kuo 撰寫

這篇文章 圓圓圈圈:利用迴圈呈現重覆的美 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【老闆週六來聊聊】吳哲宇 Artblocks Project – Electriz 製作分享 https://creativecoding.in/2022/06/09/artblocks-electriz/ Thu, 09 Jun 2022 02:40:00 +0000 https://creativecoding.in/?p=2865 老闆不藏私!抓住NFT生成式藝術浪潮,分享在製作 Artblocks Project - Electriz 背後的理念與故事,以及介紹如何在生成式藝術平台上架p5.js演算法的技術細節。

這篇文章 【老闆週六來聊聊】吳哲宇 Artblocks Project – Electriz 製作分享 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
在這波沸沸揚揚的NFT風潮中,不知道大家有沒有跟上呢?在之前的文章中,我們稍微介紹了NFT的基礎概念,但相信有些人對於從生成式藝術到上架成為NFT的方法、規範是什麼感到好奇,這次直播就要來跟大家分享老闆在製作 Artblocks Project – Electriz 背後的理念與故事,以及介紹如何在生成式藝術平台上架p5.js演算法的技術細節!

Electriz ©Che Yu Wu
Electriz ©Che Yu Wu

Project Electriz

概念來自於模擬粒子在不同空間中的各種行為模式,並記錄電子移動所留下的軌跡。早期物理學家在偵測最小粒子時,會利用加速器撞擊兩顆不同的原子,試著擊破看看裡面還有無更小粒子的存在。在實驗進行的過程中為了看見這些迷你的粒子,科學家會利用一種叫雲室(Cloud Chamber)的儀器,當粒子經過雲霧時會凝結霧滴變成軌跡。

首張觀測到正電子存在的雲室照片 By Carl D. Anderson (1905–1991) - Anderson, Carl D. (1933). "The Positive Electron". Physical Review 43 (6): 491–494. DOI:10.1103/PhysRev.43.491., Public Domain, https://commons.wikimedia.org/w/index.php?curid=93111759
首張觀測到正電子存在的雲室照片 By Carl D. Anderson (1905–1991) – Anderson, Carl D. (1933). “The Positive Electron”. Physical Review 43 (6): 491–494. DOI:10.1103/PhysRev.43.491., Public Domain, https://commons.wikimedia.org/w/index.php?curid=93111759

作品的主要架構可以分為兩個部分—空間的切割與粒子的移動和軌跡保存。空間分佈可以分為兩種—長型與圓形,當粒子經過每個不同帶電的空間(正電或負電)或者干擾力場,便會出現左右或旋轉偏移等不同行為模式。

粒子偏移的不同方式:力場拖拉與正負電場
粒子偏移的不同方式:力場拖拉與正負電場

上架&測試作品

Artblocks 分為 staging environment 跟 official environment,staging 為測試的網站,藝術家可以先把作品上架到這裡看看渲染後的效果。跟一般上架的概念類似,在 Artblocks 的後台有很多的欄位需要填寫(作品名稱、介紹描述等),值得注意的是每更改一個欄位都需要付相對應的手續費,所以下好離手、想清楚再寫唷。

在後台有一個區域稱為「Script」,在這裡需要引用 Artblocks 認證過的函式庫,並上傳自己的程式碼到區塊鏈。程式碼會依照長度切分成不同的 chunck 來上傳,一個 chunck 差不多是1個 ETH,這樣的好處是比起只有靜態的圖片,包含程式碼的做法會讓作品的附加價值更高。

帶大家一窺Artblocks後台的廬山真面目,Script為上傳程式碼的地方
帶大家一窺Artblocks後台的廬山真面目,Script為上傳程式碼的地方

以上為介面介紹,而就程式面來說要從 p5.js 走到 Artblocks chuncks 最重要的就是—亂數的固定化。假設我們今天創作完了一個作品,想藉由這個架構產生 100 個 NFT,這時候很常用到 random 這個語法來亂數生成,但又希望確保每一次的 random 所得到的東西會是相同的。這邊提供由多個藝術家共同開發的演算法(參考 code RandomHash 的分頁),主要概念為透過 keccak 這個 function 取得hash,再複寫掉原先的 random,你就會得到全部的亂數都是固定數的作品囉,同時也可以確保作品和預覽圖的一致性與獨一性。

Template HashRandomLib
Template HashRandomLib

以前在測試作品時都要手動一個一個貼上 hash,這邊介紹一個簡單又好用的工具 Token Art Tools,他可以透過左邊的 scale 調整 hash 的組成並自動餵給 script,這樣就可以快速地看到作品在不同 hash 時的不同樣貌囉。Token Art Tools 有一個功能是回到上一動,這也就代表在隨機的過程中有機會記錄到偏好的作品的亂數,對於生程式藝術來說過往可能在挑選作品時是幾百張圖片挑一張、重製的可能性不高,透過這樣的方法我們有機會保留自己最喜歡的樣貌,至於「隨機」的原則與否,這點便見仁見智了。

Token Art Tools網站截圖
Token Art Tools網站截圖

NFT之於創作者

正如同所有快速爆紅的東西,NFT 勢必也會面對泡沫化的可能性,而在茫茫的 NFT 海中,藝術家們又該何去何從?或許這是所有類型的創作者都會面臨到的窘境,不論是實體或是數位,這些都是傳達理念的一種「手法」。特別是在手法多元化爆炸的時代,如何好好掌握「品牌」和「理念」就是一件很重要的事。老闆這邊分享了自己除了在 NFT 平台(如:Artblocks、Opensea)上的經營外,同時也有經營個人的 Discord Channel,不定時推播新作品上線等相關資訊,也針對支持新作品的老朋友,給予相對應的回饋,進而增加社群的黏著度。

這邊介紹一個藝術家 Tyler Hobbs @ Feral File,除了線上的作品以外,藝術家也會將自己的作品實體展出,近一步結合虛擬和實體的概念。類似的還有先前在 Oursong 平台推出自家 NFT 的師園鹹酥雞,透過購買香菇、四季豆等 NFT,可以在店面兌換相對應的實體物品。近期老闆也在策劃關於台灣藝術家的育成合作(Artist Incubator),主要會輔導藝術家進行作品的上架、協助媒合合適的NPO,有興趣的讀者歡迎關注粉專以獲取最新的第一手消息唷! (相關閱讀:首家 NFT 炸雞店!師園鹹酥雞 NFT 上架 OurSong 一天內漲幅超過 100 倍

Feral File – Exhibiting, Curating, and Collecting Digital Media

在直播中老闆也分享說其實在NFT的領域成名與否和作品的品質不見得有正相關,很多時候是需要一個被看到的機運,衍伸而來後續的曝光。雖然有一些運氣的成份存在,但身為創作者可以盡量完整自己在平台、網站上的作品,以建立「個人品牌」的方向去努力,等待成熟發酵的那一天。

以上是這次老闆週六來聊聊的內容,希望大家看完後也可以對於航行在NFT這塊汪洋藝術之海可以更有方向,直播影片請往這裡走,我們下次再見啦!👉🏻 https://www.youtube.com/watch?v=9kUJirQsS7M

歡迎加入互動藝術程式創作入門(Creative Coding)線上課程,課程中你可以認識程式與互動藝術產業應用,開啟對工程跟設計的想像,學會使用 p5.js 開發互動介面,整合繪圖、音訊、視訊、文字、3D、互動與機器創作完整的作品,並將創作輸出應用在個人品牌或網站、主視覺或海報,甚至互動裝置、遊戲與教材製作等場景,讓你對進修的資源與路線更有方向。

有興趣的朋友歡迎加入我們的臉書社團,第一時間接收活動報名消息,希望不久的將來,就能看到你跟大家分享你的生成式藝術創作囉!

此篇直播筆記由幫手 Jeudi Kuo 協助整理

墨雨設計banner

這篇文章 【老闆週六來聊聊】吳哲宇 Artblocks Project – Electriz 製作分享 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js創作教學】Sweet Trap 甜蜜陷阱 https://creativecoding.in/2022/04/25/p5-js-sweet-trap/ Mon, 25 Apr 2022 05:52:00 +0000 https://creativecoding.in/?p=2568 生成式藝術迷人的地方就在於它的程式邏輯、它的數學藝術呈現,有秩序卻又充滿了隨機。吳哲宇的<甜蜜陷阱>便是這樣有機的創作作品。此文帶大家一步步從 sin 波慢慢建構出目眩神迷的 p5.js 創作。

這篇文章 【p5.js創作教學】Sweet Trap 甜蜜陷阱 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
色彩繽紛的幾何形狀不斷輻合旋轉,細細的線條卻像是毒刺一樣。動態改變形狀一下密密麻麻一下稀疏鬆散,多變的風貌讓人甜蜜陶醉卻又像是陷阱般危險的感覺!

<甜蜜陷阱>成品圖
<甜蜜陷阱>成品圖

本文是【p5.js 程式創作直播】210731 Sweet Trap 甜蜜陷阱 的直播影片筆記,大家如果想要和老闆一起 chill 度過寫程式的時光,可以打開影片開啟這趟心流之旅,或者…繼續往下看!

這次直播是用 openprocessing 網頁平台來撰寫,打開網頁就可以開始 coding 創作了!成品在這裡。

直播時老闆聊到了設計的作品被剽竊的故事,但也因為這樣被 Art Blocks 平台看見。現在正是NFT藝術品百花齊放的時候,大家在這支影片中可以了解到生成式藝術迷人的地方,甚至開始創作自己的 NFT 。來吧,這次的作品運用到不少關於角度的概念,讓我們一起建立一個秩序又隨機的世界!

這次直播筆記會帶大家學會

  • 將三角函數的概念運用在極座標,透過計算角度來畫出花瓣狀的軌跡
  • 旋轉與移動座標系,簡單定位軌跡中的每個點
  • 利用存取滑鼠的座標,自由變化圖形的樣貌
  • 計算角度簡單繪製出三角形狀
  • 存下自己喜歡的色票並隨機呈現顏色,每一次播放都會產生不同顏色組合
  • 運用noise()製造出有規律的隨機

會使用到的 API

這次作品會使用以下的 API,大家可以先感受一下每個 API 的功能,還沒完全理解的話也沒關係,後續透過一步步實作會漸漸學會運用的。

  • createCanvas(width, height): 創建畫布,參數中分別傳入寬跟高。
  • background(colorCode): 加上背景色,可依照文件傳入色碼參數。
  • noStroke(): 取消繪製圖形的邊框。
  • colorMode(): 定義顏色的方式,預設為 RGB 顏色,HSB 模式依序要填入的值則為(色相, 飽和度, 明度)。
  • fill(): 選擇填入的顏色,依照 colorMode 選擇的填色模式填入對應的參數。
  • ellipse(posX, posY, width, height): 在 (posX, posY) 上繪製一個寬高(width, height)的橢圓形。
  • rect(x, y, width, height): 以 (x, y) 的位置為左上角的點,畫一個寬度 width 高度 height 的方形(如果要畫正方形的話,即寬度=高度)。
  • triangle(x1, y1, x2, y2, x3, y3):以三個頂點座標繪製出三角形。
  • text(str, x, y):在(x,y)座標呈現出文字str
  • rotate(angle): 將座標系依照該角度旋轉
  • translate(x, y): 將座標系移到(x,y) 上
  • push(): 儲存目前畫筆設定的狀態
  • pop(): 恢復畫筆在push()時儲存的狀態,與push()合併使用
  • random(): 沒有傳參數時,會返回一個0~1之間的隨機浮點數。
  • noise(x,[y],[z]): 產生0~1之間的浮點數。傳入的x,y,z代表座標,會在一、二、三維的Perlin noise噪聲空間取出對應該座標在0~1之間的值。這個方法會使得相近的座標取到的值也相近,比較有連續性,不會像random()每次取值都是完全隨機的。有興趣的同學也可以延伸閱讀相關資訊:2D Noise – Perlin Noise and p5.js Tutorial
  • map(value, start1, stop1, start2, stop2, [withinBounds]):會回傳某個位於start1~stop1範圍的值如果對應到start2~stop2範圍中是多少。最後一個參數的意義可以參考文件描述。
  • pow(n,e):計算n的e次方
  • blendMode(mode):讓圖形相互以不同的方式疊加色彩,有各種模式可以選擇,例如:DARKEST、LIGHTEST、DIFFERENCE等。
  • image(img, x, y, [width], [height]):以img材質在x,y座標畫出圖片。
  • pixelDensity(val):增加像素的密度,預設像素的密度是與螢幕相同。

跟著老闆開始動手做

1. 簡單的起手式

在 openprocessing 網頁右上角可以Create a Sketch,會來到一個已有預設程式碼的新頁面。從這裡開始我們來認識setup()、draw()與mouseX()、mouseY()。

  • setup(): 可以視為程式環境的初始化,在每次按下撥放鍵開始執行時,會呼叫 setup() 裡的程式碼一次。
    • createCanvas(width, height):創建畫布,參數中分別設定寬跟高(單位是px)。也可以直接寫(windowWidth, windowHeight),會自動判斷螢幕的寬高變為滿版畫布。
    • background(colorCode):設定背景顏色,依照 p5.js 文件說明傳入不同的色碼參數表示方式,這邊寫的 100 是代表 0(黑)~255(白) 之間的灰色值 100。
  • draw(): 在不按停止播放的狀況下,會不斷重複執行在 draw() 裡面的程式碼,要繪製的內容主要會寫在這裡。
    • ellipse(posX, posY, width, height):在 (posX, posY) 上繪製寬高 (width, height) 的橢圓形,如果 (posX, posY) 帶入 (mouseX,mouseY) ,表示取滑鼠的座標當作繪製圓圈的位置。
function setup() {
  createCanvas(windowWidth, windowHeight);
  background(100);
}
function draw() {
  ellipse(mouseX, mouseY, 20, 20);
}

2. 繪製sin波形

這次老闆從自己日常紀錄的創作靈感筆記中,選擇創作類似花的圖案,可以用 sin 波來實踐-想想 sin 波的形狀是不是很像一片片的花瓣?正式的說法是,我們將在極座標(0~360度)上畫出 sin波 ,下圖的 θ 是從 0~360 度,可以看到不同的算式真的會讓 sin 波變成花瓣呢!

變成花瓣的 sin 波
變成花瓣的 sin 波

我們就先從畫出一個正常的 sin 波開始吧!老闆喜歡在畫布上再畫一個黑色矩形當作背景,這個可以寫在setup()中畫一次就好。接著在 draw() 裡透過 for 迴圈,讓 x 由左到右,每隔 20 就畫一個白色的圓點來描繪 sin 波波形。以下是 API 的相關參數意義。

  • fill(colorCode):設定接下來要填入形狀的顏色,色碼0為黑色,色碼255為白色。
  • rect(0,0,width,height):width,height是兩個可以方便取用的變數,儲存曾在createCanvas(w,h)中設定的寬高值。矩形的繪製會以(0,0)為左上角頂點,往右為寬、往下為高畫出與畫布一樣大的矩形。
  • noStroke():設定接下來畫出的圖形沒有邊框。
  • translate(0,height/2):座標系是從整個畫布的左上角為原點(0,0),往右方x越大,往下方y越大,我們運用translate()把座標系的原點改到(0,height/2),接下來座標的計算都可以重新依這個新原點為準。
  • frameCount:從程式開始執行畫面不斷更新的次數,其實也就是draw()反覆執行的次數,所以frameCount是以固定的速度增加其數值。
  • ellipse(x,y, 50):在(x,y)畫出寬高皆為50的圓形。

y = sin(x) 可以繪製出 sin 波形,如果我們想要調整波形的樣貌,可以進一步運用不同的參數來調整!在這裡如果把它表達成 y=sin(x/a+b)*c 來思考,會發現 a 越大波長越長;而 b 如果是個變動的數字,程式反覆執行時,就會讓波上的點開始垂直動起來(不然會是靜止的)。老闆即運用 frameCount/100 來當作垂直運動的速度,大家可以試試看如果將 frameCount 除以10、50 會有什麼不同?另外由於 sin() 只會給出 -1~1 之間的值,因此可以乘以一個倍數 c 來控制 y 的高度,在這裡用的是 height/5。大家可以在 y=sin(x/a+b)*c 中試驗不同 的a、b、c 參數來創作你喜歡的 sin 波模樣喔!

function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
}
 
function draw() {   
  fill(255)
  noStroke()
  translate(0,height/2)
  for(var x=0;x<width;x+=20){
    let y=sin(x/10+frameCount/100)*height/5
    ellipse(x,y, 50)
  }
}
動態的 sin 波
動態的 sin 波

如果我們想要綜觀不同的參數設定會讓 sin 波長得如何不同,這時候可以好好運用滑鼠座標 mouseX、 mouseY 啦!老闆這邊想要觀察的是圓點取樣的多寡還有波的長短,因此利用 mouseX 由小到大的值對應為圓點取樣的間隔, mouseY 的大小則對應著波長的長短,並分別由變數 span、freq 把對應的值儲存下來。大家可以試驗滑鼠在不同的位置是如何影響波的樣貌?你也會發現很有趣的是當取樣的點(由 mouseX 決定)由多至少時,本身波長很短也會變得像波長很長的波,甚至看似多條 sin 波複合。

  • map(mouseX,0,width,0,100,true):將原先 mouseX 的值本來從 0~width 大小,對應到 1~100 之間,最後的 true 是當 mouseX 的值超出 0~width,也嚴格限制值落在 1~100 之間。
function setup() {  
  createCanvas(1000, 1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
}

function draw() {
  fill(0)
  rect(0,0,width,height)
   
  fill(255)
  noStroke()
  translate(0,height/2)
  let span = map(mouseX,0,width,1,100,true)
  let freq = map(mouseY,0,height,5,100,true)
  for(var x=0;x<width;x+=span){
    let y=sin(x/freq+frameCount/100)*height/5
    ellipse(x,y, 5)
  }
}
偵測滑鼠位置控制 sin 波疏密
偵測滑鼠位置控制 sin 波疏密

3. 把 sin 波轉到極座標上

為了要讓 sin 波變成花狀,我們要運用極座標,把 x 當成 0~360 度,sin(x) 的值當成長度。首先,先把座標系的原點改移到畫布中央 (width/2,height/2) 。接著很有趣的是,老闆不直接算出圓點的位置,而是再度移動座標系:讓座標系旋轉 x 度數再移動整個座標系讓原點移至 (sin(x),0) ,因此每一個圓形只要繪製在原點 (0,0) 上就好了!

座標系移動過後都要讓它回到原位再做下一次的移動,所以移動前都先用 push() 儲存目前的設定。每次旋轉+移動完座標系後,再透過 pop() 恢復原廠設定,下一次就又會從原先設定的狀態也就是座標系原點畫布中央開始!

  • push()、pop():前者存下當前的畫筆設定、後者恢復 push() 時儲存的設定。通常我們會把想要大動特動的畫筆設定寫在 push() 與 pop() 之間,執行完想繪製的東西後就能夠恢復成原本冷靜的狀態。
  • rotate(x/width*2*PI):座標系的旋轉。根據設定的 angleMode,可以填入弧度或是角度,為了避免搞混,我們使用在裡面填入弧度 PI。當 x 在 0~width 之間,x/width*2*PI 就是從 0~360 度的範圍。
function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
  ellipse(0,10, 15)
}

function draw() {
  //畫背景
  fill(0)
  rect(0,0,width,height)
  
  //畫圓圈 
  fill(255)
  noStroke()
  translate(width/2,height/2) //將原點設定到畫面中央
  rect(0,0,50,50) //畫個矩形確認座標系原點是否移到畫布中央

  let span = map(mouseX,0,width,1,100,true)
  let freq = map(mouseY,0,height,5,100,true)
  for(var x=0;x<width;x+=span){
    push()
      rotate(x/width*2*PI) //把座標系旋轉到0-360度之間
      let y=sin(x/freq+frameCount/100)*height/2 //藉由height/2讓波幅是畫面的一半高
      translate(y,0) //把旋轉過座標系在X軸上移動y個距離
      ellipse(0,0,10)
    pop()
  }
}

4. 來幫圖形上色吧

上色時老闆喜歡運用 coolors 這個網站挑選喜歡的配色,可以用空白鍵隨選5個顏色的搭配,也可以鎖住喜歡的顏色、繼續點空白鍵直到找到五個最喜歡的顏色搭配為止。每個顏色條裡也有一些提供調整的選擇。小撇步是當你決定好時,可以複製上方的代表顏色的字碼回到程式世界喔!

老闆想嘗試看看不同的視覺效果,將原先的圓形改為方形。接著指定一個變數 colors 來儲存這串字碼,並用程式將一個個色碼分開後,將每個色碼前面加上「#」成為完整的表示,例如:#1be7ff,#6eeb83。

為了讓每個方形輪流上不同的顏色,採用取餘數的方式:colors[int(x%colors.length)],可以讓餘數落在 0~colors.length-1,對應到 colors 陣列裡的各個色碼,在這裡外面包了一層 int() 是因為有時候j avascript 餘數運算出來是浮點數,因此要讓它強制取整。

//指定一個色票陣列
var colors = "1be7ff-6eeb83-e4ff1a-ffb800-ff5714-DB4D6D".split("-").map(a=>"#"+a)

function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
}

function draw() {
  //畫背景
  fill(0)
  rect(0,0,width,height)
  
  //畫圈圈 
  fill(255)
  noStroke()
  translate(width/2,height/2) 
  rect(0,0,50,50) 
  
  let span = map(mouseX,0,width,1,100,true)
  let freq = map(mouseY,0,height,5,100,true)
  for(var x=0;x<width;x+=span){
    push()
      fill(colors[int(x%colors.length)]) //選取色票陣列裡的特定顏色
      rotate(x/width*2*PI) 
      let y=sin(x/freq+frameCount/100)*height/2
      translate(y,0)
      rect(0,0,50)
    pop()
  }
}
<甜蜜陷阱>步驟四:加入顏色
<甜蜜陷阱>步驟四:加入顏色

記得中途若是做到喜歡的圖樣,可以自訂範圍截圖存取(mac:command+shift+4、window:win+shift+s),如果要將程式碼階段性保存起來,在 openprocessing 右上角有樹枝狀的按鈕 fork,就可以再複製一個出來繼續往下做喔!

5. 用圓形、方形、三角形來豐富

x 是我們畫每個點的依據,現在如果要讓每個位置可以分別呈現圓形、方形、三角形可以怎麼做呢?老闆是運用 x 除以3(代表 3 種形狀的餘數)與 if 條件式來實現,藉由餘數 0、1、2 分別對應到繪製不同的形狀。但在這裡還有一個關於繪製正三角形的挑戰:如果直接去計算 triangle(x1, y1, x2, y2, x3, y3) 的每一點座標是有些困難的,於是我們用三角函數的方式來計算。看著下圖我們可以看到透過角度 0、120、240 度,可以取得頂點的 x、y 座標 (r*cos(θ),r*sin(θ))。

var colors = "1be7ff-6eeb83-e4ff1a-ffb800-ff5714-DB4D6D".split("-").map(a=>"#"+a)
function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
}
 
//將製作三角形定義成一支function可以隨時在draw()裡呼叫取用
function myTriangle(x,y,r){      //function可以設定想定義的參數
  push()
    translate(x,y)
    let points=[] //存放三角形的頂點座標
    for(var i=0;i<3;i++){   
      let rr =r
      let angle=i*120
      let xx = rr*cos(angle/360*2*PI) //將角度數值轉換為角度
      let yy = rr*sin(angle/360*2*PI)
      points.push(xx,yy)	//將各頂點座標依序放入points陣列
    }
    triangle(...points) //用ES6語法...展開points從一陣列變成個別的6個值
  pop()
}

function draw() {
  //畫背景
  fill(0)
  rect(0,0,width,height)
  
  //畫圈圈 
  fill(255)
  noStroke()
  translate(width/2,height/2)
  rect(0,0,50,50) 
 
  let span = map(mouseX,0,width,1,100,true)
  let freq = map(mouseY,0,height,5,100,true)
  for(var x=0;x<width;x+=span){
    push()
      fill(colors[int(x%colors.length)])
      rotate(x/width*2*PI) //把座標系旋轉到0-360度之間
      let y=sin(x/freq+frameCount/100)*height/3
      translate(y,0) //把旋轉過座標系在X軸上移動y個距離
      
      let shapeId = int(x)%3
      if(shapeId == 0){
        rect(0,0,50)
      }
      if(shapeId == 1){
        ellipse(0,0,50)
      }
      if(shapeId == 2){
        myTriangle(0,0,50)
      }
    pop()
  }
}
<甜蜜陷阱>步驟五:用不同的幾何圖形豐富圖面
<甜蜜陷阱>步驟五:用不同的幾何圖形豐富圖面

6. 妝點-陰影、材質

 再來老闆使出自己愛用的方法,給予圖樣更豐富的變化。一開始嘗試陰影效果,有兩種陰影製作的方式可以選擇,除了陰影的顏色要設定外,分別結合陰影模糊程度的設定、陰影偏離物體多少。

  • drawingContext:HTML5 Canvas的功能可以用這個API取得。
    • drawingContext.shadowBlur:設定陰影模糊的程度
    • drawingContext.shadowColor:設定陰影的顏色
    • drawingContext.shadowOffsetX:設定陰影偏離物體多少x距離
    • drawingContext.shadowOffsetY:設定陰影偏離物體多少y距離
var colors = "1be7ff-6eeb83-e4ff1a-ffb800-ff5714-DB4D6D".split("-").map(a=>"#"+a)
function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
  ellipse(0,10, 15)

  //選擇一
  drawingContext.shadowBlur=5 
  drawingContext.shadowColor = color(0,100)//透明度0-255

  //選擇二,drawingContext.shadowBlur很當時可以使用
  drawingContext.shadowColor = color(0,100)//透明度0-255
  drawingContext.shadowOffsetX = 10
  drawingContext.shadowOffsetY = 10
}
<甜蜜陷阱>步驟六:加上陰影
<甜蜜陷阱>步驟六:加上陰影

如果想要加入材質感,可以學習製作一塊材質圖樣,再把材質圖樣疊加到畫布中。在這裡老闆設計的是噪點感的材質,噪點由許多深淺不一的灰階值組成。在 p5.js 裡可以透過指定一個變數製作出空白圖樣範圍,把圖樣像素化後可以用 for 迴圈指定每一個像素要畫什麼顏色。在這裡顏色的設定利用了 noise() 產生較有規律的 0~1 數值、random([a,b,c]) 決定 noise() 值放大的倍率來設定顏色的透明度。大家也可以試試在 noise() 傳入不同的參數、random() 陣列裡設定不同的倍率來製作不同的噪點感。製作好材質後可以選擇特定的疊加方法繪製出圖片。

  • createGraphics(width,height):設定一塊圖樣,傳入想要的寬高大小。
  • loadPixels():將圖樣的像素傳到 pixels[] 陣列,後續才可以讀取或者寫入想要的圖樣。
  • updatePixels():在設定完每一個像素的顏色後,可以用這個 api 更新成為新圖樣。
  • color(gray, [alpha]):第一個參數代表 0~255 的灰階值,第二個參數代表透明度。
  • noise(x,[y],[z]): 根據傳入的座標產生 0~1 之間浮點數,傳入的座標值越相近,產生出的浮點數會較有規律,不會變動很大。
  • random([array]):如果沒有特別傳入參數,random() 會返回0~1之間的浮點數,如果有寫明一個陣列,則每次會隨機在陣列裡挑選一個元素返回。
  • image(img, x, y, [width], [height]):在特定座標繪製出圖片。
var colors = "1be7ff-6eeb83-e4ff1a-ffb800-ff5714-DB4D6D".split("-").map(a=>"#"+a)
let overallTexture
function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
  
  //選擇二,drawingContext.shadowBlur很當時可以使用
  drawingContext.shadowColor = color(0,100)//透明度0-255
  drawingContext.shadowOffsetX = 10
  drawingContext.shadowOffsetY = 10
  
  //製作噪點材質
  overAllTexture=createGraphics(width,height)
  overAllTexture.loadPixels()
 
  // noStroke()
  for(var i=0;i<width+50;i++){
    for(var o=0;o<height+50;o++){
      overAllTexture.set(i,o,color(150,noise(i/10,i*o/300)*random([50,100,200])))  
      //每一個像素指定特定的顏色
      //如果將random的值改小材質就不會太黑太明顯
    }
  }  
  overAllTexture.updatePixels()
}
 
function draw() {
  //畫背景
  fill(0)
  rect(0,0,width,height)
  
  push()
    //畫圈圈 
    fill(255)
    noStroke()
    translate(width/2,height/2)
    rect(0,0,50,50) 
 
    //製作三角形的函式
    function myTriangle(x,y,r){
      let points=[] 
      for(var i=0;i<3;i++){   
        let rr =r
        let angle=i*120
        let xx = rr*cos(angle/360*2*PI) 
        let yy = rr*sin(angle/360*2*PI)
        points.push(xx,yy)
      }
      triangle(...points) 
    }

    let span = map(mouseX,0,width,1,100,true)
    let freq = map(mouseY,0,height,5,100,true)
    for(var x=0;x<width;x+=span){
      push()
        fill(colors[int(x%colors.length)])
        rotate(x/width*2*PI) 
        let y=sin(x/freq+frameCount/100)*height/2
        anslate(y,0)

        let shapeId = int(x)%3
        if(shapeId == 0){
          rect(0,0,50)
        }
        if(shapeId == 1){
          ellipse(0,0,50)
        }
        if(shapeId == 2){
          myTriangle(0,0,50)
        }
      pop()
    }
  pop()

  //將噪點材質疊加到畫布上
  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0) //
  pop()
}
<甜蜜陷阱>步驟六:加上材質

7. 讓圖形大小變化與自轉、長出刺與小圓點

為了讓整個互動的畫面更豐富有變化,老闆運用sin() 來設定形狀的大小。此外,形狀們除了不斷輻合到畫面中央外,也用rotate() 讓它開始自轉,並且形狀上、周圍加上一些裝飾:看起來像是刺的長短不一的線條、修改利用前面製作三角形的函式讓每個形狀的周圍環繞三個小圓形。

for(var x=0;x<width;x+=span){
  push()
    fill(colors[int(x%colors.length)])
    rotate(x/width*2*PI) 
    let y=sin(x/freq+frameCount/100)*height/3
    translate(y,0) 
    let shapeId = int(x)%3
	
    let rr= sin(x)*80 //讓每個圖形大小變化
    rotate(frameCount/50) //讓每個圖形自轉 


    if(shapeId == 0){
      rect(0,0,rr)
    }
    if(shapeId == 1){
      ellipse(0,0,rr)
    }
    if(shapeId == 2){
      myTriangle(0,0,rr)
    }

    //畫上刺
    strokeWeight(3)
    stroke(255)
    line(0,0,-rr,-rr) //線條與圖形用的是同一個座標系設定

    //畫環繞的圓形
    for(var i=0;i<3;i++){   
      noStroke()
      let rr =50
      let angle=i*120
      let xx = rr*cos(angle/360*2*PI) 
      let yy = rr*sin(angle/360*2*PI)
      ellipse(xx,yy,5)
    }
  pop()
}
<甜蜜陷阱>步驟七:讓圖形大小變化與自轉、長出刺與小圓點
<甜蜜陷阱>步驟七:讓圖形大小變化與自轉、長出刺與小圓點

8. 製作網格背景

再來我們要來製作現代感的網格背景,因此在座標系設定到畫面中央後,我們新增一段程式碼,設定線條的顏色並分別畫上水平線條與垂直線條。這裡老闆運用了取餘數,讓線條每 5 條就增強它的粗度與變得更明顯(調整透明度),這邊也用到了一些 javascript 的數學與邏輯表示方式。

  • abs():取絕對值
  • boolean?a:b:如果前面的變數 boolean 值是 true,就返回 a 值;是 false,就返回 b 值
translate(width/2,height/2) //將原點設定到畫面中央
			
 //畫網格線
stroke(255,100)
for(let xx=-width/2;xx<width/2;xx+=40){
let isSpan = (abs(xx/20)%5==0)
  strokeWeight(isSpan?3:1)
  stroke(255,20+isSpan?200:0)
  line(xx,-height/2,xx,height/2)
  }
		
for(let yy=-height/2;yy<height/2;yy+=40){
  let isSpan = (abs(yy/20)%5==0)
  strokeWeight(isSpan?3:1)
  stroke(255,20+isSpan?200:0)
  line(-width/2,yy,width/2,yy)
}
noStroke()
<甜蜜陷阱>步驟八:製作網格背景
<甜蜜陷阱>步驟八:製作網格背景

9. 隨機選取顏色子集合、印出文字

為了讓圖樣在程式每次開始執行時都可以選取不同的顏色來繪製,老闆運用隨機的概念,讓每個在顏色陣列裡的色碼,會透過機率的方式決定會不會被選到。為了避免所有顏色都未能被選入,也預先儲存一些絕對會畫上去的顏色。

var colors = "1be7ff-6eeb83-e4ff1a-ffb800-ff5714-DB4D6D".split("-").map(a=>"#"+a)
var useColors = ['#000','#fff'] //真正用於著色的陣列,可以預先填入一些顏色
let overallTexture
function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
  ellipse(0,10, 15)           
         
  //繪製陰影
  drawingContext.shadowColor = color(0,100)//透明度0-255
  drawingContext.shadowOffsetX = 10
  drawingContext.shadowOffsetY = 10

  //製作噪點材質
  overAllTexture=createGraphics(width,height)
  overAllTexture.loadPixels()
 
  // noStroke()
  for(var i=0;i<width+50;i++){
    for(var o=0;o<height+50;o++){
      overAllTexture.set(i,o,color(150,noise(i/10,i*o/300)*random([50,100,200])))  
      //每一個像素指定特定的顏色
      //如果將random的值改小材質就不會太黑太明顯
    }
  }
  overAllTexture.updatePixels()

  //顏色子集合
  colors = colors.concat(colors) //隨機條件若很嚴格可以藉由讓顏色陣列複製自己,增加顏色被選到的機率
  randomSeed(Date.now()) //讓隨機依據變動的數字(如用當下的時間)會更隨機
  colors.forEach(clr=>{ //對於顏色陣列裡的每個顏色(設定變數clr)會逐一的執行{}裡的指令
    if(random()<0.25){ //當random()的值小於某數時才會執行
      useColors.push(clr)  //執行將某色存入useColors陣列
    }
  })
}

別忘了要將填色的部分改選用useColors陣列喔!

function draw(){
  ...
  for(var x=0;x<width;x+=span){
    push()
      fill(useColors[int(x%useColors.length)]) 
      rotate(x/width*2*PI) 
      let y=sin(x/freq+frameCount/100)*height/3 				
      translate(y,0) 
      let shapeId = int(x)%3
    pop()
  }
}

再來可以在畫面上以文字呈現一些參數是如何變化,讓作品看起來很有科幻系統的感覺。為了讓形狀都會有陰影但文字不會,將原本在 setup() 關於陰影的設定搬到 draw(),但在要繪製文字之前將陰影設定取消。這邊很有趣的是,老闆還繪製出了填色矩形記錄每次圖樣是由哪幾個顏色構成。

  • text(str, x, y):在 (x,y) 座標呈現出文字
function draw() {	
  drawingContext.shadowColor = color(0,200)//透明度0-255
  drawingContext.shadowOffsetX = 10
  drawingContext.shadowOffsetY = 10

  //畫背景
  fill(0)
  rect(0,0,width,height)
    
  push()         
    // blendMode(SCREEN)
    //畫圈圈 
    fill(255)
    noStroke()
    translate(width/2,height/2) //將原點設定到畫面中央
			
    //畫網格線
    stroke(255,100)
    for(let xx=-width/2;xx<width/2;xx+=40){ //
      let isSpan = (abs(xx/20)%5==0)
      strokeWeight(isSpan?3:1)
      stroke(255,20+isSpan?200:0)
      line(xx,-height/2,xx,height/2)
    }
		
    for(let yy=-height/2;yy<height/2;yy+=40){ //
      let isSpan = (abs(yy/20)%5==0)
      strokeWeight(isSpan?3:1)
      stroke(255,20+isSpan?200:0)
      line(-width/2,yy,width/2,yy)
    }
    noStroke()
		
			
    //畫形狀
    let span = map(mouseX,0,width,1,100,true)
    let freq = map(mouseY,0,height,1,100,true)
    let curveFactor = noise(frameCount/1000)*3+5 
    for(var x=0;x<width;x+=span){
      push()
        fill(useColors[int(x%useColors.length)])
        rotate(x/width*2*PI) 
        let y=sin(x/freq+frameCount/100)*height/2 
        translate(y,0) //把旋轉過的X軸上移y個距離
        let shapeId = int(x)%3
				
        let rr=(pow(noise(x),2)+pow(sin(x),1.2))*100 //製作大小不一的形狀
        rotate(frameCount/50)//自轉 
				
        if(shapeId == 0){
          rect(0,0,rr)
        }
        if(shapeId == 1){
          ellipse(0,0,rr)
        }
        if(shapeId == 2){
          myTriangle(0,0,rr)
        }
        strokeWeight(3)
        stroke(255)
        line(0,0,-rr,-rr)
				
        //環繞的小圓形
        for(var i=0;i<3;i++){  
          noStroke()
          let rr =50
          // let cirR =10 *sin(x)
          let cirR =10
          let angle=i*120+frameCount/100+x*curveFactor//?
          let xx = rr*cos(angle/360*2*PI) //將角度數值轉換為角度
          let yy = rr*sin(angle/360*2*PI)
          ellipse(xx,yy,cirR)
        }			
      pop()
    }
  pop()     
		
    //為了寫文字取消陰影
    drawingContext.shadowColor = color(0,200)//透明度0-255
    drawingContext.shadowOffsetX = 0
    drawingContext.shadowOffsetY = 0
		
  push()
    
    for(var colorId = 0;colorId<useColors.length;colorId++){
      fill(useColors[colorId])
      strokeWeight(2)
      rect(colorId*40+40,height-210,30,30) //注意這裡的座標系原點是以左上
                                        //角(0,0)計算,每個方形間隔40,寬高30
    }
    fill(255) //字體設定白色
    textSize(24)
    textStyle(BOLD)
    text("TIME: "+frameCount+"fp",50,height-130)
    text("SPAN: "+span.toFixed(2)+"\"",50,height-90) //這裡值得注意為了要顯示”
                                                     //需要在前面加一條\方便程式辨識喔
    text("FREQ: "+span.toFixed(2)+"Hz",50,height-50)
  pop()
		
  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
  pop()
}
<甜蜜陷阱>步驟九:隨機選取顏色組合,並在圖的左下角新增文字
<甜蜜陷阱>步驟九:隨機選取顏色組合,並在圖的左下角新增文字

10. 最後一點小調整!

為了讓很多東西不要只隨著 sin 變化,減少單調以及增加更多的韻律,例如小圓形原本只會跟著大圓形、方形、三角形一起同週期旋轉,為了讓它有自己的旋轉,加上了 frameCount/100,再透過加上 x*a(a 代表一個設定的倍數),讓每個位置上的三個小圓形都有不同的偏轉角度,看起來就像是扭轉纏繞的模樣。另外,利用 noise() 讓本來只會隨著 sin(x) 值規律變大變小的形狀可以增加一點隨機的變化,合併使用 pow() 次方的相乘讓值更極端。

我們可以在 setup() 設定一開始滑鼠的位置來規範一開始執行程式時就出現想要的圖樣,最後方便儲存圖片可使用 mousePressed() 偵測滑鼠點按事件的發生並以 save() 存下圖片。

  • pow(n,e):n的e次方。
  • save():存取當前畫面。
var colors = "1be7ff-6eeb83-e4ff1a-ffb800-ff5714-DB4D6D".split("-").map(a=>"#"+a)
var useColors =["#000","#fff"]
let overAllTexture

function mousePressed(){ //偵測滑鼠點按
  save()   //儲存畫面
}

function setup() {
  colors = colors.concat(colors)
	
  createCanvas(1000,1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
  pixelDensity(2)     //增加像素密度
  // drawingContext.shadowBlur=5
	
  randomSeed(Date.now())
  mouseX = random(1,width/10) //設定一開始的滑鼠座標
  mouseY = random(1,height/2) //設定一開始的滑鼠座標
	
  colors.forEach(clr=>{
    if (random()<0.25){
      useColors.push(clr)
    }
  })
	
  overAllTexture=createGraphics(width,height)
  overAllTexture.loadPixels()
  // noprotect
  // noStroke()
  for(var i=0;i<width+50;i++){
    for(var o=0;o<height+50;o++){
      overAllTexture.set(i,o,color(150,noise(i/10,i*o/300)*random([0,0,0,80,200]))) 
      //可以透過在陣列裡複製多一點某個值讓它被隨選到的機率增加
    }
  }
  overAllTexture.updatePixels()
}

function myTriangle(x,y,r){
  push()
    translate(x,y)
    let points = []
    for(var i=0;i<3;i++){
      let rr = r
      let angle =i*120
      let xx = rr* cos(angle/360*2*PI)
      let yy = rr* sin(angle/360*2*PI)
      points.push(xx,yy)
    }
    triangle(...points)
  pop()
}

function draw() {
  drawingContext.shadowColor=color(0,200)
  drawingContext.shadowOffsetX=10
  drawingContext.shadowOffsetY=10

  // print(mouseX,mouseY)
  //畫背景
  fill("#000")
  rect(0,0,width,height)
  // push()
  //  fill(0,0.1)
  //  rect(0,0,width,height)
  // pop()

  push()
    // blendMode(SCREEN)
    //畫圈圈
    fill(255)
    noStroke()

    //translate to center
    translate(width/2,height/2)

    stroke(255,100)
    for(let xx=-width/2;xx<width/2;xx+=40){
      let isSpan = (abs(xx/20)%5==0?150:0) 
      strokeWeight(isSpan?2:1)
      stroke(255,20+ isSpan?100:0)
      line(xx,-height/2,xx,height/2)
    }

    for(let yy=-height/2;yy<height/2;yy+=40){
      let isSpan = (abs(yy/20)%5==0?150:0) 
      strokeWeight(isSpan?2:1)
      stroke(255,20+ isSpan?100:0)
      line(-width/2,yy,width/2,yy)
    }
    noStroke()

    // rect(0,0,50,50)
    let span = map(mouseX,0,width,1,10,true)
    // print(span)
    let freq = map(mouseY,0,height,1,100,true)
    let curveFactor = noise(frameCount/1000)*3+5 //小圓形扭轉的程度
    for(var x=0;x<width;x+=span){
      push()
        fill(useColors[int(x%useColors.length)])
        rotate(x/width*2*PI)
        let y = sin(x/freq+frameCount/100)*height/2
        translate(y,0)
        let shapeId = int(x)%3
        let rr = ( pow(noise(x),2)+ pow(sin(x),1.2))*80 //讓形狀大小變化度更大
        rotate(frameCount/50)
        if (shapeId==0){
          rect(0,0,rr)
        }
        if (shapeId==1){
          ellipse(0,0,rr)
        }
        if (shapeId==2){
          myTriangle(0,0,rr)
        }
        strokeWeight(3)
        stroke(255)
        line(0,0,-rr,-rr)

        for(var i=0;i<3;i++){
          noStroke()
          let rr = 50
          let cirR = 10
          let angle =i*120+frameCount/100 + x*curveFactor //製造三個小圓形第二層旋轉、不同位置的三個小圓形偏轉不同角度
          let xx = rr* cos(angle/360*2*PI)
          let yy = rr* sin(angle/360*2*PI)
          ellipse(xx,yy,cirR)
        }
        // ellipse(0,0,50)
      pop()
    }
  pop()

  //把陰影取消掉
  drawingContext.shadowColor=color(0,200)
  drawingContext.shadowOffsetX=0
  drawingContext.shadowOffsetY=0

  push()
    textSize(24)
    textStyle(BOLD);
    for(var colorId =0;colorId<useColors.length;colorId++){
      fill(useColors[colorId])
      strokeWeight(2)
      rect(colorId*40+40,height-210,30,30)
    }
    fill(255)
    text("TIME: "+frameCount+ "fp", 50,height-130)
    text("SPAN: "+span.toFixed(2) + "\"", 50,height-90)
    text("FREQ: "+freq.toFixed(2) + "Hz", 50,height-50)
  pop()
  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
  pop()
}
<甜蜜陷阱>成品圖
<甜蜜陷阱>成品圖

老闆來結語

再次附上這次範例的成品<甜蜜陷阱>讓大家在開發時參考。這次的創作是從一個點子開始慢慢精修,一邊做一邊調整,讓我們快速回顧一下甜蜜陷阱的創作過程:

  • 了解 openprocessing 創作的起手式 – setup() 與 draw()
  • 運用滑鼠座標來動態改變sin波的樣貌
  • 運用旋轉與移動座標系來繪製花狀波形
  • 上色與變化形狀
  • 加入陰影、噪點材質
  • 調整形狀大小
  • 自轉、毒刺與環繞的小圓形
  • 加上網格背景與文字
  • 最後的調整修飾

這部影片結合了許多好用的數學概念與 API,讓我們可以邏輯化的選取或製作特定效果。在寫程式時也可以善用註解,比較好區塊化地理解與管理每一段程式影響了畫面哪些部分。大家會發現老闆在過程中會不斷微調參數值或是回頭修改使用的 API 試驗不同的效果,這也是創作磨人卻有趣的地方,大家一起探索與試驗吧!

如果你喜歡老闆的教學,《互動藝術程式創作入門》課程中也有手把手的實作引導。寫程式製作生成藝術世紀是一趟需要精準又沿路充滿驚喜的旅程,需要腦瓜裡有彈性的空間-細心規劃,但也放膽試驗、歡迎意外!

此篇直播筆記由幫手 C 協助整理

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 【p5.js創作教學】Sweet Trap 甜蜜陷阱 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js創作教學】Coral Carnival 珊瑚嘉年華 https://creativecoding.in/2022/04/07/p5-js-coral-carnival/ Thu, 07 Apr 2022 06:27:00 +0000 https://creativecoding.in/?p=2092 藝術創作的有趣之處在於它有無數可能與千萬種解讀,閱讀吳哲宇在創作<珊瑚嘉年華>這件作品時的逐步調整,以及一些大膽實驗的意外收穫,慢慢塑造成一件又一件的 p5.js 生成式藝術作品。

這篇文章 【p5.js創作教學】Coral Carnival 珊瑚嘉年華 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
前言

乘著生成式藝術的浪潮,讓我們一同體驗<珊瑚嘉年華>這件藝術作品的創作過程。使用 p5.js 的技術創作出絢爛夢幻、隨波蕩漾的珊瑚,看著它在海底深處搖曳擺盪,在暗夜中充滿生命力地熠熠生輝。

開場

這一次分享的內容比較特別,除了紀錄如何使用 P5.js 創作藝術之外,同時也記錄生成式藝術的創作過程。從初步的概念發想、創作中的顏色/動態/細節調整嘗試,一路到完成作品後的圖片保存處理、文案構思與撰寫,帶領大家一同體會藝術創作的趣味。

<珊瑚嘉年華>作品完成截圖
<珊瑚嘉年華>作品完成截圖

這次直播筆記會帶大家學會以下內容:

  • 瞭解一幅生成式藝術作品的完整創作過程
  • 使用 coolors 快速選擇色票並進行色彩搭配
  • 使用 frameCount() 在作品中加入逐幀動態效果,賦予作品生命力

事前準備

  1. 開發環境: 本次開發會使用 openprocessing 線上撰寫程式碼,如果想知道較詳細的設定,可以到老闆作品的 成品 查看相關的設定。
  2. 本次範例使用到的 API:

步驟講解

一、初步靈感發想

最初的靈感發想可以從自己喜歡的事物著手,像是老闆喜歡鋼琴、琴譜、植物,因此這次腦海中的雛型是「V字型、輻射狀、往外擴張」的圖案。

<珊瑚嘉年華>作品步驟一:靈感發想
<珊瑚嘉年華>作品步驟一:靈感發想

有了初步的想法後,接著老闆便參考自己在 Pinterest 上蒐集的各種海報、平面設計、生成式藝術等等,看它們的配色、使用的材質、文字樣式、3D等等,給自己更多作品靈感。此外,老闆常用的手法還有使用合適的材質跟顏色疊色,能快速地提升作品質感。

二、Sketch 起手式

使用 openprocessing 開啟一個新的 Sketch 之後,可以看到程式碼頁面已經有一段預設的程式碼:隨著滑鼠的移動,會沿路產生小球。

  • setup(): 環境建立/初始化,只在開始執行的當下會呼叫一次。以下的程式碼使用了兩個 API
    • createCanvas(width, height):創建畫布,參數中分別傳入寬跟高,如果直接寫螢幕的寬高( windowWidth, windowHeight),就會成為滿版的互動區塊。但老闆在創作<每日生成式藝術>系列時,習慣把寬高設為 (1000,1000) ,有一個比較固定的格式。
    • background(colorCode):加上背景色,可依照文件傳入色碼參數。
  • draw(): 會以每秒 60 次的循環重新呼叫並進行裡面的程式碼,想製作互動效果都可以在這個 function 中呼叫。
    • ellipse(posX, posY, width, height):在 (posX, posY) 上繪製一個寬高(width, height)的橢圓形,這邊用 (mouseX, mouseY) 的話便是隨著滑鼠移動的軌跡產生橢圓形
function setup() {
  createCanvas(1000,1000);
  background(100);
}

function draw() {
  ellipse(mouseX, mouseY, 200, 200);
}

三、繪製 V 圖案,複製並旋轉為輻射狀圖形

接下來,根據原本的靈感先畫出一串 V 型圖案,然後旋轉複製這些圖案。在開始前,先用 background()、fill()、rect() 這三個 API 將背景設為畫框的樣式。接著到中心點開始構圖,使用兩個長方形去組合成V型。要注意的是,只要有用到旋轉相關的API,都要記得 push() 跟 pop(),不然會影響到後續的程式碼。

  • background():設定背景顏色。
  • fill():選擇填入的顏色。
  • rect(x, y, width, height):在 (x, y) 的位置畫一個寬度 width、高度為 height 的方形。
  • translate(x,y):將畫筆移動到 x, y 的位置。
  • push():紀錄目前畫筆狀態。
  • pop():恢復畫筆狀態。
  • rotate(angle:依照傳入的參數進行旋轉。
  • PI():180度角。
  • rectMode(mode):設定四方型從什麼地方開始繪製。模式分別有 CENTER、CORNER、RADIUS 三種,注意模式的字必須是大寫。
function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0);
  rect(0,0,width,height);
  rectMode(CENTER);
}

function draw() {
  translate(width/2, height/2);
  fill(255);
  noStroke();
	
  push();
    rotate(-PI/4*3);
    rect(75,0,200,50);
    rotate(PI/4*2);
    rect(75,0,200,50);
  pop();
}
<珊瑚嘉年華>作品步驟三:從簡單的圖樣開始畫起
<珊瑚嘉年華>作品步驟三:從簡單的圖樣開始畫起

組合出 V 型後,接著就用迴圈來複製它,然後加上 translate() 進行角度偏移,讓它每繪完一次圖案就往上移動一點再繼續繪製,這樣就能得到一串V的圖形。在迴圈外面要記得包一層 push()、pop(),讓它不影響到後續程式。

function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0);
  rect(0,0 ,width,height);
  rectMode(CENTER);
}

function draw() {
  translate(width/2, height/2);
  fill(255);
  noStroke();
	
  push();
    for(let i=0;i<10;i++){
      translate(0,-45);
      push();
        scale(0.5)
        rotate(-PI/4*3);
        rect(75,0,200,50);
        rotate(PI/4*2);
        rect(75,0,200,50);
      pop();
    }
  pop();
}
<珊瑚嘉年華>作品步驟三:利用迴圈快速大量複製簡單的圖樣
<珊瑚嘉年華>作品步驟三:利用迴圈快速大量複製簡單的圖樣

四、進行配色、旋轉與各種細節嘗試

基本的圖形完成之後,接下來就可以進行各種不同的嘗試與色彩搭配。老闆在這邊嘗試了:

  • 迴圈數增多:增加迴圈的數量、搭配 scale() 把圖形縮小、降低 translate() 數值讓圖形排列緊密。
  • 圖形旋轉:使用 rotate() 讓圖形旋轉。rotate()的參數除了代入數字,也可以帶入 sin(i) 之類的,嘗試不同的數值帶來的變化;或甚至可以代入rotate(sin(i/(mouseY/100))),讓圖形隨著滑鼠移動的角度旋轉,然後記得加個 background(0) 把背景軌跡清掉 (有些作品也會留下軌跡,但這邊先不用)
  • 色彩搭配:可以從蒐集的海報作品中找搭配靈感,或是 coolors 網站提供的色票搭配來快速搭配出喜歡的顏色。

找到喜歡的色票之後,可以直接從 coolors 的網址上複製色票的數值。

在 Coolors 找到喜愛的色票組合後複製
在 Coolors 找到喜愛的色票組合後複製

但複製完後,還需要先把色票轉成陣列的色彩,才能在P5.js 中使用。因此這邊會需要用到 JS 的語法 split() 與 map() 把色票轉成色彩字串的陣列。

const colors = 'cacf85-8cba80-658e9c-4d5382-514663'.split('-').map(a=>`#${a}`);

完成後就能把我們的圖形加入色彩啦!

const colors = 'cacf85-8cba80-658e9c-4d5382-514663'.split('-').map(a=>`#${a}`);

function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0);
  rect(0,0 ,width,height);
  rectMode(CENTER);
}

function draw() {
  translate(width/2, height/2);
  fill(255);
  noStroke();
  background(0); // 清除軌跡
	
  push();
    for(let i=0;i<20;i++){
      translate(0,-45);
      rotate(sin(i/(mouseX/100))); // 旋轉角度跟隨滑鼠移動
      fill(colors[i%colors.length]);
      push();
        scale(0.5)
        rotate(-PI/4*3);
        rect(75,0,200,50);
        rotate(PI/4*2);
        rect(75,0,200,50);
      pop();
    }
  pop();
}
<珊瑚嘉年華>作品步驟四:加上色彩、旋轉等變化
<珊瑚嘉年華>作品步驟四:加上色彩、旋轉等變化

五、構成萬花筒

只有一條太單調了,接下來一樣使用迴圈跟旋轉的方法,把圖形變成類似萬花筒的形狀吧!這樣一來就達成原本的構想 — 同心圓放射狀的V圖形。

const colors = 'cacf85-8cba80-658e9c-4d5382-514663'.split('-').map(a=>`#${a}`);

function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0);
  rect(0,0 ,width,height);
  rectMode(CENTER);
}

function draw() {
  translate(width/2, height/2);
  fill(255);
  noStroke();
  fill(0);
  rect(0,0 ,width,height);
	
  for(let o=0; o<8; o++){
    rotate(PI/4);
    push();
      for(let i=0;i<20;i++){
        translate(0,-45);
        rotate(sin(i/(mouseX/100)));
        fill(colors[i%colors.length]);
        push();
          scale(0.5)
          rotate(-PI/4*3);
          rect(75,0,200,50);
          rotate(PI/4*2);
          rect(75,0,200,50);
        pop();
      }
    pop();
  }
}
<珊瑚嘉年華>作品步驟五:慢慢建構成了萬花筒
<珊瑚嘉年華>作品步驟五:慢慢建構成了萬花筒

六、提升質感

剛開始創作藝術作品時通常會稍微缺乏質感,主要是「材質、顏色、細節、變化」這四種狀態技巧掌握度不足。因此,想提升質感就要進行一些優化,例如:

1. 添加顏色

const colors = '18206f-17255a-f5e2c8-d88373-bd1e1e-cacf85-8cba80-658e9c-4d5382-514663'.split('-').map(a=>`#${a}`);

2. 增添大小變化:

scale(random(1))scale(noise(1)) 將每一個 V 的大小變不一致。要注意的是,使用 random() 會在每次繪製時重新產生亂數,所以畫面會感覺比較不受控;使用 noise() 則是類似在一個亂數表上取值,跟 random() 概念不同,呈現的畫面也比較沒那麼亂。

scale(random(0.6, 1.1)); 
or 
scale(noise(i,o)/5+0.9);

3. 添加動態變化:

除了使用 mouseX 去控制變化之外,也可以加上 frameCount() 讓圖形隨著時間變動。另外,如果想同時保留 mouseX 跟 frameCount,記得幫 mouseX 加上0.1,否則滑鼠一旦不動,畫面也就不會更動 。

rotate(sin(i/(mouseX/100+0.1)+frameCount/50));<

4. 使用 BlendMode() 進行一些色彩混搭組合:

使用BlendMode() 這個 API 可以進行依些色彩混搭,多些顏色的變化。如果是黑色的背景,參數建議選 SOFT_LIGHT、SCREEN 來提亮。

blendMode(SCREEN);

5. 改變材質

可以上網找 Canvas texture,就可以找到許多種材質的圖片並下載使用。如果想下載本次範例使用素材,可以到 成品 這邊,點開查看原始碼、以及右邊的 File 檔案區,找到 <canvas.jpeg>並下載到自己電腦裡

下載<珊瑚嘉年華>作品使用的材質素材說明
下載<珊瑚嘉年華>作品使用的材質素材說明

再來一樣到自己的 Sketch 頁面右側欄一樣的 File 內,點選上傳就可以上傳這張圖片了。接著會使用 preload()loadImage() 這兩個API 在程式碼內載入這張背景,然後使用 blendMode() 進行材質疊加。

push()
  blendMode(MULTIPLY);	
  image(canvasTexture, -width/2,-height/2)
pop()

完整程式碼如下

const colors = '18206f-17255a-f5e2c8-d88373-bd1e1e-cacf85-8cba80-658e9c-4d5382-514663'.split('-').map(a=>`#${a}`);

var canvasTexture;
function preload(){
  canvasTexture = loadImage("canvas.jpeg")
}

function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0);
  rect(0,0 ,width,height);
  rectMode(CENTER);
}

function draw() {
  translate(width/2, height/2);
  fill(255);
  noStroke();
  fill(0);
  rect(0,0 ,width,height);
	
  push()
    blendMode(SCREEN);
    for(let o=0; o<16; o++){
      rotate(PI/8);
			
      push();
        for(let i=0;i<20;i++){
          translate(0,-20);
          rotate(sin(i/(mouseX/500+0.1)+frameCount/100));
          scale(noise(i,o,frameCount/50)/5+0.9);
          fill(colors[i%colors.length]);
					
          push();
            scale(0.1)
            rotate(-PI/4*3);
            rect(75,0,200,50);
            rotate(PI/4*2);
            rect(75,0,200,50);
          pop();
        }
      pop();
    }
  pop()
	
  push()
    blendMode(MULTIPLY);	
    image(canvasTexture, -width/2,-height/2)
  pop()
}

範例

<珊瑚嘉年華>作品步驟六:透過改變顏色、大小、材質、動態等,增加作品質感
<珊瑚嘉年華>作品步驟六:透過改變顏色、大小、材質、動態等,增加作品質感

七、細節調整

接下來就進入好玩的部分啦~這邊開始可以做一些大膽的嘗試或調整,像是:

  • 把四方型加點圓角
  • 減輕旋轉幅度
  • 改變背景顏色
  • 多加一組色彩進行搭配
  • 改變四方型的高度、寬度,營造錯落感
  • 使用 ellipse() 增加小點點

在這個階段,可以根據自己的喜好自由調整與搭配,好好感受藝術的趣味~

const colors = '26f0f1-bd93bd-f2edeb-ffdd4a-fabc2a'.split('-').map(a=>`#${a}`);
const colors2 = '0b3954-bfd7ea-ff5a5f-c81d25-44ffd1-6153cc-a60067-961d4e'.split('-').map(a=>`#${a}`);
var canvasTexture;
function preload(){
  canvasTexture = loadImage("canvas.jpeg")
}

function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0);
  rect(0,0 ,width,height);
  rectMode(CENTER);
}

function draw() {
  translate(width/2, height/2);
  noStroke();
  fill('#3a3b47');
  rect(0,0 ,width,height);
	
  push()
    // blendMode(SCREEN);
    for(var o=0; o<8; o++){
      rotate(PI/4);
			
      push();
        // 讓每一條觸手大小各異
        scale(sin(i/5+o/5)/20+0.8);
				
        let useColors =([colors, colors2][int(o%2)])
        for(var i=0;i<20;i++){
          translate(0,-30);
          rotate(sin(i/((sin(frameCount/10))/2000+0.1)+frameCount/100)+o/80);
          // 使用pow() 讓大的更大,小的更小
          scale(pow(noise(i,o,frameCount/100), 1.2)*0.5+1);
          fill(useColors[i%useColors.length]);
					
          push();
            let useWidth = 50*noise(i,o+5000)+20;
            scale(sin(i/5+o/50)/5+0.2);
            rotate(-PI/4*3);
            rect(75,0,200+noise(i/40,o/40)*500,useWidth,useWidth);
            rotate(PI/4*2);
            rect(75,0,100+noise(i,o)*100,useWidth,useWidth);
          pop();
          // 增加小點點
          for(let k=0; k<8;k++){
            let useSize = 10*noise(k,50000)
            fill(useColors[k%5]);
            ellipse(noise(k+frameCount/100)*50,noise(k+frameCount/200,5000)*50,useSize,useSize);
          }
        }
      pop();
    }
  pop()
	
  push()
    blendMode(MULTIPLY);	
    image(canvasTexture, -width/2,-height/2)
  pop()
}
<珊瑚嘉年華>作品步驟七:細修作品
<珊瑚嘉年華>作品步驟七:細修作品

八、作品輸出

作品完成後,接下來就是輸出啦!輸出時很重要的一件事情是 — 要先把輸出畫作的品質提升,因此要用的API是:

接著我們要決定存下這幅藝術畫作的哪個畫面。程式藝術雖然是由創作者產出的作品,但它總會有某些時刻比較好看,因此當那些時刻出現時我們要把畫面存下來,這邊使用到的 API 是:

  • mousePressed(): 滑鼠點擊時
  • save():儲存畫面
function mousePressed(){
  save();
}

九、額外添加細節

儲存圖片之後,如果有什麼靈感也都可以再調整作品,像是點點上加入一些線條等等 (不過有點耗電腦效能,請自行斟酌使用)

const colors = '363636-242f40-cca43b-e5e5e5-ffffff'.split('-').map(a=>`#${a}`);
const colors2 = 'ffcb3d-2b2d42-8d99ae-edf2f4-ef233c-d80032'.split('-').map(a=>`#${a}`);

// 26f0f1-bd93bd-f2edeb-ffdd4a-fabc2a
// 0b3954-bfd7ea-ff5a5f-c81d25-44ffd1-6153cc-a60067-961d4e

var canvasTexture;
function preload(){
  canvasTexture = loadImage("canvas.jpeg")
}

function setup() {
  createCanvas(1000, 1000);
  background(400);
  pixelDensity(2);
  fill(0);
  rect(0,0 ,width,height);
  rectMode(CENTER);
}

// 按下儲存
function mousePressed(){
  save();
}

function draw() {
  translate(width/2, height/2);
  noStroke();
  fill('#3a3b47');
  rect(0,0 ,width,height);
	
  push()
    // blendMode(SCREEN);
    for(var o=0; o<8; o++){
    rotate(PI/4);
			
    push();
      // 讓每一條觸手大小各異
      scale(sin(i/5+o/5)/20+0.8);
				
      let useColors =([colors, colors2][int(o%2)])
      for(var i=0;i<20;i++){
        translate(0,-30);
        rotate(sin(i/((sin(frameCount/10))/2000+0.1)+frameCount/100)+o/80);
        // 使用pow() 讓大的更大,小的更小
        scale(pow(noise(i,o,frameCount/100), 1.2)*0.4+0.9)
        fill(useColors[i%useColors.length]);
					
        push();
          let useWidth = 50*noise(i,o+5000)+20;
          scale(sin(i/5+o/50)/5+0.2);
          rotate(-PI/4*3);
          rect(75,0,200+noise(i/40,o/40)*500,useWidth,useWidth);
          rotate(PI/4*2);
          rect(75,0,100+noise(i,o)*100,useWidth,useWidth);
        pop();
        // 增加小點點
        for(let k=0; k<8;k++){
          let useSize = 10*noise(k,50000)
          fill(useColors[k%5]);
							
          // 加上點的線條
          stroke(useColors[k%5]);
          line(0, 0, noise(k+frameCount/100)*50,noise(k+frameCount/200,5000)*50);
          noStroke();
          ellipse(noise(k+frameCount/100)*50,noise(k+frameCount/200,5000)*50,useSize,useSize);
        }
      }
      pop();
    }
  pop()
	
  push()
    blendMode(MULTIPLY);	
    image(canvasTexture, -width/2,-height/2)
  pop()
}
<珊瑚嘉年華>加上線條後的完成品

十、作品命名、發布、與構思搭配文案

圖片儲存下來後,接著便是構想作品的名稱,並為作品搭配能相呼應的一段文字。由於作品充滿絢爛的色彩,又有點像是海底生物一樣隨波擺盪,所以老闆決定將它命名為<珊瑚嘉年華>,並搭配它的顏色與整體感受加了一段文字:

又變得更熱了,連回憶都開始白化,
小丑魚永遠的離開了,沒有了他的珊瑚,
其實也是只剩多彩的空殼了吧,
畢竟還是群居的動物離不開彼此,
卻走散僅留下了時間的遺跡。

結語

這次帶大家從零到一體驗了生成式藝術的創作過程。從一開始只是腦中有 V 字型的靈感,經過一系列的調整與操作,最終才產出<珊瑚嘉年華>這個作品,讓我們快速回顧一下<珊瑚嘉年華>的創作過程:

  1. 從平日蒐集的藝術素材中發想靈感,決定做一個「V字型、輻射狀、往外擴張」的圖案。
  2. 使用 P5.js 先繪製出單一個 V 圖案。
  3. 使用迴圈與位移的API,將 V 圖案複製並形成放射狀的圓圈。
  4. 參考藝術素材或配色網站,將圖形搭配上色彩。
  5. 增加材質、變化、顏色搭配、動態等細節修飾與調整,來提升作品質感。
  6. 大膽的嘗試各種細節調整,一邊試一邊看自己喜歡的風格。
  7. 作品命名、輸出、發佈與文案撰寫。

藝術創作的有趣之處在於它有無數可能與千萬種解讀,在這個過程中,每一次的細節調整都賦予這幅創作不一樣的感受,也能看到這個作品慢慢的演化成果,最後再決定這個作品想成為什麼樣子。這次的直播創作透過逐步的調整以及一些大膽嘗試,創造出許多意想不到的感受。

藝術很多時候不僅反映當下的心情狀態,也能透過創作的過程突破自己的舒適圈,或是療癒自己的內心。所以不必糾結於該如何寫出完美的作品,直接照著老闆的教學上手試一試吧,也許在嘗試的過程中,會迸發出意料之外的藝術品!這邊附上本次範例的成品<珊瑚嘉年華>,提供大家在開發時參考。

看到這裡,你對 Creative Coding 更有興趣了嗎?
歡迎加入互動藝術程式創作入門(Creative Coding)線上課程,課程中你可以認識程式與互動藝術產業應用,開啟對工程跟設計的想像,學會使用 p5.js 開發互動介面,整合繪圖、音訊、視訊、文字、3D、互動與機器創作完整的作品,並將創作輸出應用在個人品牌或網站、主視覺或海報,甚至互動裝置、遊戲與教材製作等場景,讓你對進修的資源與路線更有方向。

此篇直播筆記由幫手 金金 協助整理

墨雨設計banner

這篇文章 【p5.js創作教學】Coral Carnival 珊瑚嘉年華 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
把程式當畫筆!《互動藝術程式創作入門》學生作品賞析 https://creativecoding.in/2022/02/08/creative-coding-students-artworks/ Tue, 08 Feb 2022 01:58:00 +0000 https://creativecoding.in/?p=1647 在創作的道路上我們難免會遇到卡關的時候,這次帶大家欣賞互動藝術程式創作入門課程中同學們的作品,看完別人的創意之後,你是否也躍躍欲試了呢?

這篇文章 把程式當畫筆!《互動藝術程式創作入門》學生作品賞析 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
在創作的道路上我們難免會遇到卡關的時候,腦袋就像打了死結的繩子、一灘死氣沉沉的池子,對於本業也是設計師的Ju編來說,這時候除了從自己的生活中尋找靈感外(a.k.a. 放假),欣賞別人的作品也是一大靈感來源。透過觀察與剖析藝術家們的腦袋,理解作品背後的動機與手法,替自己的創作注入新的活水。

這次要來帶大家欣賞互動藝術程式創作入門課程計畫中,同學們的作品。大家來自不同背景,當中也是不乏沒有程式基礎的同學,但是在完成課程的前五個章節之後,同學們都有能力開始創作屬於自己的程式藝術作品,逐漸實現腦中的創作想法。

《Drain》張晉國

「一張畫面裡的節奏如果是好的,他不管放在哪,都會是好看的。」靈感來源自於將音樂中聽覺上的「節奏」本身,透過生成式藝術轉換成視覺上的「節奏」,製造一種「用眼睛聽」的畫面。作品以聲音和粒子之間的互動形式為主軸,但在製作的過程中對於如何建立視覺和聽覺的同步性遇到了些許困難,所以轉而呈現出類似音樂播放器的效果,將粒子的移動速度、變化型態和聲音大小做互動,交織出宇宙中的夢幻感。

《Drain》作品截圖(藝術家:張晉國)
《Drain》作品截圖(藝術家:張晉國)

作品線上看

判斷聲音大小的區間可以使用函式getLevel(),設定如果聲音超過某個區間,粒子的數量就會大量增加,同時也會加入半徑較大的粒子。

哲宇老師點評:
如果有意願嘗試這種音樂節奏性的東西的話,可以先嘗試使用音量來觸發拍點,或是使用主要聲音的頻段來觸發視覺效果,以某段頻譜的平均波幅作為觸發的依據,產生粒子、改變顏色,會比較容易製作。

《Houses》蔡明達

以藝術家本身的職業為出發點,營造一個又一個的溫馨個人小空間,呈現出各種傢俱、窗戶的室內擺設排列組合,透過滑鼠的移動連動白天夜晚交替的變化,同時房子和傢俱也會根據光源而有不同的顏色變化。喜歡的話心動不如馬上行動,不然一下子就Sold Out囉!

《Houses》作品截圖(藝術家:蔡明達)

作品線上看

哲宇老師點評:
P5.js裡面有基礎的3D盒子 box() 和 光源 pointLight() 可以運用,可以變化試玩看看。

《Shadow》黃亦涵

最初創作的靈感來自於俄羅斯構成主義,交錯的矩形為特色,除了製作出塊狀的建物效果外再加以光影變化,運用明暗交錯轉換,營造出時間竄動的感覺。重複的元素以及簡單的構成,讓畫面產生韻律的疊加。

《Shadow》作品截圖(藝術家:黃亦涵)
《Shadow》作品截圖(藝術家:黃亦涵)

作品線上看

哲宇老師點評:
在處理這種比較複雜的陰影層次時,可以先把影子畫在一個圖層上,再用疊合模式畫在其他圖層上方。如果想要在光影或建築的傾斜程度做互動效果,可以使用shearX()控制平面或物件的傾斜程度,比如在畫物件前可以把整張畫面傾斜。

《章魚噴噴墨》陳慧珊

對於藝術家來說海洋是個讓人想起來就很放鬆的地方,作品以廣闊的海底和悠遊的魚群為背景,可愛的章魚先生為主角。把每天遇到大大小小、多到吐不完的煩悶事情,藉由滑鼠按壓章魚先生,一起隨著墨汁吐出去吧!

《章魚噴噴墨》作品截圖(藝術家:陳慧珊)
《章魚噴噴墨》作品截圖(藝術家:陳慧珊)

作品線上看

哲宇老師點評:
在製作的途中遇到圖層分層的小小困難,主要是章魚和魚群的繪畫順序需要調整一下,越上面的圖層會越後面繪製,image()應該是最後畫上去的。
也可以試試看墨水在水中暈開、越來越稀釋顏色漸淡的效果,每畫一次墨水就先疊上一層白色背景,再push()用blendMode(MULTIPLY)渲染,要讓他變淡等於讓他變白 inkGraphics.fill(255,10),有一定的透明度後把整個畫面再蓋一次白色inkGraphics.rect(0, 0, witdth, height),就可以製造漸淡的效果。
這邊提供一些不同的手法來增加畫面的層次感,比如:將魚分成兩層疊加,或者給魚一些不同的顏色或造型,如果想要製造接近魚群時魚群會閃避的效果,可以給予一些隨機的速度或向量。

《水墨漣漪》林文聖

藝術家本身是財金系畢業,在一次擔任志工的機緣下對設計產生興趣,再進修文創和平面設計相關的學程後,轉往前端的方向發展。

因為OpenProcessing上的東方元素作品較少,便嘗試從水墨畫發想。靈感來自於《時間之花》李炳曄,一開始想嘗試將時間和作品產生連結,但概念比較複雜所以改嘗試與水做互動。
當墨水滴入水中產生一圈一圈的漣漪,顏色也隨之在水中暈染且逐漸稀釋。作品使用鏡像疊加呈現,讓視覺有規律得變化,在墨水也會隨著滑鼠的位置以同心圓的方式改變,交互的作用下,形成如萬花筒的世界。

水波紋的效果:將物件的像素放大、frame調低,製造遞延的效果。例:pixelDensity(1/7)

《水墨漣漪》作品截圖(藝術家:林文聖)
《水墨漣漪》作品截圖(藝術家:林文聖)

作品線上看

哲宇老師點評:
如果要把波紋做得更細緻(波紋之間的干涉),可以用shader下去做。使用Shader的GPU是一點一點下去計算的,可以控制每個像素是疊合什麼顏色,這種更細緻的控制。
如果要在p5.js做,可能就要考慮像素等級的疊合,比如兩個source最靠近的那一條會受到影響的重疊範圍,各自再往原本的source偏移一些,有點像會變成被壓扁一側的圓型。

《Mysterious Bomb》Athem Lin

藝術家本身是YouTuber老高以及特斯拉的粉絲,對於像宇宙、亞特蘭提斯、外星人等等的概念很有興趣,進而創作了這個作品。

可以透過滑鼠切換作品中的兩個模式:原子爆炸、夢幻方塊,在點擊時會有爆炸後收縮的效果帶你進到另一個宇宙。

在創作過程中遇到的困難是,有時好不容易想出一個作品的方向,但從理想到實作又是一趟漫長的過程,不見得每次都能夠實現。這時會去參考其他藝術家的表現手法,藉由觀摩來摸索出屬於自己的方法。

《Mysterious Bomb》作品截圖(藝術家:Athem Lin)
《Mysterious Bomb》作品截圖(藝術家:Athem Lin)

作品線上看

哲宇老師點評:
作品從元素呈現到音效選擇都很到位,完整度很高。一般剛踏入Creative Coding的世界時,大部分都是觀摩作品然後做修改,但這個作品很有個人特色,有呈現出藝術家的風格。

《Universe》孫宇

遼闊黑暗的宇宙中,偶有光亮。就像人生失落寂寞中,偶有希望。希望這些希望可以多一點,帶給身邊的人,成就更美好的自己。

《Universe》作品截圖(藝術家:孫宇)
《Universe》作品截圖(藝術家:孫宇)

作品線上看

哲宇老師點評:
作品比較偏粒子疊加的嘗試,有用blendMode()去做疊加,更進階一點可以在形狀旁邊加光暈。

drawingContect.shadowColor = color(255,100)
drawingContect.shadowBlur = 10

以上就是這次的分享,希望透過觀摩別人的作品可以帶給大家一些新的創作靈感,那麼我們下次再見啦👋👋👋

看到這裡,你對 Creative Coding 更有興趣了嗎?
歡迎加入互動藝術程式創作入門(Creative Coding)線上課程,課程中你可以認識程式與互動藝術產業應用,開啟對工程跟設計的想像,學會使用 p5.js 開發互動介面,整合繪圖、音訊、視訊、文字、3D、互動與機器創作完整的作品,並將創作輸出應用在個人品牌或網站、主視覺或海報,甚至互動裝置、遊戲與教材製作等場景,讓你對進修的資源與路線更有方向。

此篇直播筆記由幫手 Jeudi Kuo 協助整理

墨雨設計banner

這篇文章 把程式當畫筆!《互動藝術程式創作入門》學生作品賞析 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js創作教學】Dreamy Bird 夢幻鳥 https://creativecoding.in/2021/10/12/p5-js-dreamy-bird/ Tue, 12 Oct 2021 09:12:00 +0000 https://creativecoding.in/?p=1568 本篇記錄創作<夢幻鳥>這件作品的過程,利用p5.js在短短的時間內創作出繽紛多彩、眼睛跟著滑鼠游標轉的夢幻鳥,看著他不斷地動,好像趴著在看水族館裡的魚一樣,心情也會跟著雀躍起來唷!

這篇文章 【p5.js創作教學】Dreamy Bird 夢幻鳥 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

本文翻自【Coding Vlog | p5.js】200514 Dreamy Bird 夢幻鳥 – 來做彩色又毛毛不知道是魚還是鳥的生物吧!若是想要老闆手把手帶你飛,可以跟著影片進行,這邊也附上成品歡迎大家一起動手做。

這一次分享的內容比較特別,是紀錄老闆創作的過程,起初只是想做金屬色的練習,調整不同屬性以及數值後,慢慢產生了生物的形體,而有了夢幻鳥的誕生。這個作品會利用線上的工具 openprocessing 來進行 p5.js 的創作。完成作品後會發現,其實使用到的 api 就只有那幾個,卻能創作出獨特又有趣的作品,大家了解 api 後,也能勇敢去嘗試調整,說不定會有更意想不到的作品產生。如果想要了解更詳細的製作流程和其他創作內容,可以去支持老闆的互動藝術程式創作課程哦!

這次直播筆記會帶大家學會以下內容:

  • 利用 p5.js 進行創作互動作品
  • 使用 noise 產生自然有序的隨機數
  • 在作品中加入滑鼠互動,讓作品與觀賞者產生連結

事前準備

開發環境

開發會使用 openprocessing 線上撰寫程式碼,如果想知道較詳細的設定,可以到成品看老闆的開發環境設定。

  • openprocessing:提供大家在網頁中直接使用 p5.js 進行開發,只要利用所提供的 api ,就能製作出有趣的效果。想要了解更多相關效果的開發,除了參考網站中其他的p5.js創作教學之外,也歡迎看看老闆的線上課程,跟老闆跟一起進入 processing 的世界。

會使用到的 API:

這次專案會使用以下的 api,先重點整理給大家,不清楚的地方,可以透過後面跟著老闆操作,了解每個 api 使用時機。

  • createCanvas(width, height): 創建畫布,參數中分別傳入寬跟高。
  • background(colorCode): 加上背景色,可依照文件傳入色碼參數。
  • noStroke(): 取消繪製圖形的邊框。
  • colorMode(): 定義顏色的方式,預設為 RGB 顏色,HSB 模式依序要填入的值則為(色相, 飽和度, 明度)。
  • fill(): 選擇填入的顏色,依照 colorMode 選擇的填色模式填入對應的參數。
  • ellipse(posX, posY, width, height): 在 (posX, posY) 上繪製一個寬高(width, height)的橢圓形。
  • rect(x, y, width, height): 以 (x, y) 的位置為左上角的點,畫一個寬度 width 高度 height 的方形(如果要畫正方形的話,即寬度=高度)。
  • random(): 沒有傳參數時,會返回一個隨機浮點數。
  • noise(): 產生自然有序的隨機值,與 random 的概念不一樣。在範例中會使用到 noise,所以建議先稍微理解 noise 的概念,有興趣的同學也可以延伸閱讀相關資訊:2D Noise – Perlin Noise and p5.js Tutorial
  • rotate(angle): 依照傳入的參數進行旋轉。
  • translate(x, y): 將畫筆移到(x,y) 上。
  • push(): 紀錄目前畫筆狀態。
  • pop(): 恢復畫筆狀態。
  • sin(): 正弦,將傳入的數值做為角度值,換算成 1~-1 的值。
  • cos(): 餘弦,將傳入的數值做為角度值,換算成 1~-1 的值。
  • atan2(y, x): 計算從指定點(y,x)到座標原點的角度。

跟著老闆開始動手做

1. 起手式

開啟新的 openprocessing > Create a Sketch,可以看到程式碼頁面已經有一段預設的程式碼,隨著滑鼠的移動,會沿路產生小球,理解這段程式碼後,接著只留下我們需要的部份。

  • setup(): 可以視為環境初始化,只在開始執行的當下會呼叫一次,以下的程式碼使用了兩個 api
    • createCanvas(width, height):創建畫布,參數中分別傳入寬跟高,如果直接寫螢幕的寬高(windowWidth, windowHeight),就會成為滿版的互動區塊。
    • background(colorCode):加上背景色,可依照文件傳入色碼參數。
  • draw(): 會依照時間不停地重跑裡面的程式碼,要製作互動的內容可以在這個 function 中呼叫。
    • ellipse(posX, posY, width, height):在 (posX, posY) 上繪製一個寬高(width, height)的橢圓形。
function setup() {
  createCanvas(windowWidth, windowHeight);
  background(100);
}

function draw() {
  ellipse(mouseX, mouseY, 20, 20);
}

2. 繪製基礎噪聲

在 draw 中,我們先調整顏色模式改成 HSB,後續與填色有關的 api 就會改成依序填入(色相, 飽和度, 明度),

  • noStroke() 將每次繪製圖形的邊框取消掉,每個方塊間就不會有 stroke。
  • colorMode():定義顏色的方式,預設為 RGB 顏色,HSB 模式依序要填入的值為(色相, 飽和度, 明度)。

接下來我們可以看到有兩個 for 迴圈,第一個 for 迴圈會每隔高度 20 ,再進行一次第二個 for 迴圈的內容,重新從左至右繪製一長串的方形。

  • rotate(angle): 依照傳入的參數進行旋轉。
  • fill():選擇填入的顏色,由於前面選擇了 HSB ,所以這邊要改使用 HSB 的方式填色。
  • sin():將傳入的數值做為角度值,換算成 1~-1 的值。
  • noise():躁聲,隨機序列生成器。跟 random 相比,可以利用多維的座標產生自然有序的序列,產出的值介於 0~1之間。
  • rect(x, y, width):在 (x, y) 的位置畫一個寬度 width 的方形。

經過調整後,讓呈現的顏色有時偏白,有時飽和度不會那麼高。在 sin 或 noise 中代入的值,老闆會多除上一些數字,目的是為了讓呈現的顏色變化不要太快,但這沒有正確答案,同學可以在了解每個 api 的操作方式後,依照自己的經歷或感受,去嘗試自己喜歡的氛圍。

最後一步驟,老闆希望能讓每一個橫條看起來都不同進度,所以在每一條橫條繪製前,都旋轉一下,就完成繪製基礎噪聲階段了,產生類似彩虹的畫面。

function setup() {
  createCanvas(800, 800);
  background(100);
}

function draw() {
  colorMode(HSB)
  noStroke()
  for(var o = 0; o<height; o+=20) {
    rotate(PI/1000)
    for(var i = 0; i<width; i++) {
      fill(sin(i/100)*300, noise(i/50, o/1000)*100, sin(i/40,o/1000)*30+80)
      rect(i, o, 30)
    }
  }
}
【p5.js創作教學】 夢幻鳥-步驟二:繪製基礎噪聲
【p5.js創作教學】 夢幻鳥-步驟二:繪製基礎噪聲

3. 依據噪聲橫列的影響色彩分佈跟變化

這個階段,老闆對每一行的波進行尺寸與填色的微調,也進行了波型的嘗試:

  • 讓每一橫列產生偏移:填色位置加入 o ,隨著每一行 o 的值逐漸增加,使得每一橫列的波產生偏移。
  • 讓波跟著時間動起來:填色位置加入時間因子 frameCount,讓整幅跟著時間的前進而動起來。
  • 更豐富的顏色:希望每一行波使用了 o 之後,不是只有偏移效果,所以在 noise 中又加入了 noise。
  • 降低波顏色變化速度:希望顏色變化的速度能更慢一點,針對 fill 內色相位置的值,除上更小的數字。
  • 加上插畫材質:為了讓作品更有質感,所以我們為作品加上插畫材質,材質製作方式這邊不詳細介紹,同學可以將程式碼貼到對應的地方直接使用。
let overAllTexture
function setup() {
  createCanvas(800, 800);
  background(0);
	
  // 插畫材質
  overAllTexture=createGraphics(width,height)
  overAllTexture.loadPixels()
  for(var i=0;i<width+50;i++){
    for(var o=0;o<height+50;o++){
      overAllTexture.set(i,o,color(100,noise(i/3,o/3,i*o/50)*random([0,40,80])))
    }
  }
  overAllTexture.updatePixels()
}
function draw() {
  colorMode(HSB)
  noStroke()
  for(var o = 0; o<height; o+=100) {
    for(var i = 0; i<width; i++) {
      fill(
        noise(i/400, o/400, noise(frameCount/150) + frameCount/50)*600%360,
        noise(i/50, o/1000, frameCount/100)*100,
        noise(i/40, o/1000, frameCount/100)*30+80
      )
      rect(i, o, 80)
    }
  }

  // 加上插畫材質
  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
  pop()
}
【p5.js創作教學】 夢幻鳥-步驟三:依據噪聲橫列的影響色彩分佈跟變化
【p5.js創作教學】 夢幻鳥-步驟三:依據噪聲橫列的影響色彩分佈跟變化,隨著時間前進具有插畫材質的波

4. 修飾幾何形狀與加入隨機大小

接下來,我們只會動到 draw 裡面的內容,老闆在這個階段做了以下嘗試:

  • 貓毛效果:畫方塊的時候,利用變數 glitchAmount ,在 x, y 座標加上隨機偏移值。
  • 隨著滑鼠位置改變的貓毛:將隨機偏移的值加上滑鼠的值,使作品與滑鼠產生了互動。
  • 微調樣式,將外層的 for 迴圈 o 從 10 開始,讓作品與上下邊界的距離一樣

老闆也有嘗試加入隨機的黑線在波形中,產生類似現代藝術的感覺,但實際做出來的效果不好(下圖左),大家想要嘗試,可以將以下程式碼斜線的部分恢復。

let glitchAmount = 20
function draw() {
  colorMode(HSB)
  noStroke()
  glitchAmount = mouseX/10 // 隨著滑鼠變更數字的貓毛
  for(var o = 10; o<height; o+=100) {
    for(var i = 0; i<width; i++) {
      fill(
        noise(i/400, o/400, noise(frameCount/150) + frameCount/50)*600%360,
        noise(i/90, o/1000, frameCount/100)*100,
        noise(i/80, o/1000, frameCount/100)*30+80
      )
      rect(// 繪製方塊時,繪製的座標結合隨機的值
        i + random(-glitchAmount, glitchAmount), 
        o + random(-glitchAmount, glitchAmount),
        80
      )
      // if(noise(i, o, frameCount/100) < 0.1){
      //   push()
      //     stroke(0)
      //     strokeWeight(20)
      //     rect(i, o, 80)
      //   pop()
      // }
    }
  }
	
  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
  pop()
}
【p5.js創作教學】 夢幻鳥-步驟四:加入隨機的黑線
【p5.js創作教學】 夢幻鳥-步驟四:加入隨機的黑線
【p5.js創作教學】 夢幻鳥-步驟四:隨著滑鼠產生不同波形尺寸
【p5.js創作教學】 夢幻鳥-步驟四:隨著滑鼠產生不同波形尺寸

5. 使用三角函數繪製波型

接下來老闆想做出類似極光的效果,一系列的調整與操作後,慢慢地變成一塊一塊的物體往前移動中,過程中做了以下嘗試,大家也能跟著老闆一起嘗試:

  • 由上到下、粗到細:極光從上到下粗到細,改變 rect 的第三個參數來實現改變波的大小
  • 結合 sin 產生波形:使用 sin 來繪製方形所產生波形,比較像極光或海浪,
  • 將波形結合時間因子 frameCount,讓波動起來
  • 改變 rectMode 為 CENTER,讓波上下同時變大
let glitchAmount = 5
function draw() {
  colorMode(HSB)
  noStroke()
  glitchAmount = mouseX/10
  rectMode(CENTER) // 調整繪製方形的模式
  for(var o = 50; o<height; o+=100) {
    for(var i = 0; i<width; i++) {
      fill(
        noise(i/400, o/400, noise(frameCount/150) + frameCount/50)*600%360,
        noise(i/90, o/1000, frameCount/100)*100,
        noise(i/80, o/1000, frameCount/100)*30+80
      )
      rect(
        i + random(0, glitchAmount),
        o + random(-glitchAmount, glitchAmount),
        (sin(i/40 + frameCount/20+o*50)+1)*30+20 // 結合 sin 繪製波形,加上 frameCount 讓波能跟著時間動起來
      )
    }
  }
	
  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
  pop()
}
【p5.js創作教學】 夢幻鳥-步驟五:結合三角函數,產生向前的波形物體

6. 加上眼睛與調整生物外觀

老闆認為一塊一塊向前的波形很像生物的身體,雖然還沒確定是魚還是鳥,但是老闆決定賦予每一個區塊眼睛。這階段老闆將一些值整理成變數,大家可以來回參照上一階段與這個階段的程式碼比較,在加上眼睛與生物外觀的調整過程做了以下嘗試:

  • push 與 pop:因為老闆將眼睛位置統一記錄在 eyes 中,所以程式碼會先將所有生物的身體繪製完畢後,再繪製所有生物的眼睛,這邊就會需要把畫筆位置移到對的地方,所以使用了 translate。要注意的是,做畫筆的移動或是旋轉畫布時,我們會使用 push 將原本的狀態記錄著,當完成位置時再搭配 pop 去恢復原本畫筆的狀態。
    • translate(x,y):將畫筆移動到 x, y 的位置
  • 以波形的進度 (progAng) 作為眼球的位置:將每個完整波形的長度百分之 3 的位置存進陣列中,同學要記得使用餘數,因為隨著時間增加,frameCount 是一直增加的,利用 PI * 2 去處理餘數,就能取得每個波形進度。
  • 生物的位置與滑鼠關聯:除了讓方塊的位置隨著時間去改變,這邊也做了滑鼠的互動,讓波形進度的值結合滑鼠位置。
  • 繪製生物的方塊:生物的身體,是依不同時間點來決定出不同大小的方塊所組成,老闆將原本的方大小作為 progAng 變數的值,再由 hh 變數來組合使用 progAng。
  • 區塊的大小更加生動:原本的區塊大小只是隨著滑鼠位置去變化,在 hh 的值中,除了利用 sin 之外,也加入 cos ,讓這個生物的外觀更有趣,產生毛邊金魚的感覺。
  • 繪製眼睛:眼睛陣列(eyes)裡的物件,是所有符合條件的眼睛 x 座標,結合 ellipse,繪製眼白與眼珠。
let glitchAmount = 5
function draw() {
  colorMode(HSB)
  noStroke()
  glitchAmount = mouseX/100
  rectMode(CENTER)
  for(var o = 50; o<height; o+=100) {
    let eyes = []
    push() // 記錄當下初始畫筆的狀態
      translate(0, o) // 移動畫筆到 (0, o) 的位置
      for(var i = 0; i<width; i++) {
        push() // 再次紀錄當下畫筆狀態
          translate(i,0) // 移動畫筆到(i, 0) 的位置
          fill(
            noise(i/500, o/400, noise(frameCount/150) + frameCount/50)*600%360,
            noise(i/90, o/1000, frameCount/100)*100,
            noise(i/80, o/1000, frameCount/100)*30+80
          )
          let progAng = (i/40 + frameCount/20+o*50 + mouseY/100 + mouseX*noise(o)/100) % (PI*2) // 結合餘數計算,讓值介於 0~100 之間
          let hh = (sin(progAng) + 1 + cos(progAng/2))*30 +20  // 結合波的進度作為每次繪製方塊的大小
          rotate(sin(i/10))
          rect(
            random(0, glitchAmount),
            random(-glitchAmount, glitchAmount),
            hh
          )
          if( int(progAng/PI/2*100 ) == 2) { // 符合進度條件則儲存 x 座標
            eyes.push(i)
          }
        pop() // 釋放畫筆位置
      }
      eyes.forEach( eyeX => { // 將陣列內的物件全部拿出來繪製眼睛
        fill('white')
        ellipse(eyeX, 0, 25)
        fill('#333')
        ellipse(eyeX, 0, 10)
      })
    pop() // 釋放畫筆位置
  }
	
  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
  pop()
}
【p5.js創作教學】 夢幻鳥-步驟六:加上眼睛呆呆前進
【p5.js創作教學】 夢幻鳥-步驟六:加上眼睛呆呆前進

7. 眼睛看向滑鼠、細調樣式

大致的生物形體告一段落後,除了細調樣式外,老闆也開始在作品中嘗試加入更多的互動性,例如讓眼睛看向滑鼠的位置,做了以下的操作:

  • 讓眼球看向滑鼠位置:這邊需要先取得滑鼠與眼球的角度,再利用 cos, sin 讓眼球能擺放到對的位置。使用到了新的 api – atan2
    • atan2(y2-y1, x2-x1):以弧度為單位,計算從指定的點 (y2,x2) 到 (y1,x1) 的角度,要注意這邊的 api 參數,第一個是 y 座標的計算,第二個才是 x 座標的計算。(https://p5js.org/reference/#/p5/atan2)
  • 清掉雜訊:因為 p5 是不停的地繪製新的畫面,畫面出現了許多雜點,是因為沒有在每次繪製前,先將畫面清空,這邊只要在繪製前,在畫布上蓋上一個滿版的方形就能達成。需要注意的是,利用覆蓋滿版方塊來清除雜點時,由於我們前面使用的 rectMode(CENTER),除了調整繪製的座標外,也可以先改回使用 rectMode(CORNER),等清除畫面完成後,再繼續原本的程式碼。
  • 微調生物的身體大小:微調的數值可以參考以下的程式碼,大家也可以嘗試看看不同的數值,看看會有什麼有趣的效果。
  • 取消毛邊與滑鼠的互動:固定生物毛邊的程度。
  • 調整背景色:老闆試著改變背景色,希望不要每個作品背景都是黑色。因為生物的顏色比較鮮豔,所以最後老闆挑了較深的顏色,來對比出作品的主角。
let glitchAmount = 5
function draw() {
  noStroke()
  // glitchAmount = mouseX/100 // 取消毛邊與滑鼠的互動
  rectMode(CORNER) // 改變繪製方塊的模式
  colorMode(RGB) // 使用 RGB 作為填色模式
  fill(156, 104, 104, 200) // 每次重新繪製時加上底色
  rect(0, 0, width, height)

  rectMode(CENTER)
  colorMode(HSB)
	
  for(var o = 50; o<height; o+=100) {
    let eyes = []
    push()
      translate(0, o)
      for(var i = 0; i<width; i++) {
        push()
          translate(i,0)
          fill(
            noise(i/500, o/400, noise(frameCount/150) + frameCount/50)*600%360,
            noise(i/90, o/1000, frameCount/100)*100,
            noise(i/80, o/1000, frameCount/100)*30+80
          )
          let progAng = (i/40 + frameCount/20+o*50 + mouseY/100 + mouseX*noise(o)/100) % (PI*2)
          let hh = (sin(progAng) + cos(progAng/2) + cos(progAng/5)/3 +1)*30 // 微調毛邊樣式
          rotate(sin(i/10))
          rect(
            random(0, glitchAmount),
            random(-glitchAmount, glitchAmount),
            +hh
          )
          if( int(progAng/PI/2*100 ) == 2) {
            eyes.push(i)
          }
        pop()
      }
      eyes.forEach( eyeX => {
        let mAng = atan2(mouseY - o, mouseX - eyeX) // 取得滑鼠與眼珠的相對位置
        fill('white')
        ellipse(eyeX, 0, 25)
        fill('#333')
        ellipse(eyeX + cos(mAng)*5, sin(mAng)*5, 10) // 利用 cos, sin 將眼珠放置在對的位置
      })
    pop()
  }
	
  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
  pop()
}
【p5.js創作教學】 夢幻鳥-步驟七:用滑鼠和眼睛互動,並調整整體畫面及顏色
【p5.js創作教學】 夢幻鳥-步驟七:用滑鼠和眼睛互動,並調整整體畫面及顏色

8. 加入魚鰭與最後修飾

創作到尾聲,其實老闆還沒決定他是什麼樣的生物,看起來類似尖嘴巴的魚,為了讓作品更完整,在這裡我們賦予生物們魚鰭,並做最後的微調:

  • 加上魚鰭:前面我們有記錄所有眼睛的位置,利用這個 for 迴圈,去繪製旋轉的三角形,讓它成為生物的鰭。這邊要記得使用 push 及 pop,不然會導致你下一次在繪製眼睛時出錯。
  • 調整背景色:最後老闆選擇了深藍色的背景作為定調。
  • 扭動的身體:繪製身體前的畫筆移動,在 y 參數的位置加上 sin ,可以繪製出魚移動時身體扭動的感覺。
let glitchAmount = 5
function draw() {
  noStroke()
  rectMode(CORNER)
  colorMode(RGB)
  fill(0, 0, 80, 180)
  rect(0, 0, width, height)
  rectMode(CENTER)
  colorMode(HSB)

  for(var o = 50; o<height; o+=100) {
    let eyes = []
    push()
      translate(0, o)
      for(var i = 0; i<width; i++) {
        push()
          translate(i, sin(i/30)*20) // 利用畫筆的位移,讓鳥在往前時,身體也有了變化
          fill(
            noise(i/500, o/400, noise(frameCount/150) + frameCount/50)*600%360,
            noise(i/90, o/1000, frameCount/100)*100,
            noise(i/80, o/1000, frameCount/100)*30+80
          )
          let progAng = (i/40 + frameCount/20+o*50 + mouseY/100 + mouseX*noise(o)/100) % (PI*2)
          let hh = (sin(progAng) + cos(progAng/2) + cos(progAng/5)/3 +1)*30
          rotate(sin(i/10))
          rect(
            random(0, glitchAmount),
            random(-glitchAmount, glitchAmount),
            +hh
          )
          if( int(progAng/PI/2*100 ) == 2) {
            eyes.push(i)
          }
        pop()
      }
      eyes.forEach( eyeX => {
        let mAng = atan2(mouseY - o, mouseX - eyeX)
        fill('white')
        ellipse(eyeX, 0, 25)
        fill('#333')
        ellipse(eyeX + cos(mAng)*5, sin(mAng)*5, 10)

        push() // 繪製魚鰭時,記得使用 push, pop 來記錄與釋放畫筆狀態
          stroke(0)
          noFill()
          translate(eyeX+50, 0)
          rotate(sin(eyeX/2+o/10)/2) // 畫筆進行旋轉,畫面會呈現魚鰭擺動的效果
          triangle(
            0, 0,
            50, -20,
            50, 20
          )
            pop()
        })
    pop()
  }

  push()
    blendMode(MULTIPLY)
    image(overAllTexture,0,0)
  pop()
}
【p5.js創作教學】 夢幻鳥-步驟八:加上魚鰭之後完成成品

老闆來結語

這次的創作一開始是老闆想要練習金屬色,一系列的調整與操作,最後才產出夢幻鳥這個作品,讓我們快速回顧一下夢幻鳥的創作過程:

  1. 了解 openprocessing 創作的起手式 – setup 與 draw
  2. 利用噪聲 noise 決定方塊的顏色
  3. 利用 for 迴圈的變數、噪聲與時間變數 frameCount,影響每一橫列的色彩分佈與變化
  4. 調整繪製方塊的形狀與大小
  5. 結合三角函數繪製出波形
  6. 為生物加上眼睛,並微調生物外觀
  7. 讓眼睛與滑鼠產生互動
  8. 加上魚鰭與最後修飾

萬事起頭難,一個作品不可能一步到位,將最終目標拆分成不同階段任務,從一開始的雛型慢慢開發出每個區塊,最後組裝在一起,也可以加上個人的創意去實現其他功能,讓作品更豐富。

由於這部影片比較特別,是紀錄老闆在練習與發想後,老闆回頭解說製作過程,所以中間會不停地去微調數值。創作的過程一定會有這種狀況發生,在創作時沒有所謂的正確答案,大家在了解工具之後,就勇敢地去嘗試吧!再附上這次範例的成品<夢幻鳥>,讓大家在開發時參考。

如果你喜歡老闆的教學,歡迎支持老闆,讓老闆在《互動藝術程式創作入門》課程中帶你一起學習。課程裡會帶你看看不一樣的作品,並引導大家一步步完成作品,透過每次的賞析、實作到修正作品,讓寫 code 不再是這麼困難的一件事情,將這個過程想像成,拿一隻比較難的畫筆在進行創作,如果有機會使用它,便能夠在網頁上做出與眾不同的創作。

此篇直播筆記由幫手 H 協助整理

墨雨設計banner

這篇文章 【p5.js創作教學】Dreamy Bird 夢幻鳥 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js創作教學】CreativeCoding 花火大會(直播筆記) https://creativecoding.in/2021/09/16/p5-js-creativecoding%e8%8a%b1%e7%81%ab%e5%a4%a7%e6%9c%83/ Thu, 16 Sep 2021 03:08:00 +0000 https://creativecoding.in/?p=1430 為響應日本的一群Creative Coder在Processing Community Day的社群串聯,扮起虛擬的花火大會,我們也來利用p5.js,結合粒子系統、漸變顏色甚至是聲控模組,一起在夜空中創作出絢麗的煙火吧!

這篇文章 【p5.js創作教學】CreativeCoding 花火大會(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
說到夏天,就想到海邊;說到海邊,就想到日劇裡的西瓜跟煙火。一束束的煙火短暫但繽紛,燃燒自己的生命點燃絢麗的光譜,珍惜每次綻放都是不同的樣貌。最近(2021年8月)有一群日本Creative Coder在Processing Community Day時串連起社群,在Twitter上辦起虛擬的花火大會,透過各自的作品在版面上綻放了大大小小的煙火,替最近被疫情拉開實體距離的生活中,增添一些夏天的顏色。

讓我們抓住夏天的尾巴,一起用粒子系統與漸變顏色創作煙火吧 🎆

今天要使用的是OpenProcessing搭配p5.js函式庫的大禮包組合,如果對這兩個工具還不太熟悉在這篇文章可以看到更多介紹 👉🏻 p5.js 快速上手

讓我們用草稿規劃一下煙火的概念,如果要做以粒子為基礎、從中心炸開的煙火,應該是一顆粒子從畫面水平線的底部往上移動特定距離,在上方炸開很多不同的粒子、且粒子各自擁有不同的運動方向。

根據以上的概念,我們今天會切分為以下步驟來進行:

  1. 粒子系統
  2. 動態延伸(移動、爆炸分裂)
  3. 顏色變換
花火大會作品草稿示意圖
花火大會作品草稿示意圖

製作粒子系統

首先第一個步驟我們先完成煙火的核心——粒子系統,以單顆粒子的物理模型來說會有位置(P)、速度(V)、加速度(a)和顏色(Color)、大小(r)等變數。在OpenProcessing先把畫布設成1000×1000、黑色的夜空之後,另外新增一個Tab,用來放置我的們的Class particle,在初始化時我們希望引入一些變數args裡面帶入一些固定的參數做使用,如果使用者有特別設定,把使用者引入的參數args蓋到預設值def上,再把客製化後的設定值蓋到這個物件本體this上。

//Tab2
class Particle {
  constructor(args){
    let def = {
      p: createVector(0,0), //位置
      v: createVector(0,0), //速度
      a: createVector(0,0), //加速度
      color: color('red'), //顏色
      r: 10, //大小、半徑
    }
    Object.assign(def,args)
    Object.assign(this,def)
  }
}

接下來介紹兩個關鍵的method分別是draw()update(),分別負責顯示和更新,切分成兩個部分是為了在更新的時後不動到最初始的顯示,把邏輯層區分出來,這樣對模組化的製作與管理也比較容易。

在同一個 tab2,先來處理draw()push()會保留目前的drawing style、而pop()則會回復這些設定,兩個必須搭配使用。假設粒子移動到this.p位置、顏色this.color、尺寸是this.r

class Particle{
  ...
  draw(){
    //顯示
    push()
      noStroke()
      translate(this.p) //processing可以只給向量,不一定要x,y
      fill(this.color)
      circle(0,0,this.r)
    pop()
  }
  update(){
    //資料更新
  }
}

在主要的程式定義一個陣列particles把粒子都裝進去,我們來初始化一顆粒子試試看,讓objParticle根據剛剛的規範來製作,放在外面並用let比較不會有全域打架的問題objParticle = new Particle() ,成像的位置在畫布寬高一半處,這時在畫布中間就可以看到我們千辛萬苦的第一顆隨機色粒子啦。

let particles = []
let objParticle 
function setup() {
  createCanvas(1000, 1000);
  background(0);
  objParticle = new Particle({
    p: createVector(width/2,height/2),
    r: 100,
    color: color(random(255),random(255),random(255))
  })
}

function draw() {
  objParticle.update()
  objParticle.draw()
}
萬事起頭難,頭過身就過,黑夜中的一顆小紅點。
萬事起頭難,頭過身就過,黑夜中的一顆小紅點。

單一粒子動態軌跡

update()處理位置(P)、速度(V)、加速度(a)和大小(r)的變化,每一顆的位置都會加上速度、而速度都會加上加速度。

update(){
  //資料更新
  this.p.add(this.v)
  this.v.add(this.a)
  this.r*=0.993 //由大變小
}

有物理模型後,我們來處理速度(v)和加速度(a),這邊介紹一個函式random2D(),可以在vector上隨機產生一個2D的向量。套用到速度上隨機產生方向,在爆炸初始時粒子會往原先的方向衝再往下掉,乘5倍讓初始速度>加速度,設定加速度為0.1,這樣我們就得到單一粒子的運動軌跡,也就是煙火炸開時的單一根花瓣。

單獨一顆的粒子運動軌跡。
單獨一顆的粒子運動軌跡。

製作束狀粒子群

在陣列內紀錄產生的粒子,先draw完後再update產生動態。
在陣列內紀錄產生的粒子,先draw完後再update產生動態。

有了一個粒子後,我們可以來做一束的煙火,用這些粒子加起來做成陣列。我們先把objParticle拿進來,用for迴圈做出50個粒子objParticle,再push到陣列particles內。在draw的地方把清單一個一個抓出來,我們就得到初步的美麗煙火了。

let particles = []
 
function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0) 
  rect(0,0,width,height)
	
  for(let i=0;i<50;i++){
		
    let objParticle = new Particle({
      p: createVector(width/2,height/2),
      v: p5.Vector.random2D().mult(5),
      a: createVector(0,0.1),
      r: 20,
      color: color(random(255),random(255),random(255))
    })
    particles.push(objParticle)
  }
}

function draw() {
  fill(0,5) //留下煙火軌跡
  rect(0,0,width,height)
  for(let objParticle of particles){
    objParticle.update()
    objParticle.draw()
  }
}
在夜空中綻放的一束煙火,看起來有點像下垂的海葵(?)
在夜空中綻放的一束煙火,看起來有點像下垂的海葵(?)

模組化並自動發射

完成了一束煙火後,我們要接著做此起彼落發射的夏日花火祭,把發射的動作包成一個function firework就可以重複呼叫它。包起來後先在setUp呼叫一次,也可以引入位置參數(p),如果該參數有值就顯示、沒有則出現在畫面中央。接著設定他產生的頻率,每隔100個frame放一次煙火。

大家可以發現我們調高了煙火的數量,從50到100個,在這個情況中為了預防畫面因為生成的東西越來越慢,我們來消除超出畫面的煙火,用filter()留下小於畫面的物件。

仔細觀察煙火的粒子除了大小不同外,每顆的初始速度也不同,如果初始速度相同就會較規則,看起來像下垂的海葵(?),這邊用random()給予任意值處理,煙火的顏色也調整成HSB模式,相較於RGB模式有更彈性的明度暗度可以使用,色調的變數請參考下圖。

我們把HSB色調分為兩個部分:baseHue和Hue。BaseHue為固定的偏移量,hue為根據每個粒子隨機產生出的値。
我們把HSB色調分為兩個部分:baseHue和Hue。BaseHue為固定的偏移量,hue為根據每個粒子隨機產生出的値。
let particles = []

function firework(p){
  push()
	
    let baseHue = random(300)
	
    colorMode(HSB)
    for(let i=0;i<100;i++){
      let hue = random(0,120)
      let objParticle = new Particle({
        p: p || createVector(width/2,height/2), //有位置p時取用p,沒有時就從畫面中央
        v: p5.Vector.random2D().mult(random(1,10)),
        a: createVector(0,0.1),
        r: random(40),
        color: color((baseHue+hue)%360,360,360) //避免>360的數字都是紅色
      })
      particles.push(objParticle)
    }
  pop()
}

function setup() {
  createCanvas(1000, 1000);
  background(100);
  fill(0) 
  rect(0,0,width,height)
  firework()
}

function draw() {
  fill(0,5) //留下煙火軌跡
  rect(0,0,width,height)
  for(let objParticle of particles){
    objParticle.update()
    objParticle.draw()
  }
  if (frameCount%100==0){
    firework()
  }
  particles = particles.filter(obj=>obj.p.y<height) //留下小於畫面的物件
	
  fill(0)
  rect(0,0,100,50)
  //計算畫面中粒子數
  fill(255)
  textSize(20)
  text(particles.length,50,50)
}
調整過後的煙火,終於看起來比較有層次感,不像下垂的海葵了。
調整過後的煙火,終於看起來比較有層次感,不像下垂的海葵了。

基礎版:滑鼠觸發煙火

接下來加入mouse的互動。首先註解掉自動產生的frameCount,每當滑鼠按壓時就在該位置呼叫firework,為了避免重複參數造成順序混亂,在呼叫時把p包成一個物件{p},記得setUp時也要回傳一個空的物件firework({})

這時候會產生一個問題,因為p引入firework被所有的粒子共用,所以有幾顆粒子他就會被update幾次,我們用copy()複製p出來給當下的粒子,避免所有的粒子共用位置。再加入fireRpraticleR等參數做出隨機粒子大小和隨機煙火大小。

function mousePressed(){
  firework({
    p: createVector(mouseX,mouseY),
    fireR: random(1,100), //煙火的大小
    particleR: random(1,10) //粒子的大小
  })
}
function firework({p, fireR, particleR}){
  push()
    let baseHue = random(300)
	
    colorMode(HSB)
    for(let i=0;i<100;i++){
      let hue = random(0,120)
      let objParticle = new Particle({
        p: (p && p.copy()) || createVector(width/2,height/2), //複製新的位置給當下的粒子,讓它重複100遍
        v: p5.Vector.random2D().mult(random(1,fireR || 5)), 
        a: createVector(0,0.1),
        r: particleR || random(40),
        color: color((baseHue+hue)%360,360,360)
      })
      particles.push(objParticle)
    }
  pop()
}
在夜空中用滑鼠點點點,我們就有初階的煙火大會囉
在夜空中用滑鼠點點點,我們就有初階的煙火大會囉~

進階篇:用聲音觸發煙火

做完基礎煙火互動後,如果可以用聲音來觸發煙火那一定很酷,p5.js裡有一些關於「聲音」相關的函式,今天介紹的是Mic Input可以截取電腦麥克風的聲音,我們先開啟p5.sound的library。

開啟p5.sound
開啟p5.sound

套用官方語法在setUp加上input = new p5.AudioIn()開始取用聲音。

let input

function setup() {
  createCanvas(1000,1000);
  background(100);
  fill(0)
  rect(0,0,width,height)
  firework({})
	
  input = new p5.AudioIn()
  input.start()
}

draw()加上觸發條件,如果有聲音,即在畫布上放煙火。這樣我們聲控的煙火大會就大功告成啦!

function draw() {
	
  let volume = input.getLevel()
  let speaking = voulume>0.15
	
  fill(0,8) //留下煙火軌跡
  rect(0,0,width,height)
  for(let objParticle of particles){
    objParticle.update()
    objParticle.draw()
  }
  particles = particles.filter(obj=>obj.p.y<height) //留下小於畫面的物件
	
  fill(0)
  rect(0,0,400,200)
  fill(255)
  textSize(20)
  text(speaking,50,50)
	
  if (speaking){
    firework({
      p: createVector(mouseX,mouseY),
      fireR: random(1,5),
      particleR: random(1,10)
    })
  }
}

小試身手

做完上面的煙火後,這邊提供幾個大家可以繼續嘗試看看的方向,希望大家可以長出各式各樣的煙火,讓夏天的夜晚更為熱鬧!

  • 粒子的顏色漸層
    可以在def的地方新增endColor: color('yellow'),利用lerpColor()這個漸變函式在Update()指定顏色的變化跟階數。
this.color = lerpColor(this.color, this.endColor, 0.05) //每次變換0.05
  • 扭曲的粒子運動軌跡
    在粒子translate的時候如果根據sin/cos偏移,可以做出扭曲的煙火效果會更漂亮。
curve: random(5),
curveFreq: random(2,40),

translate(this.p.x+sin(this.p.y/this.curveFreq)*this.curve,this.p.y+cos(this.p.x/this.curveFreq)*this.curve)⁠
  • 製造煙火的霧氣
    透過在Particle中的draw()增加一些半透明且半徑較大的粒子,來增加模糊的光影。Color先複製一份避免動到原先的設定,用函示setAlpha()製作透明度。
let copyColor = color(this.color.toString())
  copyColor.setAlpha(10)
  for(var i=0;i<100;i+=10){ //重複畫圓形
  fill(copyColor)
  circle(0,0,this.r*i/20) ⁠
}
  • 混合模式
    加入混合效果,blendMode()調整顏色呈現。
push()
  blendMode(SCREEN)
  for(let objParticle of particles){
    objParticle.update()
    objParticle.draw()
  }
pop()
  • 效能處理
    設定當r小於某個半徑時就不顯現,減少效能負擔。
particles = particles.filter(obj=>
            obj.p.y<height &&
            obj.r>0.01
  )

以上就是這次的教學,成品請參考這裡,希望大家還玩得開心,那我們就下次再見啦!

也許你對互動生成式藝術比較有興趣?來看看老闆的《互動藝術程式創作入門》課程,跟著將近兩千位同學一起把程式碼當作畫筆創作,或是先看看這篇文章,欣賞同學們完成的作品吧!

此篇直播筆記由幫手 Jeudi Kuo 協助整理

墨雨設計banner

這篇文章 【p5.js創作教學】CreativeCoding 花火大會(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>