互動藝術創作 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/互動藝術創作/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Mon, 31 Jul 2023 07:01:47 +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 CC!我跟你說!S1EP4|黃新來聊聊:創作鑑賞與教學心路歷程 https://creativecoding.in/2023/07/31/podcast-s1ep4/ Mon, 31 Jul 2023 07:01:46 +0000 https://creativecoding.in/?p=3966 本集我們邀請到了——黃新一起來聊聊。黃新分享了他踏入互動藝術領域的契機,當時是如何啟發他進入這個領域的。我們深入探討了生成式藝術的獨特之處,了解這一藝術形式在當代藝術中的地位和特點。除此之外,我們還聽取了黃新分享他踏入互動藝術教學領域的原因和故事,讓我們更深入了解他對於教學的熱情和使命感。

這篇文章 CC!我跟你說!S1EP4|黃新來聊聊:創作鑑賞與教學心路歷程 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

本集我們邀請到了——黃新一起來聊聊。黃新分享了他踏入互動藝術領域的契機,當時是如何啟發他進入這個領域的。我們深入探討了生成式藝術的獨特之處,了解這一藝術形式在當代藝術中的地位和特點。除此之外,我們還聽取了黃新分享他踏入互動藝術教學領域的原因和故事,讓我們更深入了解他對於教學的熱情和使命感。

在這一集的節目中,我們透過黃新的親身經歷和專業知識,深入探討了互動藝術和生成式藝術的精彩世界,也了解了台灣數位藝術創作者所面臨的現實挑戰!

至以下平台收聽: FirstStory / Spotify / KKBOX / Pocket Casts / Apple / Google / SoundOn

⬢ 黃新小資訊 ⬢

黃新,別名紐耶羅(newyellow),是新媒體世界的定居者,也可稱為加密世界的新移民。他在數位互動藝術領域有著超過十年的資歷,在政大就讀數位內容學程碩士時,他就對 AR/VR 等互動科技深感興趣。自 2011 年起,他開始嘗試進行數位藝術創作,作品更曾三度獲得國際黑客松 AR Spark 首獎,展現了他在此領域的卓越才華。

除了作為一位成功的數位藝術家外,黃新也是 Meta 元宇宙培訓學院的合作講師,同時也是政大區塊鏈應用課程的講師,專注於教授與分享他對數位互動藝術的知識。作為生成式藝術的創作者和收藏家,他在這個領域中扮演著重要的角色,持續為藝術世界帶來創新和驚喜。

⬢ 老闆小資訊 ⬢

老闆哲宇是互動設計師、墨雨互動設計創辦人、數位藝術家、講師、全端工程師。先前在紐約大學進修整合數位媒體碩士,在 Creative Coding 領域有相當厲害的創作專業,知名 NFT 作品包含 PochiHamily 家族等。

哲宇在教學推廣上也不遺餘力,引領超過 20,000 位學生進入互動網頁與生成藝術開發的大坑。在文化推廣上,他成立了 FAB DAO ,希望能提攜台灣數位藝術家,被世界看見,期望可以帶著更多人,在數位世界中創造驚喜的體驗與生命。

 精選片段 

Generative Art 大賣秘辛「毛毛密碼」㊙️?

⬢ 本集重點 ⬢

02:13 踏入互動藝術領域的契機

09:37 生成式藝術的獨特之處

18:59 生成式藝術都在模仿嗎?

23:44 作品大賣的神秘密碼

27:50 fxhash造就的生成式藝術作品寒武紀大爆炸

34:23 踏入互動藝術教學的原因與故事

42:42 談談Volume Dao

45:42 台灣數位藝術創作者的生存之路

如果有任何的意見回饋或建議,歡迎來信讓我們知道,各位的參與是我們進步的動力。讓我們一起朝向更好的 Podcast 節目邁進吧!

⌔ 寫信給我們:creativecoding@monoame.com

這篇文章 CC!我跟你說!S1EP4|黃新來聊聊:創作鑑賞與教學心路歷程 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
CC!我跟你說!S1EP1|理工宅的藝術創作之路:國內外程式創作、互動藝術進修資源,以及老闆的建議 https://creativecoding.in/2023/05/17/podcast-s1ep1/ Wed, 17 May 2023 08:18:14 +0000 https://creativecoding.in/?p=3767 在本集中,我們將探索 Creative Coding 的奧秘——里歐娜會和老闆一起討論什麼是 Creative Coding ,以及老闆是如何踏上這條路的,在這條路上,又遇到了什麼樣的困難和驚喜之事呢?我們還會分享一些有趣的案例,並且比較台灣與國外的程式創作資源的不同之處。如果你曾經對學習程式的門檻感到困惑,相信本集也會對你有所啟發。

這篇文章 CC!我跟你說!S1EP1|理工宅的藝術創作之路:國內外程式創作、互動藝術進修資源,以及老闆的建議 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

在本集中,我們將探索 Creative Coding 的奧秘——里歐娜會和老闆一起討論什麼是 Creative Coding ,以及老闆是如何踏上這條路的,在這條路上,又遇到了什麼樣的困難和驚喜之事呢?我們還會分享一些有趣的案例,並且比較台灣與國外的程式創作資源的不同之處。

如果你曾經對學習程式的門檻感到困惑,相信本集也會對你有所啟發。最後,我們也會分享近期 Creative Coding 相關的活動資訊!

至以下平台收聽: FirstStory / Spotify / KKBOX / Pocket Casts / Apple / Google / SoundOn

老闆小檔案

老闆哲宇是互動設計師,也是數位藝術家、講師、全端工程師。先前在紐約大學進修整合數位媒體碩士,在 Creative Coding 領域有相當厲害的創作專業,知名 NFT 作品包含 Pochi 、 Hamily 家族等。

哲宇在教學推廣上也不遺餘力,引領超過 20,000 位學生進入互動網頁與生成藝術開發的大坑。在文化推廣上,他成立了 FAB DAO ,希望能提攜台灣數位藝術家,被世界看見,期望可以帶著更多人,在數位世界中創造驚喜的體驗與生命。

精選片段

老闆的大學回憶:在學聯會行銷部,用 Arduino 做了蝦趴聖誕樹,還在 Dcard 上爆紅?

本集重點 ⬢

02:21 究竟什麼是Creative Coding?

03:31 老闆開始接觸Creativ e Coding的契機

05:17 Creative Coding的有趣案例

08:28 藏在非典型電機人內心的創作熱血🔥

09:39 出國進修的動機💭、國外程式創作與教育環境

15:59 我們都有茫然的時期…

18:16 國外的程式創作資源、互動設計風氣和台灣有什麼不同?

21:31 面對學習程式的門檻,我該怎麼辦——身為過來人,老闆用心良苦的建議

30:44 在台灣的進修資源有哪些

32:20 學習Creative Coding有什麼出路或運用?

36:36 老闆語重心長的建議

38:09 近期Creative Coding相關活動資訊

如果有任何的意見回饋或建議,歡迎來信讓我們知道,各位的參與是我們進步的動力。讓我們一起朝向更好的 Podcast 節目邁進吧!

⌔ 寫信給我們:creativecoding@monoame.com

這篇文章 CC!我跟你說!S1EP1|理工宅的藝術創作之路:國內外程式創作、互動藝術進修資源,以及老闆的建議 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
未來 JavaScript 應用指標!不藏私 p5.js、生成式藝術與NFT 技術分享教學 https://creativecoding.in/2023/04/28/%e6%9c%aa%e4%be%86-javascript-%e6%87%89%e7%94%a8%e6%8c%87%e6%a8%99%ef%bc%81%e4%b8%8d%e8%97%8f%e7%a7%81-p5-js%e3%80%81%e7%94%9f%e6%88%90%e5%bc%8f%e8%97%9d%e8%a1%93%e8%88%87nft-%e6%8a%80%e8%a1%93/ Fri, 28 Apr 2023 08:39:02 +0000 https://creativecoding.in/?p=3643 JavaScript 未來全攻略!包括不藏私實際操作 Processing 和 p5.js,以及解密如何在 Artblocks 和 Opensea 等 NFT 平台上架作品。我們還會介紹連結硬體 OSC 和 socket,以及 MaxMSP 中 JS 模組的撰寫和基於 Tensorflowjs 的機器學習,帶領創作者一步步了解這些工具和技術,以便可以更好地創建自己的生成式藝術和互動作品,滿滿 JavaScript 乾貨讓創作者一次帶回家!

