前言
乘著生成式藝術的浪潮,讓我們一同體驗<珊瑚嘉年華>這件藝術作品的創作過程。使用 p5.js 的技術創作出絢爛夢幻、隨波蕩漾的珊瑚,看著它在海底深處搖曳擺盪,在暗夜中充滿生命力地熠熠生輝。
開場
這一次分享的內容比較特別,除了紀錄如何使用 P5.js 創作藝術之外,同時也記錄生成式藝術的創作過程。從初步的概念發想、創作中的顏色/動態/細節調整嘗試,一路到完成作品後的圖片保存處理、文案構思與撰寫,帶領大家一同體會藝術創作的趣味。
這次直播筆記會帶大家學會以下內容:
- 瞭解一幅生成式藝術作品的完整創作過程
- 使用 coolors 快速選擇色票並進行色彩搭配
- 使用 frameCount() 在作品中加入逐幀動態效果,賦予作品生命力
事前準備
- 開發環境: 本次開發會使用 openprocessing 線上撰寫程式碼,如果想知道較詳細的設定,可以到老闆作品的 成品 查看相關的設定。
- 本次範例使用到的 API:
- createCanvas(width, height): 創建畫布,參數中分別傳入寬跟高。
- background(colorCode): 加上背景色,可依照文件傳入色碼參數。
- noStroke(): 取消繪製圖形的邊框。
- colorMode(): 定義顏色的方式,預設為 RGB 顏色,HSB 模式依序要填入的值則為(色相, 飽和度, 明度)。
- fill(): 選擇填入的顏色,依照 colorMode 選擇的填色模式填入對應的參數。
- rect(x, y, width, height): 以 (x, y) 的位置為左上角的點,畫一個寬度 width 高度 height 的方形(如果要畫正方形的話,即寬度=高度)。
- rectMode(): 設定四方型從什麼地方開始繪製。模式分別有 CENTER、CORNER、RADIUS 三種
- translate(x, y): 將畫筆移到 (x, y) 上。
- scale(): 控制圖形的大小尺寸。
- random(): 沒有傳參數時,會返回一個隨機浮點數。
- noise(): 產生自然有序的隨機值,與 random 的概念不一樣。在範例中會使用到 noise,所以建議先稍微理解 noise 的概念,有興趣的同學也可以延伸閱讀相關資訊:2D Noise – Perlin Noise and p5.js Tutorial。
- rotate(angle): 依照傳入的參數進行旋轉。
- mouseX(): 滑鼠沿著左右移動。
- mouseY(): 滑鼠沿著上下移動。
- frameCount(): 控制每一秒產生的 frame 格數。
- ellipse(posX, posY, width, height): 在 (posX, posY) 上繪製一個寬高 (width, height) 的橢圓形。
- push(): 紀錄目前畫筆狀態。
- pop(): 恢復畫筆狀態。 不清楚 push/pop 如何運作的人,可以參考老闆互動藝術程式創作入門的課程筆記:章節 7 進階繪圖 – 畫布操作與編織複雜圖形 (pop/push 的圖解)
- sin(): 正弦,將傳入的數值做為角度值,換算成 1~-1 的值。
- cos(): 餘弦,將傳入的數值做為角度值,換算成 1~-1 的值。
- pow(): 冪運算(指數運算)。
- blendMode(): 將色彩融合。
- pixelDensity(): 完成後欲印出作品時,提高作品的像素,從而提升細緻度。
- loadImage(): 匯入圖片。
- save(): 儲存畫面。
步驟講解
一、初步靈感發想
最初的靈感發想可以從自己喜歡的事物著手,像是老闆喜歡鋼琴、琴譜、植物,因此這次腦海中的雛型是「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 的網址上複製色票的數值。
但複製完後,還需要先把色票轉成陣列的色彩,才能在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是:
- pixelDensity(): 提高作品的像素,從而提升細緻度
接著我們要決定存下這幅藝術畫作的哪個畫面。程式藝術雖然是由創作者產出的作品,但它總會有某些時刻比較好看,因此當那些時刻出現時我們要把畫面存下來,這邊使用到的 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 字型的靈感,經過一系列的調整與操作,最終才產出<珊瑚嘉年華>這個作品,讓我們快速回顧一下<珊瑚嘉年華>的創作過程:
- 從平日蒐集的藝術素材中發想靈感,決定做一個「V字型、輻射狀、往外擴張」的圖案。
- 使用 P5.js 先繪製出單一個 V 圖案。
- 使用迴圈與位移的API,將 V 圖案複製並形成放射狀的圓圈。
- 參考藝術素材或配色網站,將圖形搭配上色彩。
- 增加材質、變化、顏色搭配、動態等細節修飾與調整,來提升作品質感。
- 大膽的嘗試各種細節調整,一邊試一邊看自己喜歡的風格。
- 作品命名、輸出、發佈與文案撰寫。
藝術創作的有趣之處在於它有無數可能與千萬種解讀,在這個過程中,每一次的細節調整都賦予這幅創作不一樣的感受,也能看到這個作品慢慢的演化成果,最後再決定這個作品想成為什麼樣子。這次的直播創作透過逐步的調整以及一些大膽嘗試,創造出許多意想不到的感受。
藝術很多時候不僅反映當下的心情狀態,也能透過創作的過程突破自己的舒適圈,或是療癒自己的內心。所以不必糾結於該如何寫出完美的作品,直接照著老闆的教學上手試一試吧,也許在嘗試的過程中,會迸發出意料之外的藝術品!這邊附上本次範例的成品<珊瑚嘉年華>,提供大家在開發時參考。
看到這裡,你對 Creative Coding 更有興趣了嗎?
歡迎加入互動藝術程式創作入門(Creative Coding)線上課程,課程中你可以認識程式與互動藝術產業應用,開啟對工程跟設計的想像,學會使用 p5.js 開發互動介面,整合繪圖、音訊、視訊、文字、3D、互動與機器創作完整的作品,並將創作輸出應用在個人品牌或網站、主視覺或海報,甚至互動裝置、遊戲與教材製作等場景,讓你對進修的資源與路線更有方向。
此篇直播筆記由幫手 金金 協助整理