前言

M.C. Escher 的作品大概是很多人對「數學之美」的第一次覺醒。那些魚變成鳥、蜥蜴互相咬尾的鑲嵌圖案,既是嚴謹的數學結構,又充滿了想像力和幽默感。

鑲嵌(tessellation)本身是一個古老的數學主題——用形狀完全覆蓋平面、不重疊、不留間隙。從伊斯蘭建築的幾何磁磚到日本的和柄圖案,鑲嵌無所不在。而當你把鑲嵌圖案與雷射雕刻結合,就能創造出層次豐富的裝飾作品。

這篇文章會從正多邊形鑲嵌的數學開始,介紹 Escher 風格的變形技巧,然後用 p5.js 生成鑲嵌圖案,最後分享雷雕實作的經驗。

正多邊形鑲嵌

三種正則鑲嵌

能夠單獨鋪滿平面的正多邊形只有三種:

  1. 正三角形:每個頂點周圍有 6 個三角形(60° × 6 = 360°)
  2. 正方形:每個頂點周圍有 4 個正方形(90° × 4 = 360°)
  3. 正六邊形:每個頂點周圍有 3 個六邊形(120° × 3 = 360°)

為什麼只有這三種?因為正多邊形的內角必須能整除 360°:

正 n 邊形內角 = (n-2) × 180° / n

n=3: 60° → 360°/60° = 6 ✓ n=4: 90° → 360°/90° = 4 ✓ n=5: 108° → 360°/108° = 3.33... ✗ n=6: 120° → 360°/120° = 3 ✓ n=7: 128.6° → 不整除 ✗ ...以此類推,都不行了

半正則鑲嵌(Archimedean Tiling)

如果允許混合使用不同的正多邊形,就能得到 8 種半正則鑲嵌。例如:

  • (3, 6, 3, 6):三角形和六邊形交替排列
  • (4, 8, 8):每個頂點周圍是一個正方形和兩個正八邊形
  • (3, 3, 3, 3, 6):四個三角形加一個六邊形

這些組合在裝飾設計中非常實用,因為它們提供了比單一形狀更豐富的視覺變化。

Escher 變形技巧

基本原理

Escher 的鑲嵌魔法其實基於一個簡單的原理:對基本鋪排單元進行變形,同時保持鋪排的數學約束

以正方形鑲嵌為例,最基本的變形步驟是:

  1. 在正方形的一條邊上畫一條自由曲線
  2. 將這條曲線平移到對邊(如果是平移對稱的鑲嵌)
  3. 在另一對邊上重複同樣的操作
  4. 最終得到的形狀仍然能完美鋪排

四種基本對稱操作

Escher 使用了四種對稱操作來創造鑲嵌:

  1. 平移(Translation):一邊的曲線直接平移到對邊
  2. 旋轉(Rotation):曲線繞頂點旋轉到相鄰邊
  3. 鏡射(Reflection):曲線鏡射到對邊
  4. 滑移鏡射(Glide Reflection):平移+鏡射的組合

實作:從正方形到魚

讓我用一個具體的例子示範。我們要把正方形變形成一條魚:

步驟一:取一個正方形
┌────────┐
│        │
│        │
│        │
└────────┘

步驟二:在上邊畫一條曲線(魚的背部輪廓) ╱╲ ╱╲ ╱ ╲╱ ╲ │ │ │ │ └────────┘

步驟三:將上邊的曲線平移到下邊 ╱╲ ╱╲ ╱ ╲╱ ╲ │ │ │ │ ╲ ╱╲ ╱ ╲╱ ╲╱

步驟四:在左邊畫魚頭,平移到右邊成為魚尾

最終,這個變形後的形狀可以像正方形一樣完美鋪排平面。

用 p5.js 生成鑲嵌圖案

基本的正六邊形鑲嵌

先來一個基礎的六邊形鑲嵌,作為後續變形的起點:

// p5.js 正六邊形鑲嵌
const hexSize = 30;

