本文代碼見:https://github.com/DesertsX/p5js-zju126
又是一年浙大校慶時
第一篇 Three.js Shader 教程總算本周上周更新了骇塘,想著在更新這個系列的同時穿插寫些簡單輕松的內容蝎宇,填些之前留下的小坑瑞筐。
今天昨天前天是2023年5月21號珍剑,是浙大126周年校慶的日子笨蚁。想到去年125周年校慶官方制作了個可以輸入校友專業(yè)和名字么抗、認證通過后會生成每個人專屬的由“燦若繁星”的浙大人姓名的粒子系統(tǒng)組成的“求是星海”
的網(wǎng)頁矮固。(網(wǎng)頁還在,但需要輸入信息才能生成譬淳,所以大家看不到档址,這里錄個視頻供大家觀賞下,可參見原文里的視頻內容:「用 p5.js 實現(xiàn)圖片組成的文字效果(含2D和3D版本的代碼) - 牛衣古柳」)
當時古柳剛接觸 Three.js Shader 沒多久邻梆,看到這個作品后就用 Yuri 油管視頻里提到的 Spector.js
插件看了下該網(wǎng)頁的 Shader 代碼守伸,見到熟悉的 Vertex Shader 頂點著色器、熟悉的 Fragment Shader 片元著色器浦妄,果然就是粒子系統(tǒng)尼摹。
記得那會古柳一方面感慨接觸 Shader 后,“孕婦效應”再次顯現(xiàn)剂娄,到處都能看到 Shader 的應用蠢涝,這次校慶里居然就有“送上門”的;一方面覺得這些人怎么都會 Shader阅懦,仿佛就自己沒學會和二,非常沮喪。
不過說起來去年比較特別耳胎,印象里前些年以及今年都沒有類似專門做個網(wǎng)頁效果的惯吕,一般都是文章或視頻的形式惕它。
帝國大廈亮燈祝賀
之所以提這件事,是古柳想到更早之前2017年120周年校慶那時废登,本來碰上這種滿十年的年份就感覺非常特殊怠缸、更為熱鬧,而當時更有美國紐約帝國大廈專門為浙大120周年校慶亮燈這則“大新聞”钳宪,說可能是帝國大廈有史以來第一次為中國國內大學校慶而亮燈揭北,就很有排面。
畢竟素來幾所985高校間喜歡爭論誰才是中國內地Top3的大學吏颖,并且各自喜歡用各種排行榜里自己的好名次給自己臉上“貼金”搔体。難得讓我等浙大師生或校友遇上這么個“有史以來第一次為中國國內大學校慶而亮燈”的“大噱頭”,可不得“奔走相告”半醉。
而那會剛自學 Python 編程和爬蟲沒多久的古柳也想做點什么應個景疚俱、湊個熱鬧,于是想到可以爬取帝國大廈官網(wǎng)的過往亮燈圖片來拼個 "ZJU 120" 的字樣缩多。
說干就干呆奕,當時帝國大廈的官網(wǎng)可以選擇日期以顯示當天的亮燈圖片,直接遍歷日期用 post 請求就能爬取圖片衬吆。原本古柳以為是每天專門拍攝的照片梁钾,后來發(fā)現(xiàn)沒爬多少就重復了,其實沒多少不同的圖逊抡。
有了圖片后姆泻,古柳在隨手拿的餐巾紙上畫了下“ZJU 120”字樣以確定應該如何布局。
然后古柳用 Python 里的 PIL 庫手動將每張圖片放到文字的位置上冒嫡,當時用的還是笨辦法手動存了每個位置坐標拇勃,但能實現(xiàn)出來就行,本文將介紹個簡單直觀的方法孝凌。
最后分別用單張圖片和不同圖片排列出文字方咆,效果對當時的自己來說還是很酷的。
p5.js 復現(xiàn)上述效果
交代完上述前因后果蟀架,其實本文的目的就是用 p5.js 復現(xiàn)下當初的圖片拼湊出文字的效果瓣赂。
雖然之前古柳幾乎僅在「伴隨 P5.js 入坑創(chuàng)意編程 - 牛衣古柳 - 2019.06.28」一文提到 p5.js,但這個庫真的很簡單辜窑,即便是藝術家钩述、設計師、編程小白都能輕松上手穆碎,而且拿來做創(chuàng)意編程、生成藝術职恳、NFT 作品所禀、數(shù)據(jù)可視化方面、WebGL 3D、Shader 編程色徘、AR/VR/XR 等都可以恭金。
p5.js 有多簡單,讓我們一起看看褂策。首先引入 p5.js 的庫横腿;接著一般需要在 setup()
函數(shù)和 draw()
函數(shù)里書寫代碼,兩者都會被 p5.js 自動執(zhí)行斤寂,前者只執(zhí)行一次耿焊,可以初始設置一些內容,比如設置畫布大小遍搞,后者會被反復調用執(zhí)行罗侯,比如一般電腦60FPS幀率就是每秒執(zhí)行60次,可以實現(xiàn)動畫溪猿、交互钩杰。因為這里僅靜態(tài)地展示圖片無需動態(tài)效果,所以只需在 setup() 里實現(xiàn)即可诊县。
下面我們在 400x400 淺綠色背景的畫布上繪制一個深綠色填充且無描邊的矩形讲弄,其位置在(50, 50)處、寬高為(100,100)依痊,可以看到代碼非常的直觀垂睬,簡直和用 PS/AI 等軟件工具直接畫一個矩形一樣簡單。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>p5.js 實現(xiàn)圖片組成的文字效果</title>
<style>
body {
margin: 0;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js"></script>
</head>
<body>
<script>
function setup() {
createCanvas(400, 400);
background('#e6fcf5');
noStroke();
fill('#0ca678');
rect(50, 50, 100, 100);
}
</script>
</body>
</html>
借助二維數(shù)據(jù)顯示特定形狀
繪制出一個矩形后抗悍,我們如何將一堆矩形繪制出特定的形狀或文字驹饺?這里介紹下古柳之前學 Canvas
時從 Coding Math
這個系列教程里學到的一招,即在二維數(shù)組直接存儲所需形狀格式的數(shù)據(jù)缴渊。
比如同樣的 400x400 畫布赏壹,假如我們將其劃分成5x5的網(wǎng)格,一個單元格就是80x80衔沼,那么我們可以直接在5x5的二維數(shù)組里以下面的格式將我們想在哪些位置繪制矩形用不同數(shù)字進行區(qū)分蝌借,1就是繪制,0就是不繪制指蚁,這樣這個數(shù)組就和實際想繪制的形狀非常直觀的對應上菩佑,比如下圖繪制的“X”形狀。
注意這里矩形寬高 (width / row.length, y * height / grid.length)
就是80x80凝化,直接固定寫死也問題不大稍坯,width 和 height 是創(chuàng)建畫布后就能拿到的畫布寬高,分別除以列數(shù)行數(shù)就是每個單元格的大小。
const grid = [
[1, 0, 0, 0, 1],
[0, 1, 0, 1, 0],
[0, 0, 1, 0, 0],
[0, 1, 0, 1, 0],
[1, 0, 0, 0, 1],
];
function setup() {
createCanvas(400, 400);
background('#e6fcf5');
noStroke();
fill('#0ca678');
// rect(50, 50, 100, 100);
for (let y = 0; y < grid.length; y++) {
const row = grid[y]
for (let x = 0; x < row.length; x++) {
if (row[x] === 1) {
rect(x * width / row.length, y * height / grid.length, width / row.length, height / grid.length);
}
}
}
}
在 grid 里調整形狀非常方便瞧哟,比如很簡單就能變成“回”字形狀混巧,再也不用很笨的去數(shù)每個位置具體的坐標然后手動繪制。
const grid = [
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
[1, 0, 1, 0, 1],
[1, 0, 0, 0, 1],
[1, 1, 1, 1, 1],
];
用圖片替換矩形
接著我們用圖片替換矩形勤揩,在 p5.js 的 setup() 函數(shù)之前的 preload()
函數(shù)里通過 loadImage()
方法加載圖片咧党,然后在 setup() 里通過 image()
方法將圖片在對應位置以特定寬高放置即可,這里 image() 后四個參數(shù)和 rect() 一樣陨亡,且因為用的就是 1:1 的圖片所以直接替換即可傍衡。
const grid = [
[1, 0, 0, 0, 1],
[0, 1, 0, 1, 0],
[0, 0, 1, 0, 0],
[0, 1, 0, 1, 0],
[1, 0, 0, 0, 1],
];
let img;
function preload() {
img = loadImage('./images/0.jpeg');
}
function setup() {
createCanvas(400, 400);
background('#e6fcf5');
noStroke();
fill('#0ca678');
for (let y = 0; y < grid.length; y++) {
const row = grid[y]
for (let x = 0; x < row.length; x++) {
if (row[x] === 1) {
// rect(x * width / row.length, y * height / grid.length, width / row.length, height / grid.length);
image(img, x * width / row.length, y * height / grid.length, width / row.length, height / grid.length);
}
}
}
}
如果需要加載多張圖片,只需要放進數(shù)組里负蠕,使用時按索引獲取對應圖片即可蛙埂。
// let img;
const num = 9;
const images = [];
function preload() {
// img = loadImage('./images/0.jpeg');
for (let i = 1; i <= num; i++) {
const img = loadImage(`./images/${i}.jpeg`)
images.push(img);
}
}
最終的“ZJU 126”效果
最后,只需套到實際圖片數(shù)據(jù)集上和基于想要的文字效果去設置 grid 即可虐急。這里因為帝國大廈官網(wǎng)的圖片寬高不一箱残,而本文只是演示如何復現(xiàn),就不去手動剪裁圖片了止吁,有些拉伸變形無關緊要被辑,簡單設置繪制的圖片高度 h 為寬度 w 的1.5倍。然后設置 grid 成 “ZJU 126” 的字樣敬惦,行列數(shù) rows cols 隨之確定盼理,然后畫布寬高大小也能確定,最后就是遍歷繪制9張亮燈圖里的任意一張即可俄删。
const num = 9;
const images = [];
const w = 40;
const h = w * 1.5;
const grid = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// ZJU
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0],
//
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 126
[0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
//
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
const rows = grid.length;
const cols = grid[0].length;
function preload() {
for (let i = 1; i <= num; i++) {
const img = loadImage(`./images/${i}.jpeg`);
images.push(img);
}
}
function setup() {
createCanvas(cols * w, rows * h);
background(10);
// let i = 0;
for (let y = 0; y < rows; y++) {
const row = grid[y];
for (let x = 0; x < cols; x++) {
if (row[x] === 1) {
// const img = images[i % images.length];
const img = random(images);
image(img, x * w, y * h, w, h);
// i++;
}
}
}
}
3D 效果同樣可以
以上的實現(xiàn)宏怔,是不是很簡單,而且用 grid 來擺放圖形元素如此方便畴椰,古柳想到同樣可以在 3D 里擺出立體字的效果臊诊,之所以有這個想法,是因為一直記得五十嵐威暢的這張海報斜脂,覺得蠻漂亮的抓艳。
- 鏈接:五十嵐威暢:只為結構動情
因此古柳簡單地切換到 p5.js 的 WEBGL 模式,然后在每個位置放上 box() 立方體帚戳,并將圖片貼上去玷或,在正交相機下做出2.5D效果如圖所示。大家覺得是上面2D的好看還是這個3D的好看呢片任?
這部分代碼就不做解釋了偏友,3D WEBGL 的東西解釋起來也麻煩些,大家可自行學習对供,此處僅供參考位他。
const num = 9;
const images = [];
const w = 40;
const h = w * 1.5;
const grid = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// ZJU
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0],
//
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// 126
[0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0],
//
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
const rows = grid.length;
const cols = grid[0].length;
function preload() {
for (let i = 1; i <= num; i++) {
const img = loadImage(`./images/${i}.jpeg`);
images.push(img);
}
}
function setup() {
createCanvas(cols * w, rows * h, WEBGL);
background(10);
// background('#e6fcf5');
// camera 位置、朝向、自身的法向量方向棱诱,需要3個向量泼橘、9個坐標來確定
camera(200, -400, height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0);
ortho(-500, 500, -500, 500, 0.1, 2000);
let i = 0
for (let y = 0; y < rows; y++) {
const row = grid[y];
for (let x = 0; x < cols; x++) {
if (row[x] === 1) {
push();
noStroke();
translate((x - cols / 2) * w, (y - rows / 2) * h, -10);
// const img = random(images);
const img = images[i % images.length];
texture(img);
box(w, h, h);
pop();
i++;
}
}
}
}
照例
最后和古柳交流涝动,也請多點贊支持迈勋!