這篇文章 未來 JavaScript 應用指標!不藏私 p5.js、生成式藝術與NFT 技術分享教學 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

哲宇老師榮幸受邀在 2021 年臺灣 JavaScript 開發者年會( JavaScript Developer Conference ,簡稱 JSDC )開講 JavaScript 在生成式、演算藝術與 NFT 的應用。 JavaScript 開發者年會是台灣最大的 JavaScript 年度性技術研討會, 提供台灣中高階 JavaScript 技術人才與世界最新 JavaScript 相關技術討論與分享。

此次分享將涵蓋到生成式藝術和互動領域的常用工具,包括不藏私實際操作 Processing 和 p5.js,以及解密如何在 Artblocks 和 Opensea 等 NFT 平台上架作品。我們還會介紹連結硬體 OSC 和 socket,以及 MaxMSP 中 JS 模組的撰寫和基於 Tensorflowjs 的機器學習,帶領創作者一步步了解這些工具和技術,以便可以更好地創建自己的生成式藝術和互動作品,滿滿 JavaScript 乾貨讓創作者一次帶回家!

JS的發展與瀏覽器虛擬化

JavaScript 在 p5.js 或是 即時 Web Base Ide 這些工具上,都能快速幫助藝術家和工程師快速的開發,甚至是結合網頁特效的 Library。當這些前端的東西都有了後,創作者通常會連結硬體。以前連接硬體,我們可能需要就是再額外安裝一些軟體或直接寫一些需要編譯的執行檔,現在你可以透過比如說像 OSC 與 socket,或者是像 web USB 這類協議直接連接硬體。在其他的軟體生態系裡面也逐步方便,比如說 Max MSP,也可以用 JS 來撰寫模組,甚至像 Tensorflowjs 的機器學習以及 ml5.js,也可以把機器學習跑在前端,然後最後再用 Electron 把它包裝成跨平臺的應用程式,都十分流暢。

JavaScript 在近年來的快速發展和優化,特別是 V8 引擎的改進和像 M1 晶片這樣的新技術的加入,使得JavaScript 的執行速度得到了大幅提升,從而使得 JavaScript 在處理一些複雜的任務,例如大量的particle 模擬或者是電腦繪圖等方面,也能夠有較好的表現。在這樣的情況下,像 Processing 和 p5.js 這樣的 JavaScript 框架也因其易學易用,而逐漸成為許多創作者和開發者的首選。這些框架提供了一個簡單的方式來設計和實現各種視覺化效果,從而使得創作者可以更輕鬆地進行創作,而不必擔心性能問題。

