前言
到目前為止,我們的所有作品都是在二維平面上的。但 p5.js 其實有一個強大的 3D 模式——只要在 createCanvas() 加上 WEBGL 參數,你就進入了一個全新的三維世界。
3D 模式打開了許多二維做不到的可能性:旋轉的幾何體、光影效果、深度感、透視投影。你可以用它來做 3D 的生成式藝術、互動雕塑、資料視覺化,甚至簡單的 3D 場景。
說實話,我一開始對 p5.js 的 3D 功能抱著「玩具」的心態——畢竟要做 3D,大家第一個想到的是 Three.js 或 Unity。但實際用過之後,我發現 p5.js 的 WEBGL 模式在「快速原型」和「藝術表達」方面有獨特的優勢:它的 API 保持了一貫的簡潔風格,讓你專注在創意而不是技術細節。
這篇文章會帶你進入 p5.js 的 3D 世界:基本幾何體、相機控制、光源設定、材質應用。
createCanvas WEBGL
進入 3D 模式只需要一個參數:
function setup() {
createCanvas(800, 600, WEBGL);
}
function draw() {
background(30, 30, 40);
// 畫一個旋轉的方塊
rotateX(frameCount * 0.01);
rotateY(frameCount * 0.013);
fill(255, 100, 100);
stroke(255);
box(150);
}
WEBGL 模式有幾個重要的差異:
- 座標原點在畫布中央(不是左上角)
- Y 軸方向朝下(跟 2D 模式一樣)
- Z 軸指向你(朝螢幕外面)
- 有深度緩衝(遠的物體會被近的擋住)
-Y (上)
|
|
+------ +X (右)
/
/
+Z (朝你)
3D 基本幾何體
p5.js 提供了幾個內建的 3D 幾何體:
function draw() {
background(30, 30, 40);
lights(); // 先加個基本光源
// 方塊
push();
translate(-250, -100, 0);
rotateY(frameCount * 0.02);
fill(255, 100, 100);
box(100); // box(寬, 高, 深) 或 box(大小)
pop();
// 球體
push();
translate(0, -100, 0);
rotateY(frameCount * 0.02);
fill(100, 255, 100);
noStroke();
sphere(60); // sphere(半徑, 細分X, 細分Y)
pop();
// 圓柱
push();
translate(250, -100, 0);
rotateX(frameCount * 0.02);
fill(100, 100, 255);
cylinder(40, 120); // cylinder(半徑, 高度)
pop();
// 圓錐
push();
translate(-250, 150, 0);
rotateX(frameCount * 0.02);
fill(255, 200, 100);
cone(50, 120); // cone(半徑, 高度)
pop();
// 圓環
push();
translate(0, 150, 0);
rotateX(frameCount * 0.02);
fill(200, 100, 255);
torus(60, 20); // torus(外半徑, 管半徑)
pop();
// 平面
push();
translate(250, 150, 0);
rotateX(-0.5);
fill(100, 200, 200);
plane(120, 120); // plane(寬, 高)
pop();
}
自訂幾何體
用 beginShape() 和 vertex() 建立自訂的 3D 形狀:
function draw() {
background(30, 30, 40);
lights();
rotateX(frameCount * 0.01);
rotateY(frameCount * 0.015);
// 金字塔
fill(255, 180, 50);
stroke(255);
strokeWeight(1);
let s = 100;
let h = 150;
// 四個側面
beginShape(TRIANGLES);
// 前面
vertex(0, -h/2, 0);
vertex(-s/2, h/2, s/2);
vertex(s/2, h/2, s/2);
// 右面
vertex(0, -h/2, 0);
vertex(s/2, h/2, s/2);
vertex(s/2, h/2, -s/2);
// 後面
vertex(0, -h/2, 0);
vertex(s/2, h/2, -s/2);
vertex(-s/2, h/2, -s/2);
// 左面
vertex(0, -h/2, 0);
vertex(-s/2, h/2, -s/2);
vertex(-s/2, h/2, s/2);
endShape();
// 底面
beginShape();
vertex(-s/2, h/2, s/2);
vertex(s/2, h/2, s/2);
vertex(s/2, h/2, -s/2);
vertex(-s/2, h/2, -s/2);
endShape(CLOSE);
}
Camera 控制
在 3D 場景中,「從哪裡看」跟「看什麼」一樣重要。
基本相機
function draw() {
background(30, 30, 40);
// camera(眼睛x, 眼睛y, 眼睛z, 看向x, 看向y, 看向z, 上方x, 上方y, 上方z)
camera(
200 sin(frameCount 0.01), // 眼睛 x:繞圈
-150, // 眼睛 y:在物體上方
200 cos(frameCount 0.01), // 眼睛 z:繞圈
0, 0, 0, // 看向原點
0, 1, 0 // 上方是 Y 正方向
);
lights();
// 地面
push();
translate(0, 50, 0);
rotateX(HALF_PI);
fill(50, 50, 60);
noStroke();
plane(400, 400);
pop();
// 物體
fill(255, 100, 100);
noStroke();
box(80);
}
orbitControl 互動相機
p5.js 有個很方便的函數,讓你用滑鼠旋轉、縮放、平移相機:
function draw() {
background(30, 30, 40);
orbitControl(); // 就這一行!
lights();
// 左鍵拖曳:旋轉
// 右鍵拖曳:平移
// 滾輪:縮放
fill(255, 100, 100);
box(100);
push();
translate(200, 0, 0);
fill(100, 255, 100);
sphere(50);
pop();
}
透視 vs 正交投影
function draw() {
background(30, 30, 40);
// 透視投影(預設)— 有近大遠小效果
perspective(PI / 3, width / height, 0.1, 5000);
// 正交投影 — 無透視效果,像技術製圖
// ortho(-width/2, width/2, -height/2, height/2, 0.1, 5000);
orbitControl();
lights();
// 排列多個方塊,觀察透視差異
for (let z = -500; z <= 500; z += 200) {
push();
translate(0, 0, z);
fill(map(z, -500, 500, 100, 255), 100, 100);
box(80);
pop();
}
}
光源設定
光源是 3D 場景的靈魂。沒有光,所有物體都是平的色塊。p5.js 提供了幾種光源類型:
環境光(Ambient Light)
均勻地照亮所有表面,不產生陰影或高光:
ambientLight(80, 80, 80); // 淡灰色的環境光
方向光(Directional Light)
平行光,像遠處的太陽:
// directionalLight(顏色, 方向)
directionalLight(255, 255, 255, 0.5, 1, -0.5);
// 從右上方照下來
點光源(Point Light)
從一個點向四面八方發光,像燈泡:
// pointLight(顏色, 位置)
pointLight(255, 200, 100, 200, -200, 200);
聚光燈(Spot Light)
錐形光束,像舞台燈:
// spotLight(顏色, 位置, 方向, 角度, 衰減)
let spotX = 200 sin(frameCount 0.02);
spotLight(
255, 255, 255, // 白光
spotX, -300, 200, // 位置
0, 1, 0, // 方向(朝下)
PI / 6, // 錐角
5 // 衰減
);
綜合光源範例
function draw() {
background(20, 20, 30);
orbitControl();
// 微弱的環境光
ambientLight(30, 30, 40);
// 暖色的點光源(跟著時間移動)
let lx = 200 sin(frameCount 0.02);
let lz = 200 cos(frameCount 0.02);
pointLight(255, 180, 100, lx, -150, lz);
// 冷色的方向光
directionalLight(100, 150, 255, -0.5, 0.5, -1);
// 用小球標記點光源位置
push();
translate(lx, -150, lz);
fill(255, 180, 100);
noStroke();
emissiveMaterial(255, 180, 100);
sphere(10);
pop();
// 地面
push();
translate(0, 100, 0);
rotateX(HALF_PI);
fill(80, 80, 90);
noStroke();
plane(600, 600);
pop();
// 物體們
noStroke();
push();
translate(0, 0, 0);
fill(255, 100, 100);
sphere(50);
pop();
push();
translate(-150, 50, -50);
fill(100, 255, 100);
box(70);
pop();
push();
translate(150, 50, 50);
fill(100, 100, 255);
torus(40, 15);
pop();
}
材質(Material)
材質決定了物體表面如何回應光線。
基本材質
// 正常材質 — 會回應光源
fill(255, 100, 100);
normalMaterial(); // 依法線方向著色(除錯用)
環境材質
ambientMaterial(255, 100, 100);
// 只對 ambientLight 有反應
自發光材質
emissiveMaterial(255, 100, 0);
// 自己發光,不受光源影響
鏡面材質
specularMaterial(255);
shininess(50); // 高光的銳利度
// 會產生高光反射
材質展示範例
function draw() {
background(20);
orbitControl();
ambientLight(50);
pointLight(255, 255, 255, 200, -200, 200);
directionalLight(200, 200, 255, -1, 1, -1);
// 環境材質
push();
translate(-200, 0, 0);
ambientMaterial(255, 100, 100);
sphere(50);
pop();
// 自發光材質
push();
translate(-70, 0, 0);
emissiveMaterial(100, 255, 100);
sphere(50);
pop();
// 鏡面材質(低光澤)
push();
translate(70, 0, 0);
specularMaterial(100, 100, 255);
shininess(10);
sphere(50);
pop();
// 鏡面材質(高光澤)
push();
translate(200, 0, 0);
specularMaterial(255, 255, 255);
shininess(100);
sphere(50);
pop();
}
3D 生成式藝術範例
把之前學的概念應用到 3D 中。以下是一個用 noise 驅動的 3D 地形:
let cols = 50;
let rows = 50;
let scl = 15;
let terrain = [];
let flying = 0;
function setup() {
createCanvas(800, 600, WEBGL);
// 初始化地形陣列
for (let y = 0; y < rows; y++) {
terrain[y] = [];
for (let x = 0; x < cols; x++) {
terrain[y][x] = 0;
}
}
}
function draw() {
background(20, 20, 30);
// 生成地形高度
flying -= 0.03;
let yoff = flying;
for (let y = 0; y < rows; y++) {
let xoff = 0;
for (let x = 0; x < cols; x++) {
terrain[y][x] = map(noise(xoff, yoff), 0, 1, -80, 80);
xoff += 0.15;
}
yoff += 0.15;
}
// 相機設定
rotateX(PI / 3);
translate(-cols scl / 2, -rows scl / 2, 0);
// 光源
ambientLight(30, 30, 50);
directionalLight(200, 200, 255, 0.5, 0.5, -1);
pointLight(255, 150, 100, cols scl / 2, rows scl / 2, 100);
// 繪製地形網格
for (let y = 0; y < rows - 1; y++) {
beginShape(TRIANGLE_STRIP);
for (let x = 0; x < cols; x++) {
let h1 = terrain[y][x];
let h2 = terrain[y + 1][x];
// 高度 → 顏色
let c1 = getTerrainColor(h1);
let c2 = getTerrainColor(h2);
fill(c1);
vertex(x scl, y scl, h1);
fill(c2);
vertex(x scl, (y + 1) scl, h2);
}
endShape();
}
}
function getTerrainColor(h) {
if (h < -30) {
return color(30, 50, 120); // 深水
} else if (h < -10) {
return color(50, 80, 160); // 淺水
} else if (h < 10) {
return color(80, 160, 80); // 草地
} else if (h < 40) {
return color(120, 100, 70); // 山坡
} else {
return color(220, 220, 230); // 雪頂
}
}
3D 粒子雲
let particles = [];
function setup() {
createCanvas(800, 600, WEBGL);
for (let i = 0; i < 2000; i++) {
particles.push({
pos: p5.Vector.random3D().mult(random(50, 250)),
size: random(1, 4),
speed: random(0.001, 0.005),
offset: random(TWO_PI)
});
}
}
function draw() {
background(15, 15, 25);
orbitControl();
// 微弱的環境光
ambientLight(30);
pointLight(255, 200, 150, 0, 0, 0);
// 中央發光球
push();
emissiveMaterial(255, 150, 50);
noStroke();
sphere(20);
pop();
// 粒子
for (let p of particles) {
let angle = frameCount * p.speed + p.offset;
// 繞原點旋轉
let x = p.pos.x cos(angle) - p.pos.z sin(angle);
let z = p.pos.x sin(angle) + p.pos.z cos(angle);
let y = p.pos.y + sin(frameCount 0.01 + p.offset) 10;
let d = dist(0, 0, 0, x, y, z);
let brightness = map(d, 50, 250, 255, 50);
push();
translate(x, y, z);
emissiveMaterial(brightness, brightness 0.7, brightness 0.4);
noStroke();
sphere(p.size);
pop();
}
}
效能注意事項
3D 模式比 2D 吃效能得多。幾個建議:
- 減少幾何體數量:sphere 和 torus 的 detail 參數可以降低
- 用
noStroke():描邊在 3D 中很耗效能 - 限制光源數量:每個光源都會增加 shader 的計算量
- 善用
push/pop:避免不必要的矩陣運算
// 降低球體精細度
sphere(50, 12, 8); // 預設是 (50, 24, 16)
// 批量繪製時關閉描邊
noStroke();
小結
p5.js 的 WEBGL 模式為你打開了三維創作的大門。雖然它不像 Three.js 那樣功能完整,但它的簡潔 API 讓你可以快速原型、快速實驗。
這篇文章我們學了:
- WEBGL 模式的啟用與座標系統
- 六種內建 3D 幾何體
- 相機控制(手動設定與 orbitControl)
- 四種光源類型及其特性
- 材質系統(環境、自發光、鏡面)
- 3D 地形生成與粒子雲範例
3D 的世界很深,這篇只是入門。但有了這些基礎,你已經可以開始做很多有趣的 3D 創意專案了。
延伸閱讀
- p5.js 官方 3D 教學頁面 — WEBGL 的完整 API 文件
- Daniel Shiffman, Coding Train: “WEBGL in p5.js” 系列教學
- The Book of Shaders — 如果你想深入 shader 程式設計
- Three.js — 當 p5.js 的 3D 功能不夠用時的進階選擇
- p5.js createShader() 文件 — 自訂 GLSL shader