前言

有一天我用 Stable Diffusion 生成了一張圖——那是一幅以深海為主題的抽象畫,有著幽暗的藍色漸層、漂浮的發光粒子、和有機的流動線條。我盯著它看了很久,然後想:「如果這不是一張靜態的圖片,而是一個可以互動、會呼吸的程式——那會多美?」

這個念頭開啟了我一系列的實驗:用 AI 生成圖作為「視覺參考」,然後用 p5.js 程式碼重新詮釋它。不是像素級的複製,而是捕捉圖像的「精神」——它的色彩氛圍、動態暗示、空間感——然後用演算法賦予它生命。

這篇文章記錄我的工作流程、技術手段、和過程中的思考。

工作流程概覽

整個流程分為四個階段:

1. AI 生成參考圖
   ↓
  1. 分析視覺元素
  1. p5.js 程式化重現
  1. 風格融合與迭代

每個階段都有其獨特的挑戰和樂趣。

第一階段:用 Stable Diffusion 生成參考圖

為什麼用 AI 生成參考圖?

傳統的程式藝術工作流程是「先想好要什麼效果,再寫程式實現」。但有時候你不知道自己想要什麼——你只有一個模糊的方向,比如「深海感的抽象畫」。

AI 圖像生成的價值在於快速視覺化你的模糊想法。你可以在幾分鐘內生成幾十張變化,從中挑選最有啟發性的那一張。

有效的 Stable Diffusion Prompt

生成適合做為 p5.js 參考的圖片,prompt 有一些技巧:

強調抽象和程式化元素:

abstract generative art, procedural patterns, flowing particles,
bioluminescent deep ocean, dark blue gradient background,
organic curves, scattered glowing dots, ethereal atmosphere,
digital art, creative coding aesthetic

加入風格修飾:

style of creative coding, reminiscent of processing/p5.js sketches,
mathematical beauty, algorithmic patterns, generative design

避免太寫實的元素:

Negative prompt: photorealistic, text, watermark, human face,
detailed objects, sharp edges

建議的生成參數

  • 解析度:512×512 或 768×768(不需要太高,因為這只是參考)
  • Steps:30-50
  • CFG Scale:7-9(太高會太銳利,失去抽象感)
  • Sampler:DPM++ 2M Karras 或 Euler a

生成多個變體

不要只生成一張。我通常會:

  1. 先用一個基本 prompt 生成 4-8 張
  2. 從中挑 2-3 張有潛力的
  3. 用這些作為起點,調整 prompt 或 seed 生成更多變體
  4. 最終選定 1-2 張作為程式化重現的目標

第二階段:分析視覺元素

這是最關鍵的一步。你需要把一張圖片「拆解」成可以用程式實現的元素。

分析框架

我使用以下的分析框架:

