前言
如果你有在做創意編程,應該或多或少聽過 NFT(Non-Fungible Token)和生成式藝術(Generative Art)的交集。從 2021 年 Art Blocks 平台上 Tyler Hobbs 的 Fidenza 系列賣出天價開始,「用程式碼生成的藝術品」突然變成了一個有市場價值的東西。
我自己雖然不是靠 NFT 賺錢的人,但作為一個做 p5.js 創作的後端工程師,我對這個領域做了不少研究,也嘗試過把作品放上鏈。這篇文章想客觀地分享我對生成式藝術 NFT 的理解——平台介紹、技術門檻、市場現況,以及我個人的看法。
提醒:這篇文章不是投資建議。NFT 市場波動極大,請自行評估風險。
什麼是生成式藝術 NFT?
傳統的 NFT 藝術品通常是一張圖片或一段影片——藝術家創作完成後,把成品放到鏈上。
生成式藝術 NFT 不一樣。藝術家上傳的是程式碼,每次有人鑄造(mint)的時候,程式碼會根據隨機種子生成一個獨特的作品。
也就是說:
- 藝術家寫的是「規則」(演算法)
- 每個買家拿到的是規則的一個「實例」
- 同一套規則可以產生幾十到幾千個不同的作品
- 每個作品都是獨一無二的
// 概念示意:同一套規則,不同的種子
function setup() {
// 隨機種子決定了這次的作品長什麼樣
randomSeed(tokenData.hash);
noiseSeed(tokenData.hash);
createCanvas(800, 800);
background(20);
// 同樣的演算法,不同的種子 → 不同的結果
let palette = generatePalette();
let composition = generateComposition();
render(palette, composition);
}
主要平台介紹
Art Blocks
Art Blocks 是生成式藝術 NFT 的先驅和標竿。
特色:
- 建在 Ethereum 上
- 程式碼完全存在鏈上(on-chain)
- 有嚴格的審核制度(Curated / Playground / Factory)
- 作品品質普遍很高
技術要求:
- 必須用純 JavaScript(vanilla JS)
- 不能引用外部函式庫(所有依賴要打包進去)
- 程式碼大小有限制(~10-20KB 壓縮後)
- 必須是確定性的(deterministic):同一個 seed 永遠產生同一個結果
// Art Blocks 的標準接口
// tokenData.hash 是一個 66 字元的 hex string
// 你的程式碼必須用這個 hash 作為唯一的隨機來源
let tokenData = {
hash: "0x..." // 由平台在 mint 時提供
};
// 把 hash 轉成可用的隨機數
function hashToRandom(hash) {
let seed = parseInt(hash.slice(2, 10), 16);
// 用 seed 初始化你的 PRNG
return new SeededRandom(seed);
}
// 自己寫或引入 PRNG(因為不能用 Math.random)
class SeededRandom {
constructor(seed) {
this.seed = seed;
}
// 簡單的 LCG(Linear Congruential Generator)
next() {
this.seed = (this.seed * 1664525 + 1013904223) & 0xFFFFFFFF;
return (this.seed >>> 0) / 0xFFFFFFFF;
}
// 範圍內的隨機數
range(min, max) {
return min + this.next() * (max - min);
}
// 隨機選擇
pick(array) {
return array[Math.floor(this.next() * array.length)];
}
}
fxhash
fxhash 是建在 Tezos 區塊鏈上的生成式藝術平台。
特色:
- Tezos 的交易費用比 Ethereum 低很多(幾毛錢 vs 幾十美金)
- 開放式平台,任何人都可以上傳作品
- 社群非常活躍,有各種主題活動
- 入門門檻低,適合新手
技術要求:
- 支援 p5.js、Three.js 等外部函式庫
- 使用
fxhash變數作為隨機種子 - 需要呼叫
fxpreview()產生預覽圖
// fxhash 的標準模板
// fxhash 和 fxrand() 由平台注入
function setup() {
createCanvas(800, 800);
noLoop();
}
function draw() {
background(20);
// 使用 fxrand() 而不是 random()
// fxrand() 是確定性的,基於 fxhash 種子
let numCircles = Math.floor(fxrand() * 50) + 20;
for (let i = 0; i < numCircles; i++) {
let x = fxrand() * width;
let y = fxrand() * height;
let r = fxrand() * 100 + 10;
let hue = fxrand() * 360;
fill(hsla(${hue}, 70%, 60%, 0.5));
noStroke();
circle(x, y, r);
}
// 告訴平台可以截圖了
fxpreview();
}
其他平台
- Prohibition:以太坊上的策展型平台
- Highlight:支援多鏈,比較新的平台
- Verse:由 Art Blocks 團隊推出的二級市場
如何讓作品具有隨機種子
生成式藝術 NFT 的核心挑戰是:確定性隨機(Deterministic Randomness)。
為什麼不能用 Math.random()?
因為 Math.random() 每次執行結果都不同。但 NFT 需要:
- 同一個 token 每次渲染都要產生完全一樣的結果
- 不同 token 之間要有足夠的差異
自己實作 PRNG
// Mulberry32: 一個簡單但品質不錯的 32-bit PRNG
function mulberry32(a) {
return function() {
a |= 0;
a = a + 0x6D2B79F5 | 0;
var t = Math.imul(a ^ a >>> 15, 1 | a);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
// 從 hash 字串產生種子
function hashToSeed(hash) {
let seed = 0;
for (let i = 0; i < hash.length; i++) {
seed = ((seed << 5) - seed + hash.charCodeAt(i)) | 0;
}
return seed;
}
// 使用
const rand = mulberry32(hashToSeed(tokenData.hash));
// 替代 p5.js 的 random()
function myRandom(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + rand() * (max - min);
}
特徵(Features)的設計
好的生成式 NFT 會定義「特徵」——根據隨機種子決定的屬性。這些特徵會影響稀有度和價值:
// 定義作品的特徵
function generateFeatures(rand) {
// 色彩方案:有些比其他的稀有
const palettes = [
{ name: "Sunset", colors: ["#FF6B6B", "#FFE66D", "#4ECDC4"], weight: 30 },
{ name: "Ocean", colors: ["#0077B6", "#00B4D8", "#90E0EF"], weight: 30 },
{ name: "Forest", colors: ["#2D6A4F", "#40916C", "#95D5B2"], weight: 25 },
{ name: "Neon", colors: ["#FF006E", "#8338EC", "#3A86FF"], weight: 10 },
{ name: "Mono", colors: ["#F8F9FA", "#ADB5BD", "#212529"], weight: 5 },
];
const palette = weightedPick(palettes, rand);
// 密度
const density = rand() < 0.7 ? "Normal" : "Dense";
// 構圖
const compositions = ["Radial", "Grid", "Organic", "Spiral"];
const composition = compositions[Math.floor(rand() * compositions.length)];
return {
palette: palette.name,
density: density,
composition: composition,
};
}
function weightedPick(items, rand) {
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
let r = rand() * totalWeight;
for (let item of items) {
r -= item.weight;
if (r <= 0) return item;
}
return items[items.length - 1];
}
在 fxhash 上,你可以用 $fxhashFeatures 來宣告特徵:
window.$fxhashFeatures = {
"Palette": features.palette,
"Density": features.density,
"Composition": features.composition,
};
智能合約基礎
對後端工程師來說,智能合約其實就是「跑在區塊鏈上的程式」。如果你用 Art Blocks 或 fxhash,你不需要自己寫合約——平台會幫你處理。
但了解基本概念有助於理解整個系統:
ERC-721:NFT 的標準
// 簡化的 ERC-721 概念(Solidity 語言)
interface IERC721 {
// 查詢擁有者
function ownerOf(uint256 tokenId) external view returns (address);
// 轉移 NFT
function transferFrom(address from, address to, uint256 tokenId) external;
// 查詢餘額
function balanceOf(address owner) external view returns (uint256);
}
生成式 NFT 的合約邏輯
// 概念示意(簡化版)
contract GenerativeArt is ERC721 {
mapping(uint256 => bytes32) public tokenIdToHash;
uint256 public totalSupply;
uint256 public maxSupply;
string public script; // 藝術家的 JavaScript 程式碼
function mint() external payable {
require(totalSupply < maxSupply, "Sold out");
uint256 tokenId = totalSupply;
// 用區塊資訊和 tokenId 生成唯一的 hash
bytes32 hash = keccak256(
abi.encodePacked(blockhash(block.number - 1), tokenId, msg.sender)
);
tokenIdToHash[tokenId] = hash;
_mint(msg.sender, tokenId);
totalSupply++;
}
}
這個 hash 就是你的 p5.js 程式碼收到的 tokenData.hash。
市場現況與個人觀察
2021-2022:狂熱期
- Art Blocks 的 Fidenza、Ringers、Chromie Squiggle 等系列賣出數百 ETH
- 大量創作者和投機者湧入
- fxhash 的 Tezos 生態蓬勃發展
2023-2024:冷卻期
- 整體 NFT 市場大幅下滑
- 投機者離開,但核心收藏家和創作者留下
- 品質導向,策展更嚴格
- 「生成式藝術」作為藝術形式的認同度提高(進入美術館和拍賣行)
2025-2026:現在
市場回歸到比較理性的狀態。一些觀察:
- 好作品依然有市場,但「隨便做一個就能賣」的時代已經過去了
- 社群和聲譽很重要:收藏家更看重藝術家的持續投入和社群參與
- 跨媒介創作受歡迎:結合實體(版畫、裝置)的作品更有吸引力
- 開源和教育被重視:分享過程和技術的創作者更受歡迎
我的個人看法
生成式藝術 NFT 對創意編程者來說,是一個有趣但不可靠的收入來源。
我建議的心態是:
- 先做好作品,再考慮上鏈:不要為了 NFT 而做作品,那樣通常做不出好東西
- 當成展覽機會:把平台當成線上藝廊,而不是賺錢機器
- 投入社群:生成式藝術社群是全世界最友善的技術社群之一
- 控制成本:用 fxhash(Tezos)開始,Gas fee 便宜,風險低
實作:把一個 p5.js 作品準備成 fxhash 格式
以下是一個完整的範例,展示如何把普通的 p5.js sketch 改成 fxhash 相容的格式:
原始版本
// 普通的 p5.js sketch
function setup() {
createCanvas(800, 800);
noLoop();
}
function draw() {
background(20);
noStroke();
for (let i = 0; i < 100; i++) {
let x = random(width);
let y = random(height);
let r = random(10, 60);
fill(random(255), random(255), random(255), 150);
circle(x, y, r);
}
}
fxhash 版本
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/npm/p5@1.9.0/lib/p5.min.js"></script>
<script src="fxhash-snippet.js"></script>
<style>
body { margin: 0; overflow: hidden; background: #000; }
canvas { display: block; }
</style>
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
// sketch.js — fxhash 版本
// fxrand() 和 fxhash 由 fxhash-snippet.js 提供
// 自訂的 random 函式,包裝 fxrand
function fxRandom(min, max) {
if (min === undefined) return fxrand();
if (max === undefined) { max = min; min = 0; }
return min + fxrand() * (max - min);
}
function fxRandomInt(min, max) {
return Math.floor(fxRandom(min, max));
}
// 定義特徵
const PALETTES = [
{ name: "Warm", colors: ["#FF6B6B", "#FFE66D", "#F7934C"] },
{ name: "Cool", colors: ["#0077B6", "#00B4D8", "#48CAE4"] },
{ name: "Earth", colors: ["#606C38", "#283618", "#DDA15E"] },
{ name: "Neon", colors: ["#FF006E", "#8338EC", "#3A86FF"] },
];
const palette = PALETTES[fxRandomInt(0, PALETTES.length)];
const count = fxRandomInt(50, 200);
const hasGrid = fxrand() < 0.3;
// 設定 fxhash 特徵
window.$fxhashFeatures = {
"Palette": palette.name,
"Element Count": count < 100 ? "Sparse" : "Dense",
"Has Grid": hasGrid ? "Yes" : "No",
};
function setup() {
let size = min(windowWidth, windowHeight);
createCanvas(size, size);
pixelDensity(2);
noLoop();
}
function draw() {
background(20);
noStroke();
if (hasGrid) {
drawGrid();
}
for (let i = 0; i < count; i++) {
let x = fxRandom(0, width);
let y = fxRandom(0, height);
let r = fxRandom(10, 60);
let colorIdx = fxRandomInt(0, palette.colors.length);
let c = color(palette.colors[colorIdx]);
c.setAlpha(150);
fill(c);
circle(x, y, r);
}
// 告訴 fxhash 可以截圖了
fxpreview();
}
function drawGrid() {
stroke(255, 10);
strokeWeight(0.5);
let step = width / 20;
for (let x = 0; x < width; x += step) {
line(x, 0, x, height);
}
for (let y = 0; y < height; y += step) {
line(0, y, width, y);
}
noStroke();
}
function windowResized() {
let size = min(windowWidth, windowHeight);
resizeCanvas(size, size);
redraw();
}
上傳前的檢查清單
[ ] 作品是確定性的(同一個 fxhash 永遠產生同一個結果)
[ ] 沒有使用 Math.random()(改用 fxrand())
[ ] 沒有使用 p5.js 的 random()(改用自訂的 fxRandom())
[ ] 有呼叫 fxpreview() 產生預覽圖
[ ] 有設定 $fxhashFeatures
[ ] 在不同的視窗大小下都能正確顯示
[ ] 載入速度合理(< 5 秒)
[ ] 已用不同的 hash 測試過多次,確認多樣性足夠
小結
生成式藝術 NFT 是一個把「程式碼即藝術」推到極致的領域。它讓創意編程者有了一個嶄新的展示和分發作品的方式。
但請記住:好的作品比好的行銷重要。先磨練你的創作技巧,培養你的美學品味,建立你在社群中的聲譽。NFT 只是一個管道,核心永遠是作品本身。
如果你已經在做 p5.js 創作,花一個週末把作品改成 fxhash 格式,發布上去看看。即使沒人買,你也學到了新東西,而且你的作品永遠存在鏈上——這本身就很酷。
延伸閱讀
- Art Blocks — 生成式藝術 NFT 先驅平台
- fxhash — Tezos 上的生成式藝術平台
- fxhash 文件 / Guide — 如何在 fxhash 上發布作品
- Generative Art and NFTs (Tyler Hobbs) — Fidenza 創作者的部落格
- Creative Code Art — 生成式藝術靈感收集