V8 在十年間的快速成長(圖片來源

生成式藝術與互動的常用工具

在創作生成是藝術跟互動的常用工具,第一個首選就是 p5.js 。 p5.js 可以視為 Processing 的 JavaScript 版本,它可以在網頁瀏覽器中運行。在 Processing 和 p5.js 中,可以使用不同的圖形和動畫函數,例如線條、形狀、色彩等,來創建各種視覺效果。或是使用鍵盤、滑鼠和觸控螢幕等輸入方式來創建互動應用程式。現在也有很多可以快速實作 p5.js 的 web ID,以下簡介給各位,第一個是 open processing 也是哲宇老師最常用的平臺,Open processing 使用上對初學者的設計師或工程師非常友善,像是在介面上以視覺為主,canvas 本身已幫創作者準備好,可以直接撰寫 p5.js 簡化的程式語言在上,並即時看見其渲染圖應用效果。比起 web canvas api 需要準備基礎的結構,可以讓創作者在最短的時間內執行 prototype,快速將生產環境建構出來。只要使用簡易語法,即可在畫布上建立不同的效果,例如:

  • createCanvas():建立畫布
  • fill():填上顏色
  • stroke():線條粗細
  • rect():建立方形
  • ellipse():建立圓形
  • mouseXmouseY:利用滑鼠 X 軸與 Y 軸的移動來控制圖形呈現的效果
  • rotate():旋轉
  • sin()cos():旋轉的角度

直述式的程式語言,與公開的文件參考資料讓使用者能夠直覺式的撰寫,讓大家可以更快速的進行創作。假使在使用上想要更嚴謹,或是引用更多的函式庫,哲宇老師推薦使用 CodeSandbox ,其與他者的差異性在於可以引入 Npm ,有點像是一個在遠端的虛擬機,可直接加入特定想使用的函式,檔案結構完整,甚至可以建立自己的模板,讓撰寫嚴謹,也符合個人化的專案設定。再來是 Codepen ,純粹使用 web canvas api,相較於 p5.js 較繁雜,但使用的工具本身並無優劣,而是需要依照每個人不同的的使用情境以及需求等等,去做創作上的使用。

在創作生成式藝術上,哲宇老師最常使用的方式是 p5.js 再加上 glsl 的 shader,產出效能大約為2至3小時一張創作便能完成,以下幾張較代表性的創作圖與各位做分享:

NFT 210612 Chaos Dancer #Classic #1(圖片來源

創作上,哲宇老師經常使用留下筆跡的創作手法,在畫布上設定畫布大小、需要出現的圖形與顏色,並且記得藏起背景功能,因為背景功能在 draw 底下為在每一次重複將東西清掉,所以無法顯示圖形軌跡,如下示範:

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

function draw() {
  // background(220);

  fill("blue");

  push();
    fill(255, mouseY, 50);
    translate(mouseX, mouseY);
    rotate(frameCount / 50);
    rect(0, 0, 200, 200);
  pop();

  ellipse(0, 0, 200, 200);
}

而 Chaos Dancer 就是以類似產生大量的 particle,讓它們隨著時間旋轉並逐漸變小,再加上 sin cos 等力場,並將軌跡留下,形成畫布,並再疊上不同材質讓創作有更不一樣的感覺。

NFT 200506 Loop Mountain #Classic #1(圖片來源

就是單純用迴園功能,讓圓圈往漸小的往上長,與隨機的大小變化去執行。

NFT 210913 Sweet Dreams #Classic #1(圖片來源

除了留下軌跡外,哲宇老師也分享了另一種常使用的創作手法,變化的遞迴切割。

變化的遞迴切割示範(圖片來源

半圓型線條,再加上大小不同的色塊與質感組成,跳脫一般遞迴切割的相同模式,形成較新穎的氛圍。在此創作上,主要使用 divideSq() 去形成遞迴切割的部分,並且設定在不同機率下形成的多樣變化。哲宇老師分享因自己的工程背景,在創作上常使用一些數學物理相關概念,也說明到其實遞迴、粒子或是物理模擬等等進行創作。諸如此類的視覺特效也有不同的資料庫提供大家參考使用,像是專門製作 3D 的 Three.js 、圖像處理與 Shader,具有像素等級處力效能的 Pixi.js 、向量操作專門戶 Paper.js 以及類似於早期的 Flash,專門製作小型遊戲的 Phaser.js

Artblocks – 區塊鏈與生成式藝術的整合

生成式藝術與 Artblocks 的關係到底為何呢?先來簡單介紹 Artblocks 給大家。 Artblocks 是賣生成式藝術的 NFT(非同質化代幣)平台,每個作品在平台上可能會有 500至1000個版本。但其限制是,平台希望程式也要在鏈上,所以只能接受使用一種  dependency 。當創作者完成創作後,每一個版本都會有所不同,但同時間因為透過了每個作品上的 hash ,轉換成亂數並給作品做使用。 在 opensea 上面會擁有這些不同版本的創作。

Artblocks 首頁(圖片來源

平台上編寫時,主要使用 xor 演算法,此演算法會依據不同的 seed ,也就是亂數位元作操作。操作完後,在以模擬電腦亂數表方式產生亂數, 如果次數固定,則意味著在相同 hash 的狀況下,一定可以在某些特定狀況下得到相同亂數,此方法可以在 random 中設定想要更改的數字或陣列。哲宇老師也使用了 Token Art Tools 執行 debug,透過左側項目欄調整各種比例以更改 hash 的組成,並自動提供給 script ,這樣就可以快速地看到作品在不同 hash 時的不同樣貌。(更多 Artblocks 的執行細節可參考此篇 >>【老闆週六來聊聊】吳哲宇 Artblocks Project – Electriz 製作分享

Token Art Tools 網站截圖

在上架 Artblocks 平台時,會要求將所有程式碼上架到主網上,而因此需付 1 至 2 ETH 左右。另外,在 mint 的同時,即會產生隨機的 hash ,也同時間將 preview 上架到 Opensea 上。所以,從 Artblock 到 Opensea 上的流程總體結論就是從 Hash 到 Random Number Generator ,最後在看要使用 Static output(靜態輸出) 或是 Dynamic output(動態輸出),並且會在這整個流程中的某一時刻擷取快照形成 preview 。生成式藝術的應用多變性,也直接的反應在創作中。像是要形成如上述複雜的圖樣,哲宇老師通常會使用變化角度 sin() 或 cos() 等去執行,或是假使作品要應用到不同載體上,如投影或是印製到硬體上,需要放大作品的像素密度,我們也只需要簡單設定 PixelDensity(),使作品在只要能透過瀏覽器進行的載體上,都能設定出自己所需要的圖片密度大小。

連接硬體 OSC 與 socket

以前在連結硬體上,都需要再透過 processing 進行傳輸,現在其實有多種不同方式可以使用,包含 p5.js 也可以幫你執行!比較為一般人常見的傳統 Open sound control(網絡音訊傳輸協定)有應用在 DJ 控制燈光及音樂的常見場景,使用 udp 加上 socket.io client,使前端網頁可以直接連結到 bridge 上接收 osc 的訊息,範例如下,哲宇老師使用過藉由賭博的方式去控制檯燈的作品。

哲宇老師連結硬體創作作品(圖片來源

此作品連結 MaxMSP,運用偵測到骰子上的點數以及有幾個骰子加上連結 socket 去控制檯燈的明亮度。另外,像是開源式平台 Arduino 只要在網站上就能編輯,或是 Johnny-Five ,一個基於 JavaScript 的機器人和物聯網(IoT)程式框架,允許開發人員使用高級程式語言來控制硬體。 Johnny-Five 設計可於包括 Arduino 、 Raspberry Pi 和 Particle 設備等各種硬體平台一起使用。

Johnny-Five 首頁(圖片來源

還有 Node-RED 以簡單易用的圖形化用戶界面,用戶可以通過拖拽節點並用連接線連接它們來創快速構建 IoT 應用程式。

Node-RED 首頁(圖片來源

經由這些新平台的介紹,我們可以知道,其實 Javascript 現在的語言邏輯更容易上手,能整合多種生態系,甚至是跨軟硬體執行,對創作者更增加了無限的可能。

MaxMSP 的 JS 模組撰寫

講到硬體與軟體的應用,就不得不提到 MaxMSP 了。 MaxMSP 為一款音樂創作、聲音處理和音樂表演的軟體,它可以讓用戶使用圖形化的方式編寫音樂程序。界面直觀,使用滑鼠和鍵盤就可以輕鬆地創建、編輯和控制音樂和聲音,所以 VJ 或是新媒體藝術家常運用此軟體進行創作。在使用上, MaxMSP 也提供了 Node for Max ,一個專門為 Max/MSP 環境開發的軟體,讓使用者也可以運用 Javascript 撰寫並轉譯成 MaxMSP 可以運用的語言。語言資源方面,除了 Max 的 api 外,也能使用像是 tonal,擁有諸多和弦及音符等相關處理函數能夠去做運算。

Node for Max 介面(圖片來源

前端機器學習及相關運用

以下與大家介紹更多相關資源,例如 TensorFlow.js ,是 Google 開發的一個開源 JavaScript 框架,它使得開發人員可以在網頁瀏覽器上使用並進行機器學習。強大的 JavaScript API ,可以用於在瀏覽器中創建、訓練和部署機器學習模型,除了可以在瀏覽器中運行這些模型外,還可以在瀏覽器中使用預訓練模型進行影像、聲音和自然語言處理等任務。

TensorFlow.js 首頁(圖片來源

使用上,則有 ml5js 函式庫的協助。函式庫中包含影像、聲音、姿勢和自然語言處理等多種模型,提供了更簡單的方式來使用機器學習。

ml5js 首頁(圖片來源

示範案例,使用函式庫進行臉部以及動作的追蹤創作,也可以依此應用延伸出更多商業價值,像是依據追蹤動作,留下軌跡或是特別觸發某些機關等互動式創作。

ml5js 示範創作截圖(圖片來源

或是透過 Google 的 Teachable Machine,在線上訓練不同的模型,輸出為 json 檔案後再透過 ML5 做載入,例如以下為說明左傾右傾的差別,並在最後到 p5.js 進行視覺化。

Teachable Machine 示範(圖片來源

上述所講到的瀏覽器端應用,皆可以使用 Electron 進行包裝,藉由封裝方式形成桌面板可應用程式,可運行於 Windows 、 macOS 和 Linux 等多個操作系統平台上,讓開發者可以使用熟悉的前端技術來開發桌面應用程式。

JS 在創作領域發展的展望與未來

瀏覽器的完整虛擬化和純雲端應用程式使得開發者可以在雲端環境中開發和部署應用程式,從而降低開發和維護成本,同時提高應用程式的可擴展性和安全性,進而也讓 Isomorphic JavaScript 的執行效能得到了大幅優化,並且提供了 App 包裝功能,使得開發者可以將應用程式打包成原生應用程式,運行在桌面和手機等平台上。加上高階特性和語法糖的成熟以及 TypeScript 的支持使得開發者可以更加容易地編寫高質量的程式碼,與 Package 生態系的豐富性和開源社區的活躍性,都為開發者提供了更多的選擇和支持。快速開發環境和不重複造輪子的潮流下,使得開發者可以更加專注於應用程式的邏輯,並且快速開發出高品質的應用程式,同時也可以減少開發成本和風險。

商業應用範例(圖片來源

總體而言, JavaScript 的發展帶來了許多新的機會和可能性,使得創作者可以更輕鬆地實現各種想法和概念。同時,像 Processing 和 p5.js 這樣的框架也為創作者提供了更多的工具和資源,使得他們可以更加專注於創作本身,而不必花費太多時間和精力在技術層面上。最後,經過本次分享你也躍躍欲試,等不及想要開始進行快速易上手的創作了嗎?趕快訂閱 老闆,來點寇汀吧。Boss, CODING please. Youtube 頻道搶先了解第一手消息,或是加入哲宇老師在 hahow 開設的課程,一起快速上手程式藝術撰寫!也歡迎對相關項目有興趣的同學可以至 墨雨設計 聊聊天喔 ~

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

這篇文章 未來 JavaScript 應用指標!不藏私 p5.js、生成式藝術與NFT 技術分享教學 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js 程式創作】Soul Fish 數位永恆生命的靈魂魚 – 製作流程 https://creativecoding.in/2023/03/02/soul-fish-process/ Thu, 02 Mar 2023 08:59:45 +0000 https://creativecoding.in/?p=3510 此篇文章是前陣子發行的作品 - 靈魂魚 - 背後的製作流程,關於如何使用程式來創作一隻夢幻的數位生物!靈魂魚的作品是用理性的程式創造生命感的探索,也融合了互動生成音樂與視覺,是在互動型 NFT 邊界上的探索之作 。

這篇文章 【p5.js 程式創作】Soul Fish 數位永恆生命的靈魂魚 – 製作流程 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
本次內容將會以前陣子發布的作品「靈魂魚」作為主題,分為創作故事製作流程分別介紹,此篇主要是製作流程的分享。靈魂魚的作品是如何使用理性的程式創造生命感的探索,也融合了互動生成音樂與視覺,也是在互動型 NFT 邊界上的探索之作 。

靈魂魚的創作故事:p5.js 程式創作 | Soul Fish 數位永恆生命的靈魂魚 – 創作故事

老闆在直播中分享五月份至美國紐約參加研討會的感想,研討會上有像是 6529 等創作家在研討會上做分享交流。讓老闆想到,最一開始接觸 NFT 是因為創作,從創作上也有看到像是 Pak 這樣藝術家的作品,純粹、好玩卻也附有商業性價值,因此開始接觸區塊鏈,參加像是 Consesus 這樣的區塊鏈相關活動。雖然近期不論國內與國外,對於虛擬世界的交易開始有些反感的聲浪出現,但也因此某些事項反而逆勢而起,像是在 Art Blocks 上購買生成式藝術的趨勢逐漸成長。在創作上,老闆也建議,雖然觀眾在某一期間追求的風格會相似,導致大多創作人會往同風格進行創作,但也不要忘了,走出自己的路線才最有可能被記憶紀錄。

為什麼會選靈魂魚當主題與創作概念?

在準備個展期間,有關看到一部與金魚相關影集,觀察到金魚本身的姿態,因此以此為創作靈感來源。取名自老闆的姓名,靈魂魚誕生於虛空之中,由各式各樣的情緒凝結在深海中誕生,他靜靜的在那邊帶來平靜,自己游著,有些靈魂與全身長滿了亮麗的鱗片,有些則默默地緩緩地飄動,身軀幾乎消逝於深海的光線中。

p5.js 靈魂魚實作示範

創作的初始概念,先以 Shader 呈現(下方圖示參考),使用扭曲線條模擬金魚尾巴線條的流動。 Shader 本來是以點到點之間連成的線條組成,在其後,老闆應用 Sin 波的原理,將 Shader 線條隨著波長座移動變化。

再來進到金魚的輪廓姿態,下方第一版的金魚創作其實可以看出大致上金魚的形狀,以及圖樣上模糊的效果,呈現靈魂的感覺。

將金魚的輪廓以線條構出後,發現從身體上半步到尾巴下半部的線條,呈現 Sin 波的形狀,就以多重 Sin 波線條組成第一版金魚。老闆也實際操作一次供大家參考,首先,由左到右畫出連成一條線。

function setup() {
  createCanvas(windowWidth, windowHeight);

  background(100);
}

function draw() {
  background(255);

  beginShape();

  for (x = 0; x < width; x += 20) {
    stroke(0);

    strokeWeight(5);

    vertex(x, height / 2);
  }

  endShape();
}

當線條畫出來之後,如何形成金魚的輪廓形狀呢?由左到右以及上下的的曲線變化,這時就需要加上 Sin 波,呈現出魚的一半輪廓。

function draw() {
  background(255);

  beginShape();

  for (x = 0; x < width; x += 20) {
    let ang = map(x, 0, width, 0, PI * 1.5);

    let y = height / 2 + (sin(ang) * height) / 5;

    stroke(0);

    strokeWeight(5);

    vertex(x, y);

    circle(x, y, 20);
  }

  endShape();
}

得出單一線條後,以相反方向再繪製出另一條線,將完整金魚的輪廓勾勒出來。

function draw() {
  background(255);

  translate(width / 2, height / 2);

  scale(0.8);

  translate(-width / 2, -height / 2);

  for (direction = -1; direction <= 1; direction += 1) {
    beginShape();

    stroke(0);

    noFill();

    strokeWeight(5);

    for (x = 0; x < width; x += 20) {
      let ang = map(x, 0, width, 0, PI * 1.5);

      let y = height / 2 + ((sin(ang) * height) / 5) * direction;

      vertex(x, y);

      circle(x, y, 20);
    }

    endShape();
  }
}
第一版金魚圖示

上述大略是第一版金魚的組成方式,但老闆認為於本身可以再更活潑的方式做律動,於是進行第二版的製作。第二版的示範操作,著重在呈現魚尾巴,如雨水般的流動感外,也將除了輪廓外的線條作範例,呈現擁有底色與動態的尾巴。

function draw() {
  background(255);

  translate(width / 2, height / 2);

  scale(0.8);

  translate(-width / 2, -height / 2);

  for (direction = -1; direction <= 1; direction += 0.2) {
    beginShape();

    stroke(0);

    noFill();

    strokeWeight(5);

    for (x = 0; x < width; x += 20) {
      let ang = map(x, 0, width, 0, PI * 1.5);

      let y = height / 2 + ((sin(ang) * height) / 5) * direction;

      let xx = x;

      if (ang > PI) {
        xx += (ang - PI) * 50;

        xx += sin(xx / 40 + frameCount / 50) * 50;
      } else {
        if (x % 40) {
          arc(xx, y, 100, 100, -PI / 4, PI / 4);
        }
      }

      noStroke();

      vertex(xx, y);

      fill(255, 0, 0, 90);

      arc(xx, y, 5, 5, -PI / 2, PI / 2);

      circle(x, y, 1);
    }

    endShape();
  }
}
第二版金魚圖示

反覆編輯的過程中,從第一、第二版次的基本輪廓呈現,到了第三版大面積的色彩變化(下方圖示參考),以及最後,第四版層疊了鱗片出現發光發亮的質感(下方圖示參考),再繼續堆疊後,變會呈現帶有霧霧的靈魂感覺。這樣的靈魂質感,是如何執行的呢?

第三版金魚圖示
第四版金魚圖示

在第四版金魚上,主要強調使用 blendMode,並進行疊光 screen,就能發現微發光的質感。而後建議以 shader 再去修飾外型,下方程式與圖片為示範範例。

function draw() {
  push();

  beginShape();

  background(0);

  translate(width / 2, height / 2);

  scale(0.8);

  translate(-width / 2, -height / 2);

  blendMode(SCREEN);

  for (direction = -1; direction <= 1; direction += 0.2) {
    noFill();

    strokeWeight(5);

    for (x = 0; x < width; x += 20) {
      let ang = map(x, 0, width, 0, PI * 1.5);

      let y = height / 2 + ((sin(ang) * height) / 5) * direction;

      let xx = x;

      if (ang > PI) {
        xx += (ang - PI) * 50;

        xx += sin(xx / 40 + frameCount / 50) * 50;
      } else {
        if (x % 40) {
          arc(xx, y, 150, 150, -PI / 4, PI / 4);
        }
      }

      noStroke();

      vertex(xx, y);

      fill(255, x / 2, 0, 100);

      arc(xx, y, 5, 5, -PI / 2, PI / 2);

      circle(xx, y, 5);
    }
  }

  pop();
}
第四版金魚圖示

特殊技法介紹

在完成了基本霧面質感金魚後,老闆進到 shader 技法教學。在這邊使用到 shader 的原因是想呈現發亮的螢光感,以及 shader 特有的毛邊質感。在背景中的流體顏色,也是應用 shader去達到此效果,而這樣的概念從老闆早期作品中,就陸續有不同的變化。像是如同宇宙星河般的效果,堆疊的彩色雲層。這樣雲層的概念,又是取自於老闆本身的創作,純粹使用 p5.js 繪製出的雲層效果(下方圖示)

宇宙星河作品變化

從 p5.js 單點渲染,到 shader 即時動態渲染,後面更進化到會滑動的線條,甚至在線條與線條間也出現反光感的效果,呈現細緻,類似地層沉積剖面的質感,也應用這樣的技法呈現在靈魂魚的背景中。

背景範例

老闆也提到,他認為目前應用 shader 最極致的呈現是如同金屬液態的質感創作(下方圖示)

金屬液態圖示

要呈現這樣的效果,就是將背景進行扭曲。像此圖示最初是一張漸層平面圖(下方圖示),形成原理是以多個點點構成一張圖的基底平面,每個點點會有著大小不同的漩渦,再以每個點點在圖上偏移的角度要是多少,與漩渦的方向還有角度構出。

漸層平面圖

不過老闆也說,雖然 shader 很有趣,作品彈性高,但缺點是每次遇到不同型態的需求都會需要重新造輪子,所以目前實際手寫 shader 的人不多,通常都還是以 3D 軟體執行,或是使用 Unreal、Unity 和 shader note editor 等等。以 node based shader editor 為範例,一樣也是經由不同的部分進行疊加,但不一樣的是,已經進行了模組化,讓使用上更快速簡便。

(截圖自 node based shader editor

讓我們重新回到作品靈魂魚的技法上吧!

在創作金魚本體時,我們有使用到一個叫做 blendmode 的融合選項,在 blendmode 中其實有兩種不同的方式能呈現疊色。第一個就是上述有提及的 blendmode(SCREEN),這一項變化就是讓疊色時出現螢光的效果,再來第二個就是 blendmode(MULTIPLY),而這線就是將疊色效果呈現墨水的質感。以下使用範例是以多線條的方式,進行 blendmode(MULTIPLY),所呈現出的細緻金魚。

疊色技法金魚(截圖自影片

以魚鰭來舉例,下方圖示可以看到老闆簡單的說明

金魚魚鰭說明(截圖自影片

魚鰭是依據金魚身體上半部的輪廓點延伸,設定在特定距離上做不同點,再依這些點連成一條又一條組合成魚鰭的線。不過,也因為以線條作為主要組成金魚的來源,所以效能上並不是非常理想。在最終完成品上,可以觀察到,除了金魚本身與線條外,內部也有像是生命線般,會在魚擺動時跟著起舞,這個部分是老闆使用物理模擬操作。在 p5.js 中, Example 中有一個系列叫做 Simulation 物理模擬,在這系列裡,像是 chain,此功能就是將特定點與點之間連成線外,也能進行動態移動,除了作品上,老闆也將其運用在官網上面,將視覺衝擊更加優化。

官網應用(截圖自墨雨設計官網

作品在電腦上完成後,老闆也有將靈魂魚藉由台北藝術中心舉辦的活動,大範圍的投影在藝術中心外,最顯眼的位置做展示。

台北藝術中心展示(截圖自老闆推特

生成式音樂

在圖樣不斷變化的過程中,老闆也在本次創作中增加入生成式音樂做襯托,讓音樂配合游動的靈魂魚產生不同音調。不過生程式藝術結合音樂輔助呈現這個部分,目前是 fxhash 才提供可上架的服務,因為生成式音樂本身檔案較大,像是 artblocks 目前無法接收過大的夾帶檔案。靈魂魚的生成式音樂是將每一節的音樂 sample 檔案與空白格做不同的排列組合,使用 tone.sampler 做完整拼接,拼接完再套用 bpm 讓整段旋律有休止或是不同的音樂變化,最後,再以 reverb 營造具有回音的空間感,讓我們在觀看、收聽時,能夠聽到一段又一段的美妙旋律。

生成式音樂撰寫(截圖自影片

如何將作品上架 fxhash

接下來,我們進入統整上 fxhash 的流程,老闆習慣當作品在 openprocessing 修整到一定程度後,就回到 VsCode 上,使用 fxhash 的模板進行整合。 fxhash 的模板會提供一個固定的亂數,引導你如何使用這套亂數,讓導出的結果都是一樣的(參考連結),而 fxrand 就是提供的亂數名稱,只要給它特定的 hash,就會產生特定的 random。在 VsCode 編輯上,老闆就會針對例如說 features 的這個選項進行編輯。為何是 features 呢?在將生成式藝術上架到 fxhash 時,都會需要標記每一個作品產出的相關特徵,而老闆通常會使用 renderfeatureas() 的方式,將特徵全都渲染出來,但也有時候會因為抓取的值與原本創作呈現的值其實是不同的狀況,這時候就會需要再根據一定的規則算出,我們最終要顯示的是什麼。這樣,在告訴收藏家時,也能以較通俗的說明讓藏家了解作品本身,除了意象外的特徵內容。

fxhash 模板介面(截圖自影片
fxhash 模板 feature 介面(截圖自影片

為了應對較複雜的上架流程,老闆製作了一個名為「previewer」的專案,這個專案可以印出所有生成式藝術作品中的渲染結果。當今天一個作品渲染出了一千張圖片後,這個功能會將這一千張圖片綜合在一個小型網頁上,然後可以在此網頁上進行不同種類的過濾,以進行每一張渲染圖片的檢查。在上架方面,老闆分享了他在fxhash上架時遇到的問題,包括價格、數量和最初設定的販售名單和策略之間的差異,因此他建議同學們在這方面要多加注意。此外,他還提到了作品呈現方面的要求,希望之後製作的每一件作品都能具有足夠的精采度。在前置作業完成後,老闆帶領我們稍微講解了白名單處理流程。在NFT世界中,每當我們購買一件項目,都需要鑄造並購買NFT,但通常NFT在販售時會有數量限制,並非所有人都能鑄造,這就是「白名單」的概念。白名單主要是指只有在名單內的購買方才能鑄造項目,因為他們已經預先保留在名單中了。因此,賣方需要在fxhash上設定白名單流程,首先,要去「mint generation token」瀏覽要上傳的解壓縮檔案,然後按照每個步驟的要求進行上傳或選項勾選,最後,確定上架後就完成了。老闆還建議上架最好一次上完,並且數量控制在256到500之間,這樣對於購買單項作品的買家再繼續購入,帶來的後續效益會更大。老闆也順勢舉例近期執行的「FabDAO 百岳山脈計畫 」,此計畫除了老闆外也與多位藝術家合作,執行公益的 NFT 計畫。在計畫中,老闆的作品便是以 Shader 執行,也示範有無 Shader 的差異。作品內容都十分精彩,有興趣的同學都歡迎至官網做更進一步的了解。

新作品介紹

新作品呈現如同粒子搬形成的圖畫,經由 Shader 再去製造出邊緣不規則的狀態。

最初作品構想(參考連結

作品經反覆編輯製作後,以2D 生成3D 的方向勾勒出虛擬世界感。而此項目將會與老闆致英國作展出。展出的地方名為 Outernet London,主要就是在進行生成式藝術展示的展館,那到時至英國後,老闆也會與共同展出的藝術家們合作交流,後續有任何更新也都會開直播影片與大家分享心得。

預計展出的圖片參考(截圖自影片

總結

在最後也觀眾提出有關近期蔚為風潮,MidJourney – AI 生成藝術的相關問題,老闆也對於這個興起的議題提出了一些看法。其實,不論是生成式藝術與 AI 生成藝術都是經由更快速與便利,與傳統手繪不同的數位化方法進行藝術創作,個人特色或是創意能不能持續表現,才是更值得被記錄的,也希望大家都能勇於嘗試不同的方法做各式各樣的藝術創作探索。

如果想要更深入的了解,目前有與李婷婷講師合開針對 Web3的相關課程,也歡迎直接訂閱老闆的 Youtube 頻道,時不時會有直播 live coding或是近期有持續更新像是 Web3相關影片等等,都歡迎加入並進行踴躍發問喔!

以上就是本次影片和文章的介紹啦!別忘了加入互動藝術程式創作入門課程開始學習吧!還有不要忘了追蹤老闆 Twitter 和訂閱老闆,來點寇汀吧。Boss, CODING please. Youtube 頻道隨時補充新媒體藝術的養份,讓我們一起探索這多元的世界吧!下次見~

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

這篇文章 【p5.js 程式創作】Soul Fish 數位永恆生命的靈魂魚 – 製作流程 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js 程式創作】Soul Fish 數位永恆生命的靈魂魚 – 創作故事 https://creativecoding.in/2023/01/30/soul-fish-story/ Mon, 30 Jan 2023 10:42:09 +0000 https://creativecoding.in/?p=3475 在這篇文章中,哲宇想跟大家介紹為什麼會製作靈魂魚,如何透過多重感官觀賞作品,他的完整故事與設計,以及在視覺跟聽覺和互動上的巧思,曾展出的空間形式和未來期待繼續發展的方向!

這篇文章 【p5.js 程式創作】Soul Fish 數位永恆生命的靈魂魚 – 創作故事 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
本次內容將會以前陣子發布的作品「靈魂魚」作為主題,分為創作故事製作流程分別介紹,此篇主要是製作流程的分享。靈魂魚的作品是如何使用理性的程式創造生命感的探索,也融合了互動生成音樂與視覺,也是在互動型 NFT 邊界上的探索之作 。

靈魂魚的製作過程:【p5.js 程式創作】Soul Fish 數位永恆生命的靈魂魚 – 製作過程

SoulFish #183

大家好,我是生成式藝術家吳哲宇,四五年前我開始製作動態的數位藝術,像是互動網站、平面設計等開始對踏入互動設計的領域,後來研究所到了紐約 NYU 互動媒體學系讀書,因此認識了 Creative Coding 這個領域而深深著迷,發現程式也有很多的可能性,不僅是為了設計或是達成目標而存在,而是寫程式的本身即是藝術

在這篇文章中,我想跟大家介紹為什麼會製作靈魂魚,如何透過多重感官觀賞作品,他的完整故事與設計,以及在視覺跟聽覺和互動上的巧思,曾展出的空間形式和未來期待繼續發展的方向!

Soul Fish Fxhash 作品專案連結 ( https://www.fxhash.xyz/generative/slug/soulfish )

我曾製作了很多互動型態的作品,像是 CryptoPochi 或是 Seahams,也曾在 Foundation 平台上嘗試傳統繪畫手法探索美術館質感的作品,我發現我最喜歡的是能夠製造有機的狀態,意味著作品的本身的隨機性除了表現在初始屬性之外,也隨著動態生長讓作品在執行的過程中有一定的隨機度,例如粒子的軌跡是由每個時刻的亂數累積決定的,最後繪製成一個完整的作品,得益於亂數原理與現在的區塊鏈技術,我們能夠確保在同一個數位作品中,完美而精準的重現隨機過程,這是我覺得最迷人的部分。

永恆的生命是什麼樣子?

剛開始想到製作靈魂魚的時候,我的靈感來自於深海生物,他們的形體透明的浮游在水中,通常會有一些自己的發光器官來照亮周圍的環境,我覺得這樣的有機感很迷人,也很喜歡一些像是光暈、觸鬚般物理上的物理模擬動態,因此想來以深海魚作為主題來創作一個作品,我想要創造出深海中精細結構帶有透明狀態的魚的感覺,這些魚像是靈魂似的漂浮在幽暗的水域中,因此取名為「靈魂魚」,這些魚能夠永恆的存在與游動,不跟著時間的進行而逝去。

靈魂魚其實也同時取名自我自己的名字 – 吳哲宇最後一個字,他們靈魂魚誕生於虛空之中,在各式各樣的情緒凝結,在深海中誕生,他靜靜的在那邊帶來平靜,自己游著,有些靈魂與全身長滿了亮麗的鱗片,有些則默默地緩緩地飄動,身軀幾乎消逝於深海的光線中。

如何去繪製魚柔軟有機的形體呢?

在製作初期,我仔細的觀察後發現,魚的形狀類似於一個波(sin/cos)的前半部所有狀態疊加的形狀,所以如果我把一條波的線條擷取出來,上下透過不同的比例複製重複,就能夠繪製出外觀。

Sin / Cos 波構成魚的線條

過程中除了計算一個一個點的精確位置之外,也確保不會死板的加入了不同頻率,也就是不同大小的波來建構形體,如果加上了時間函數讓他們慢慢的交錯移動,就能夠形成魚的動態質感。每一個點都有額外再經過噪聲跟波形處理,來創造靈魂般不穩定的形體跟質感,最後再加上從身體到尾部的波狀動態,產生出空間中尾巴來回擺動的效果。

使用波狀函數、自然噪聲以及物理模擬

我製作的第一個版本看起來像實心的卡通魚,接下來逐漸改的透明、加上shader背景,讓所有的線條跟筆觸在繪製時,能夠帶有透明度的重疊,也逐漸加入細節 像是魚骨頭、鬍鬚、魚鰭、尾巴等設計,為了創造出有機感,我混合了材質、形狀跟很多的結構設計,包括中心的魚骨頭一節一節的可以動、生命線的鬍鬚是由20幾個點透過鎖鏈物理模擬的形式來控制,與尾巴跟魚鰭的薄膜每一條線條,都透過精密的計算擺動隨機性的設計,每一隻魚都會有不同的線條數量、大小跟身體結構。

顏色設計上,我讓魚可以橫跨所有的光譜,並且在每一隻魚中,都會有類似泡泡表面的色偏質感,也讓他們的顏色更為有機,隨機性的產生了所有的顏色數值之後,我讓他們再對應到相對的顏色的名字。顏色上會有兩個主要的顏色過渡來建構一隻魚的形體,來確保能夠產生和諧漂亮的漸層,我想要作品呈現出類似極光般的質感以及擁有漂亮的光暈,因此用不同的顏色模式重疊了幾次混合,模擬用光線作為筆觸來去繪製靈魂般的小生物

光線色偏的效果 Soul Fish #123

另外也有一些魚是白色背景的,小時候的我很喜歡生物科學,不同於黑色背景螢光的效果,白色的背景更像是顏料暈染,也很像小時候在做標本時,會將植物的組織切片染色,在顯微鏡底下看到的狀態。

Soul Fish #302
SoulFish #3

背景中有什麼樣的巧思?

在靈魂魚的系列中,有三款主要的背景 – 虛空、波動跟深海,大部分的魚都是虛空背景,能夠讓你在投影或黑暗的空間感受時,能最直觀的感受到作品的神聖感,會將焦點全部都放在魚的身上。波動的背景來自於以前的另一個作品 – 星雲 (220413 Nebula Drift),是由 GPU計算所有的星星之後,動態的扭曲來創造流動的銀河。最後一種背景深海,是模擬由上照下的一束光線穿進深海的質感,在者三款背景中,都有額外加入一些灰塵跟懸浮物,會緩緩地在水中漂動,並會跟著音樂閃爍。

220413 Nebula Drift

如何搭配視覺產生有機的聲音系統設計?

在靈魂魚的作品中,聲音是很重要的部分,在前幾個作品如 Sliderverse 或是 Soul sea 中,我都有嘗試使用 Web audio api 的方式來合成聲響,而在 Soul Fish 這個作品中,我使用了一組鋼琴的 Sample,並在作品開始執行的時候,確定這個作品的和弦進行和旋律線,因為是動態生成的音樂,每一隻魚的旋律線都是獨一無二的,會搭配較高的弦律線與較低的伴奏聲音來構築曲子,每一隻魚都是旋律自動機,能夠合成屬於他的曲調。

SoulFish #59

就像是 生成式視覺一樣,生成式音樂的概念鮮為人知,是打散一首歌的長度跟制式編曲,不是根據既有的譜面來演奏,而是透過規則來去建構音符和和弦的進行,因此可以產生永不停止可以持續生成而有機的聲音編制,在 開啟基因 (B) 的模式中,你能夠看到每隻魚都有自己對應的和弦,是自己的旋律不斷的重複的自動機,另外泡泡破掉時,也可以聽到他觸發高音的鋼琴音符,這也對應到我當初對於作品的期望,是能夠創造視覺與聽覺的詩意同步,當這些所有的感官整合起來觀賞作品時能夠有最完整的體驗。

在互動設計中有哪些可以玩的元素?

在製作作品的最後,我加入了游動與泡泡的設計讓藏家能夠去控制魚的游動時快時慢,以及使用鍵盤往特定的方向移動,我搜尋了一些真實的魚的影片,發現他們其實魚鰭跟尾巴在高速游動時都比我預期的快跟靈活,因此加上了游動的機制之後變的很生動,更像自然中魚類運動的樣子。

除了魚的互動之外,泡泡會不斷的從下方冒上來,你能夠使用滑鼠戳破泡泡,或泡泡碰到魚的時候,會同時觸發高音的鋼琴聲,跟背景的音樂呼應。

發售的狀況與後續調整

在 2023/1/16 順利鑄造完售!

我在 2022 五月時發行了靈魂魚的作品,原本計畫是500版次,原本預期會有很多 Hamily 來 mint 所以調整成 1000 版,近期調整回 400 版確保作品的獨特性,雖然在過去的一年中沒有馬上 mint out,隨著時間過去,終於在 2023 Jan 16, 02:53:29 PM 鑄造完畢,達成了里程碑!

此外,我也曾受邀在台北表演藝術中心的大圓球建築物上投影靈魂魚,在大街上看到魚在圓球上游泳很像實體世界的巨型魚缸。我於台北101的個展 – 「混沌實驗室」中有展出,投影在沈浸式的空間中,沐浴在像是極光般的靈魂魚質感時很浪漫。

2022/5 月在台北 101 的個展 「Chaos Laboratory

除了單件作品的展示之外,我也很喜歡讓靈魂魚以群體的方式呈現,因此後續希望能夠在線上做一個虛擬的水族箱,一個發光的海底世界,並且結合場域投影讓魚有機會再實體空間持續存在與游動,如果能夠結合實體場域,製作一個全互動式的虛擬投影空間,讓人可以彷彿置於深海的體驗黑暗中的光輝,一定會非常感動。

如果你對更多的創作過程有興趣,可以看之前的靈魂魚製作分享直播!

這篇文章 【p5.js 程式創作】Soul Fish 數位永恆生命的靈魂魚 – 創作故事 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
初次見面請多指教!Creative Coding第一次社群聚(上)吳哲宇的生成式藝術宇宙 https://creativecoding.in/2022/05/06/creative-coding-meetup-202204-cheyu-wu/ Fri, 06 May 2022 02:59:00 +0000 https://creativecoding.in/?p=2639 第一次的社群聚,召集人吳哲宇破題開講 Creative Coding 以及 Shader 在生成式藝術領域的相關應用,分享他經典的NFT作品,最後也帶來他為這次的共創主題<宇宙>的新作

這篇文章 初次見面請多指教!Creative Coding第一次社群聚(上)吳哲宇的生成式藝術宇宙 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
2022年4月14日這個重要的日子必須紀念一下,因為喜愛生成式藝術的 Creative Coding Taiwan 社群,大家終於在臺北實體見面啦!

Creative Coding Taiwan 社群大合照
Creative Coding Taiwan 社群大合照

Creative Coding Taiwan 是由墨雨互動設計所發起與支持的社群,旨在台灣推廣互動程式設計以及任何由程式創作衍生出來的藝術作品及行為。之前一直都是在線上直播、線上課程,接下來我們希望藉由每個月的固定聚會,舉辦各種不同類型的活動,讓喜歡寇汀的大家互相交流,分享自己的創作與想法,技術上有任何不懂的,現場說不定也會有很多大神出沒可以討教哦!

什麼是 Creative Coding?

Creative Coding 顧名思義是結合程式加創意,狹義上是用程式寫一些看起來很有藝術感的圖形,廣義上是可以用程式,將世界上的各種事物賦予互動跟連結起來,創造出新的現象、關聯與有趣的呈現。用程式偵測走在路上的人群,轉換成五線譜之後播放成音樂,或是比較商業的演唱會背景,擷取歌手的影像做後製,結合動態設計,變成即時的互動背景,跟著聲音的大小、人的位置、時間等做變化,這些都是程式創作能應用的範疇。近期由於 NFT 生成藝術平台 Art Blocks 的興起,也讓更多人注意到了生成藝術與 Creative Coding 的魅力與各種可能性!

因為篇幅關係,此次的內容將這次將近兩小時的直播影片拆成上、中、下三篇,首先由吳哲宇破題開講 Creative Coding 以及生成式藝術的相關應用,最後也帶來他為這次的共創主題<宇宙>的新作(影片同時服用);中篇由叁式互動新媒體的技術美術Hoba帶來演算視覺的應用,不藏私揭露叁式近年來精彩的作品幕後祕辛(影片從這裡開始看),以及另一位與會的創作者張文瀚帶來作品 <Wormhole>分享(看看作者的詮釋及解析);下篇則是由 FAB DAO 的黃豆泥讓大家一窺<沒有市場的靈魂綁定NFT>又是怎麼一回事(影片從這裡開始看),以及第三位創作者 Mizok 帶來他的<Universe-alpha>及<Universe-beta>(作者展示)。

延伸閱讀:
初次見面請多指教!Creative Coding第一次社群聚(中)Hoba與叁式的演算視覺邁進之路
初次見面請多指教!Creative Coding第一次社群聚(下)黃豆泥:沒有市場的靈魂綁定NFT

墨雨設計創辦人 吳哲宇 <Creative Coding 是甚麼?Shader與Generative Art 應用>

對藝術家吳哲宇,也是我們口中的老闆來說, Creative Coding 程式創作是結合設計、工程、數學、動態、程式邏輯甚至是硬體的一種藝術創作形式,把程式當作畫筆,搜尋喜歡的主題,利用一行行的程式碼將世界上的各種事物連結起來、賦予互動,以邏輯思維創造出新的現象、關聯,編織成有趣互動的成果呈現。例如下方這一個神祕的生物,僅僅是用不到二十行的程式碼寫成。

哲宇用簡單算式程式碼完成的神祕生物
哲宇用簡單算式程式碼完成的神祕生物

很多人都會說你視覺要看起來有藝術感才能算是藝術。但哲宇覺得, Creative Coding 本身程式的運作以及邏輯概念是藝術的展現,Creative Coding 的魅力也正在此。

進入程式創作的世界,常使用的軟體包括 Unreal 以及 Unity 兩套3D遊戲開發軟體及引擎,MaxMSP 是一款圖形化編程語言的軟體工具,還有主流創作的 Open Frameworks 和 Touch Designer,有興趣的都可以玩玩看。

想必有許多人都是透過 p5.js 生成式藝術創作認識哲宇以及 Creative Coding 的,2020年因為疫情,哲宇而開啟了一天一 p5.js 創作的計畫,最初的起點其實都很簡單,利用三角形、圓形、線條、色彩等不同的主題,從生活中取材,隨意實驗,慢慢發展出有趣的圖像以及互動小品。包括用物理模擬做成軟軟的生物、魚鉤釣魚,或是童趣的蝸牛、用 shader 寫 DNA 等。

常使用的平台是 Open Processing 這個線上編輯器,修改程式碼能即時看到成果。 p5.js 是個入門好上手的程式語言,只要花個一週左右的時間就可以做出有趣的作品,尤其是即時視覺回饋的有趣,足以沖淡一些學習或是撰寫程式的痛苦。

p5.js 生成式藝術創作的過程其實跟一般藝術創作的過程相似,從最初始的動機,也許是個視覺想法(波浪?)或是某個靈感開始,想像不同的可能性(大小、顏色、樣態、互動等等)將草圖逐步建置成形,中途來回不停修改,像調配藥水一樣嘗試各種排列組合、疊加變形,甚至有時會有一些意料之外的驚喜,最後完成作品。

生成式藝術的核心

常看哲宇直播的人,或是 Creative Coding 台灣站的忠實讀者大概可以發現,其實生成式藝術,或說用程式創作藝術,重要的概念可大致分為以下:

  • 規則:大自然中有法則,透過探索、模仿,建立規則而非框架。
  • 限制:有限制才能激發更多的創意以及自由。
  • 核心概念及故事:也是創作的動機,賦予作品更多意義。
  • 視覺呈現:加上材質、小動態,用心雕琢細節是畫龍點睛的關鍵。
  • 變化:一成不變讓人無趣,無論是透過時間、
  • 隨機性:加入一點 random 一點 noise,讓人摸不透規則更好奇。
  • 輸入/輸出:輸入包含了影像、音像,滑鼠點擊或移動,其他外部資料或是時間;輸出則是以視覺、聲音及動態(也可能是硬體)為三個主要的方向。

像是哲宇之前利用骰子骰出來的數字控制檯燈的作品;或是將 Unreal 結合動態捕捉系統,即時接收舞者(也就是哲宇本人)的動作並顯示在四周的投影牆上,再加上物理引擎模擬,碰撞、擾動虛擬世界中的圖像,所完成的現場即時表演,這些都算是互動藝術。無論是在 p5.js 或是 Unreal 等平台,程式創作和互動的邏輯都是可以互通的。

NFT作品分享:SoulSea 以及 CryptoPochi

如果對於哲宇在 NFT 的心路歷程有興趣,歡迎右轉閱讀【老闆週六來聊聊】我開始賣NFT作品啦!生成式藝術在NFT的價值,這邊就不再多述,僅挑出兩件特別喜歡的作品跟大家分享。

哲宇在fxhash上發行的SoulSea系列作品
哲宇在fxhash上發行的SoulSea系列作品

第一件是發布在 fxhash 的 SoulSea 系列作品,其實是在做商業案子的時候,哲宇發現太漂亮到捨不得給別人,所以就自己上架。作品使用 p5.js 製作出噪聲和波形,模擬海洋的質感,從一條波浪,在多個地方疊加上 noise 產生連續且自然的波紋,加上材質等小元素,其中牽涉到了對數學、形狀的掌握度,製造出接近生活體驗、有機體的東西。

哲宇在OpenSea上發布的互動型NFT <CryptoPochi>
哲宇在OpenSea上發布的互動型NFT <CryptoPochi>

第二件是發布在 OpenSea 的 CryptoPochi ,也是一個互動型的 NFT,結合區塊鏈以及網頁的技術,以網頁的形式呈現,並使用物理模擬引擎與 Artblocks 製作互動,隨時都可以透過滑鼠去玩他,甚至製作了 On Chain Event 觸發同步事件,可以讓不同作品內的 Pochi 互相互動。

如果你是工程師,鼓勵你多看一些作品,會有一些想法和創意開始踏出利用程式碼創作的第一步;如果你是設計師,便可以從熟悉 p5.js 語法開始,慢慢地往生成式藝術的方向邁進。

在這裡也要推薦一下在HaHow好學校的〈互動藝術程式創作入門〉課程。大約兩年前開這堂線上課程時,還沒有很多人知道 Creative Coding 這個領域,如今隨著元宇宙、NFT成為夯字,也越來越多人投入。

哲宇不藏私,分享他在創作的同時,也持續思考作品商業的潛力,除了持續創作、賣 NFT 之外,另外跟很多品牌合作,從品牌的角度出發,例如跟漢堡店合作,將漢堡層層拆分,以程式藝術手法詮釋。未來硬體也會越來越強大、跑得動複雜的程式碼,之後許多公司品牌可能都會開始發展動態或是 3D 的視覺以及衍伸行銷,大家也可以想想看新媒體藝術有哪些商業應用。哲宇也趁機苦口婆心地以過來人身分勸大家不要賣自己作品的授權,因為哲宇之前曾遇過,授權給對方使用他的畫作,對方還反寄律師函回來告的情形。

Shader 與 Generative Art 的應用

哲宇用Shader創作出來的生成式藝術
哲宇用Shader創作出來的生成式藝術

Shader 就是執行在GPU上的電腦程式,控制著GPU渲染管線,處理開發者給他的各種繪圖相關的資料,即使是畫一個點、一個顏色、一個向量,也要透過 Shader 渲染。 Shader 就像是 Creative Coding 的進階版,將原本在 3D 軟體內才能看到的材質,運用 Shader 做出來。

GLSL(OpenGL Shading Language) 全稱 OpenGL 著色語言,是一個以C語言為基礎的高階著色語言,用來在OpenGL 中著色編程,這個語言的好處是因在 shader 裡面全部的東西以像素為單位做運算,若你的 GPU 越強,就能渲染出越精細漂亮的畫面,而且效能也很好。如果有興趣的話,也可以參考一些哲宇之前使用相關語言做的作品:

哲宇的作品 The Soul
哲宇的作品 The Soul

舉一件自己的作品當例子,看起來像瞳孔的這幅作品 The Soul,程式碼就非常單純,計算離中心的角度,算出像是類似山脈的偏移效果,再把顏色畫出來,從內到外,紅色是離中心距離的兩倍、綠色是離中心距離的三倍,就可以呈現這種類似極光、光譜分散的效果。

大家有興趣都可以去 fork 哲宇的 shader template (連結這裡走,不可商用、需標記來源),裡面已經準備好 shader 裡基礎的東西,從這個環境開始,可以快速完成你自己的 shader 作品.或是你可以從改一些範本中的不同參數,看會製造出甚麼樣的效果開始。

另外也想跟大家介紹一個很酷的學/寫程式的好用工具 VSCode,你可以去申請 Copilot 的功能,他可以解釋程式碼在做些甚麼,假設我這邊有一串 shader 或是任何其他語言的程式碼,選取後點選 Ask Copilot 就會出現解釋,他甚至會去猜你的變數在幹嘛,譬如說這個 p 可能是位置等。在撰寫程式碼的時候也會有一個自動輸入功能,你只要起頭,他就會幫忙填完 function 和 name 甚至是後面的程式碼,也可以把寫好的程式碼轉換成各式各樣的語言。

若想了解更多有關 shader 的知識,可以參考 TheBook of Shaders https://thebookofshaders.com/,一步步帶你了解如何使用 Fragment Shaders 開始做幾何圖形,從像素開始建構圖像、 3D 等。

Q:整包 HTML 如何上傳變成 NFT?

A:可以用 fxhash 這個平台,將你的程式碼打包成一個 zip 檔案丟上去就可以了,需要注意的是要使用官方提供的 fxhash-snippet 把每個 NFT 獨立的序號生成一個 random object(fxrand),這個 random 就能夠自行產生你在作品中所需要用到的亂數,只要作品序號相同,亂數就是一樣的。把這個部分丟進去、順利跟 fxrand 對接之後,就可以產生很多獨一無二的 NFT 了。

參考:Guide to mint a token

截圖自Guide to mint a token

Lightning talk 2 – 吳哲宇

社群聚當天,最後的一個小環節是 Lightning Talk,每一次的 Creative Coding Taiwan 聚會都會事先提供一個主題,邀請大家趁著這個機會創作出一件互動藝術作品並與大家分享。在一次又一次的共創過程中,激盪出更多更有趣的火花。

哲宇為這次的共創主題<宇宙>所作的創作 <Nebula Shift>
哲宇為這次的共創主題<宇宙>所作的創作 <Nebula Shift>

作品連結:https://openprocessing.org/sketch/1543928

用 Shader 寫的作品,移動鼠標不只有星雲飄動,還有星星閃爍。將粒子的資料輸入進 Shader 裡使用,透過 p5.js 使用多層 perlin noise 與一般的 noise 兩種不同的躁聲做扭曲,呈現出線條流動之感,另外也須計算點跟星星的距離,完成不同明暗程度的閃爍以及穿透霧氣的感覺,將 RGB 分層處理,因為光譜頻率偏差,便會有如色散的效果呈現。

由哲宇創辦的墨雨設計也持續地經營 Creative Coding Taiwan 互動程式藝術創作台灣站,無論是 p5.js 的實作教學,或是你該怎麼入門、去哪裡找資源,都可以從這邊看起。歡迎各方好手加入,一齊充實內容!


最後小小工商時間,我們第一次的社群聚會現場也請到了 AWS Activate Taiwan 的 Alex Cheng 帶著最正宗的韓式炸雞來到現場,以及分享給新創公司的好康,無論你是剛起步的小團隊,或是已經加入育成中心、新創加速器的新創公司,使用 AWS 升級服務可以獲得 US$1,000 的免費積分!有興趣的人歡迎掃瞄下方 QR Code 了解更多。

這次 Creative Coding Taiwan 四月社群聚,每位講者都帶著滿滿的知識和熱忱分享,若是你對哲宇的生成式藝術感到意猶未盡,那你一定要繼續看下去:
初次見面請多指教!Creative Coding第一次社群聚(中)Hoba與叁式的演算視覺邁進之路
初次見面請多指教!Creative Coding第一次社群聚(下)黃豆泥:沒有市場的靈魂綁定NFT

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

整理編輯:Chia 編

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 初次見面請多指教!Creative Coding第一次社群聚(上)吳哲宇的生成式藝術宇宙 最早出現於 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 - 互動程式創作台灣站

]]>
【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 - 互動程式創作台灣站

]]>