1. 色彩分析

  • 主色調是什麼?(例:深藍 #0a1628)
  • 輔色是什麼?(例:青綠 #1a8f7a、橙 #e8a544)
  • 色彩的分佈比例?(70% 深色、20% 中間色、10% 亮色)
  • 色彩之間如何過渡?(漸層?突變?噪音混合?)

2. 元素識別

  • 有哪些可辨識的視覺元素?

– 粒子/光點
– 線條/曲線
– 形狀(圓、矩形、不規則形)
– 紋理/圖案

  • 這些元素的密度和分佈?
  • 大小的變化範圍?

3. 空間結構

  • 有沒有明顯的焦點?
  • 深度感(近大遠小、漸層模糊)?
  • 對稱性?
  • 構圖法則(三分法、黃金比例、中心放射)?

4. 動態暗示

  • 靜態圖片中有沒有暗示方向的元素?
  • 流動感(曲線的方向)?
  • 擴散或收縮的趨勢?
  • 如果讓這張圖動起來,最自然的動態是什麼?

實際分析範例

假設我的參考圖是一幅深海主題的抽象畫:

色彩分析:
  • 背景:從 #050a14(幾乎黑色)到 #0a2040(深藍)的徑向漸層
  • 發光粒子:#40e0d0(藍綠色),帶光暈
  • 有一些橙色的小點作為對比色:#ff7043
  • 整體偏冷,但有少量暖色作為視覺焦點

元素識別:

  • 大量小粒子(50-200 個),大小不等(2-10px)
  • 若干曲線,像海流或水母觸鬚
  • 底部有類似海底地形的模糊形狀
  • 頂部較空曠,有「向上看」的空間感

空間結構:

  • 焦點在中央偏下
  • 粒子從焦點向外擴散
  • 邊緣漸暗(暈影效果)

動態暗示:

  • 粒子應該緩慢漂浮,有隨機的布朗運動
  • 曲線應該微微搖擺,像在水中
  • 整體節奏緩慢,有「呼吸」的韻律感

第三階段:p5.js 程式化重現

建立基本結構

根據分析,先建立最基本的色彩和空間:

function setup() {
  createCanvas(800, 800);
  colorMode(HSB, 360, 100, 100, 100);
}

function draw() { // 背景漸層(從中心向外漸暗) drawRadialGradient();

// 海流曲線 drawFlowCurves();

// 發光粒子 drawParticles();

// 暈影 drawVignette(); }

function drawRadialGradient() { loadPixels(); let cx = width / 2; let cy = height * 0.6; // 焦點偏下

for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let d = dist(x, y, cx, cy) / (width * 0.7); d = constrain(d, 0, 1);

// 深藍到接近黑色 let h = lerp(210, 220, d); let s = lerp(70, 90, d); let b = lerp(20, 4, d);

let idx = (y width + x) 4; let rgb = hsbToRgb(h, s, b); pixels[idx] = rgb[0]; pixels[idx + 1] = rgb[1]; pixels[idx + 2] = rgb[2]; pixels[idx + 3] = 255; } } updatePixels(); }

粒子系統

class DeepSeaParticle {
  constructor() {
    this.reset();
  }

reset() { // 集中在中下方 this.x = randomGaussian(width 0.5, width 0.3); this.y = randomGaussian(height 0.6, height 0.25); this.size = random(2, 10); this.glowSize = this.size * random(3, 6);

// 大部分是青色,少數是橙色 if (random() < 0.15) { this.hue = random(15, 35); // 橙色 } else { this.hue = random(160, 190); // 青藍色 }

this.brightness = random(60, 100); this.pulseSpeed = random(0.01, 0.03); this.pulsePhase = random(TWO_PI);

// 緩慢漂移 this.vx = random(-0.1, 0.1); this.vy = random(-0.2, 0.05); // 微微上升

this.noiseOffsetX = random(1000); this.noiseOffsetY = random(1000); }

update() { // 噪音驅動的布朗運動 this.x += this.vx + (noise(this.noiseOffsetX) - 0.5) * 0.5; this.y += this.vy + (noise(this.noiseOffsetY) - 0.5) * 0.3;

this.noiseOffsetX += 0.005; this.noiseOffsetY += 0.005;

// 邊界循環 if (this.y < -this.glowSize) this.y = height + this.glowSize; if (this.x < -this.glowSize || this.x > width + this.glowSize) { this.reset(); } }

display() { let pulse = sin(frameCount * this.pulseSpeed + this.pulsePhase); let currentBrightness = this.brightness + pulse * 15; let currentGlow = this.glowSize (1 + pulse 0.2);

// 光暈(多層半透明圓) noStroke(); for (let i = 3; i >= 0; i--) { let r = map(i, 0, 3, this.size, currentGlow); let alpha = map(i, 0, 3, 40, 5); fill(this.hue, 60, currentBrightness, alpha); ellipse(this.x, this.y, r * 2); }

// 核心亮點 fill(this.hue, 20, 100, 80); ellipse(this.x, this.y, this.size); } }

海流曲線

class FlowCurve {
  constructor(startY) {
    this.points = [];
    this.startY = startY;
    this.amplitude = random(20, 60);
    this.frequency = random(0.005, 0.015);
    this.speed = random(0.005, 0.015);
    this.alpha = random(5, 20);

// 生成曲線控制點 for (let x = -50; x < width + 50; x += 10) { this.points.push(createVector(x, startY)); } }

update() { for (let i = 0; i < this.points.length; i++) { let x = this.points[i].x; this.points[i].y = this.startY + sin(x this.frequency + frameCount this.speed) * this.amplitude + noise(x 0.01, frameCount 0.003) * 30; } }

display() { noFill(); stroke(180, 40, 50, this.alpha); strokeWeight(1);

beginShape(); for (let p of this.points) { curveVertex(p.x, p.y); } endShape(); } }

組合與精煉

let particles = [];
let curves = [];

function setup() { createCanvas(800, 800); colorMode(HSB, 360, 100, 100, 100);

// 建立粒子 for (let i = 0; i < 150; i++) { particles.push(new DeepSeaParticle()); }

// 建立海流曲線 for (let y = 100; y < height; y += 80) { curves.push(new FlowCurve(y)); } }

function draw() { // 半透明背景(軌跡淡出效果) fill(215, 85, 5, 15); noStroke(); rect(0, 0, width, height);

// 海流 for (let c of curves) { c.update(); c.display(); }

// 粒子 for (let p of particles) { p.update(); p.display(); }

// 暈影 drawVignette(); }

function drawVignette() { noFill(); for (let i = 0; i < 40; i++) { let alpha = map(i, 0, 40, 0, 30); stroke(220, 90, 3, alpha); strokeWeight(width * 0.02); let offset = i width 0.015; rect(-offset, -offset, width + offset 2, height + offset 2); } }

第四階段:風格融合與迭代

不要追求一模一樣

這是最重要的心態。你不是在「複製」AI 的圖,而是在「翻譯」。p5.js 有它自己的語言——動態、互動、演算法之美。讓最終作品有自己的生命。

加入 AI 圖沒有的元素

  • 互動:滑鼠影響粒子的行為
  • 時間:隨著時間推移,色彩或構圖緩慢演變
  • 隨機性:每次重新整理頁面都是一幅新作品
  • 聲音:如果搭配音樂,可以做音頻反應式視覺
function mouseMoved() {
  // 滑鼠附近的粒子被吸引
  for (let p of particles) {
    let d = dist(mouseX, mouseY, p.x, p.y);
    if (d < 150) {
      let angle = atan2(mouseY - p.y, mouseX - p.x);
      p.vx += cos(angle) * 0.05;
      p.vy += sin(angle) * 0.05;
    }
  }
}

多張參考圖的融合

有時候最好的結果來自融合多張參考圖的元素:

  • 從圖 A 取色彩方案
  • 從圖 B 取空間結構
  • 從圖 C 取紋理風格
  • 加入你自己的動態設計

這種跨圖融合是程式化重現的獨特優勢——在傳統繪畫中很難做到如此靈活的元素組合。

從 AI 圖提取色彩的實用方法

用 Python 分析色彩

from PIL import Image
from collections import Counter
import numpy as np

def extract_palette(image_path, num_colors=8): """從圖片中提取主要色彩""" img = Image.open(image_path) img = img.resize((100, 100)) # 縮小以加速

# 量化色彩 img_quantized = img.quantize(colors=num_colors) palette = img_quantized.getpalette()

colors = [] for i in range(num_colors): r, g, b = palette[i3:(i+1)3] colors.append((r, g, b))

return colors

# 使用 palette = extract_palette('ai_reference.png') for i, (r, g, b) in enumerate(palette): print(f"Color {i}: rgb({r}, {g}, {b}) -> #{r:02x}{g:02x}{b:02x}")

用瀏覽器開發者工具

更簡單的方法:把 AI 生成的圖片在瀏覽器中打開,用 ColorZilla 等取色器擴充套件直接取色。

思考:AI 圖像生成 vs 程式藝術

這兩者之間有一個有趣的張力:

  • AI 生成:結果導向,快速,視覺品質高,但你對過程沒有控制
  • 程式藝術:過程導向,較慢,但你理解每一個像素為什麼在那裡

把兩者結合,你得到的是:用 AI 探索視覺方向,用程式賦予它生命和邏輯。AI 是地圖,程式碼是旅程。

我認為這是一個非常有前景的創作方法。它不是偷懶——分析一張圖片並用演算法重建它,其實需要相當深入的視覺理解力和程式能力。

小結

從 Stable Diffusion 到 p5.js 的風格遷移,本質上是一種「跨媒介翻譯」。就像把一首詩從中文翻譯成英文——你不是在逐字對應,而是在捕捉精神和意境。

這個過程中,你會發現自己對視覺的理解變得更深——你開始能用語言精確描述一個色彩漸層的特質、一組粒子的動態行為、一條曲線的情緒暗示。這種分析能力,反過來也會提升你從零開始創作的能力。

延伸閱讀:

  • Generative Design (Hartmut Bohnacker 等) — 生成式設計的經典教科書
  • Tyler Hobbs 的文章 “Working with Color in Generative Art” — 程式藝術的色彩理論
  • Stable Diffusion 的 prompt 工程指南 — 更有效地生成參考圖
  • OpenProcessing 的 “Inspired By” 作品集 — 看看其他人如何從參考圖出發創作