function setup() { createCanvas(800, 600); noLoop(); }

function draw() { background(255);

const w = hexSize * sqrt(3); const h = hexSize * 2;

for (let row = -1; row < height / (h * 0.75) + 1; row++) { for (let col = -1; col < width / w + 1; col++) { let x = col * w; let y = row h 0.75;

// 奇數行偏移半個寬度 if (row % 2 !== 0) { x += w / 2; }

drawHexagon(x, y, hexSize); } } }

function drawHexagon(cx, cy, size) { beginShape(); for (let i = 0; i < 6; i++) { let angle = TWO_PI / 6 * i - PI / 6; let x = cx + size * cos(angle); let y = cy + size * sin(angle); vertex(x, y); } endShape(CLOSE); }

Escher 風格變形鑲嵌

接下來,我們在正方形鑲嵌的基礎上加入變形,用平移對稱創造出互鎖的形狀:

// Escher 風格平移鑲嵌
const tileSize = 60;
let deformation = [];

function setup() { createCanvas(800, 600); noLoop();

// 生成上邊的變形曲線(隨機但平滑) const steps = 20; for (let i = 0; i <= steps; i++) { let t = i / steps; // 用正弦波組合產生有機的曲線 let dx = t * tileSize; let dy = 8 sin(t PI 2) + 5 sin(t PI 4); deformation.push(createVector(dx, dy)); } }

function draw() { background(240);

for (let row = -2; row < height / tileSize + 2; row++) { for (let col = -2; col < width / tileSize + 2; col++) { let x = col * tileSize; let y = row * tileSize;

// 交替著色 let colorIndex = (row + col) % 3; if (colorIndex < 0) colorIndex += 3;

let colors = [ color(70, 130, 180), // 鋼藍 color(180, 100, 60), // 赤褐 color(100, 160, 90) // 橄欖綠 ];

fill(colors[colorIndex]); stroke(0); strokeWeight(0.5);

drawDeformedTile(x, y); } } }

function drawDeformedTile(ox, oy) { beginShape();

// 上邊(使用變形曲線) for (let p of deformation) { vertex(ox + p.x, oy + p.y); }

// 右邊(使用同一條變形曲線,旋轉90度) for (let i = 0; i < deformation.length; i++) { let p = deformation[i]; let t = i / (deformation.length - 1); vertex(ox + tileSize + p.y, oy + t * tileSize); }

// 下邊(平移自上邊) for (let i = deformation.length - 1; i >= 0; i--) { let p = deformation[i]; vertex(ox + p.x, oy + tileSize + p.y); }

// 左邊(平移自右邊) for (let i = deformation.length - 1; i >= 0; i--) { let p = deformation[i]; let t = i / (deformation.length - 1); vertex(ox + p.y, oy + t * tileSize); }

endShape(CLOSE); }

加入細節讓形狀成為「生物」

Escher 的高明之處在於,他不只是讓形狀互鎖,還讓每個形狀看起來像某種可辨識的生物。要達到這個效果:

  1. 先決定你想要的生物(魚、鳥、蜥蜴等)
  2. 設計變形曲線時,讓輪廓暗示該生物的特徵
  3. 在形狀內部加上眼睛、翅膀等細節
  4. 用不同顏色區分相鄰的形狀
// 在形狀內部加入細節(以魚為例)
function addFishDetails(ox, oy, facing) {
  push();
  translate(ox + tileSize/2, oy + tileSize/2);
  if (facing < 0) scale(-1, 1);

// 眼睛 fill(255); ellipse(-tileSize 0.2, -tileSize 0.1, 6, 6); fill(0); ellipse(-tileSize 0.2, -tileSize 0.1, 3, 3);

// 鰭 noFill(); stroke(0, 100); arc(0, 0, tileSize 0.3, tileSize 0.3, -PI/4, PI/4);

// 鱗片紋理 for (let i = 0; i < 3; i++) { arc(tileSize 0.1 i, 0, 8, 8, -PI/3, PI/3); }

pop(); }

從 p5.js 到 SVG

要將 p5.js 生成的圖案用於雷雕,需要匯出 SVG 格式。可以使用 p5.js-svg 函式庫:

// 在 HTML 中引入 p5.svg.js
// <script src="p5.svg.js"></script>

function setup() { createCanvas(800, 600, SVG); // SVG 模式 noLoop(); }

function draw() { // ... 繪製鑲嵌圖案 ...

// 完成後儲存 save('tessellation.svg'); }

或者,如果你偏好 Python,可以直接用 svgwrite 生成:

import svgwrite
import math

def hex_tessellation_svg(filename, cols=10, rows=8, size=15): w = size * math.sqrt(3) h = size * 2

canvas_w = cols * w + w/2 canvas_h = rows h 0.75 + h * 0.25

dwg = svgwrite.Drawing(filename, size=(f'{canvas_w}mm', f'{canvas_h}mm'), viewBox=f'0 0 {canvas_w} {canvas_h}')

for row in range(rows): for col in range(cols): cx = col * w + (w/2 if row % 2 else 0) cy = row h 0.75 + size

points = [] for i in range(6): angle = math.pi / 3 * i - math.pi / 6 px = cx + size * math.cos(angle) py = cy + size * math.sin(angle) points.append((px, py))

dwg.add(dwg.polygon(points, fill='none', stroke='black', stroke_width=0.15))

dwg.save()

hex_tessellation_svg('hex_tess.svg')

雷雕鑲嵌的實作技巧

線條粗細與材料

雷雕鑲嵌圖案時,線條的粗細很重要:

  • 密度板上雕刻:建議線寬 0.1-0.2mm(向量模式)
  • 壓克力上雕刻:可以稍粗一點,0.2-0.3mm
  • 紙張上切割:鑲嵌的每個形狀可以切割出來,用不同顏色的紙重新拼貼

層次感的營造

單純的線條雕刻會顯得平淡。加入層次感的方法:

  1. 不同深度的雕刻:輪廓線用較高功率(深雕),內部細節用較低功率(淺雕)
  2. 填充雕刻:某些區域用柵格填充,產生色調差異
  3. 鏤空切割:交替的形狀一個切穿、一個保留,背後放上對比色材料

尺寸考量

鑲嵌圖案的最小特徵尺寸取決於雷切的精度:

  • CO2 雷切:最小特徵約 0.3mm
  • 光纖雷切:最小特徵約 0.1mm
  • 建議鑲嵌單元不要小於 10mm,否則細節會糊掉

範例作品:六邊形鑲嵌燈罩

我做過一個蠻滿意的作品——用六邊形鑲嵌設計的木質燈罩。做法是:

  1. 用 Python 生成六邊形鑲嵌的 SVG
  2. 在每個六邊形內部加入不同的幾何圖案(有些鏤空、有些雕刻)
  3. 用 3mm 樺木合板雷切
  4. 將多片拼接成圓柱形燈罩
  5. 內部放 LED 燈條

光線透過鏤空的六邊形投射在牆壁上,形成漂亮的光影圖案。這是鑲嵌設計的一個很棒的應用場景。

小結

鑲嵌圖案是數學、藝術和工藝的完美交會。從基本的正多邊形鋪排,到 Escher 風格的生物變形,再到雷雕的實體化,每一步都有足夠的深度值得探索。

我特別喜歡用程式生成鑲嵌的過程——寫幾十行程式碼就能產生無限延伸的圖案,這本身就是一種創作的快感。

延伸閱讀:

  • Craig Kaplan 的論文集 — 計算鑲嵌設計的學術基礎
  • Visions of Symmetry by Doris Schattschneider — Escher 鑲嵌作品的完整分析
  • 阿爾罕布拉宮(Alhambra)的磁磚圖案 — 鑲嵌藝術的歷史巔峰
  • GeoGebra 的鑲嵌工具 — 互動式探索鑲嵌的好工具