小菜:老鸟,我在 openprocessing 网站上看到了一个作品,点赞数蛮多的,作品也挺有意思。
老鸟:哦?
小菜:这个鸟来回变换,不重样,诺,你看!
老鸟:确实挺有意思,有点像前阵子看过的一个关于区块链的新闻。
小菜:啥新闻?我来兴趣了!
老鸟:英国 12 岁男孩本雅明绘制了 3350 张形态颜色各异的鲸鱼,在区块链上以 NFT 的形式出售,赚到了不少虚拟货币,价值相当于 250 万人民币!
小菜:(惊呆了!)真**牛逼啊!
老鸟:我们来一起分析这种效果是怎么实现的吧!
小菜:哦耶✌️,走你!学完我就用代码生成形态各异的主题画,也去卖,哈哈哈哈哈...(小菜幻想着走向了人生巅峰!)
作者将整个鸟,拆分成了
4个大的部位。
老鸟:之前文章《玩转Processing生成艺术不可不知的几个创作手法》中也提到玩转 Processing 生成艺术常用的一些创作武器,基本图元如
增强武器如
结合这个例子,我们来看看作者用到了哪些手法。
rect[1] arc[2] circle[3]
1)mouth = rect + arc
// 注意后4个圆角参数
// top-left(tl)左上
// top-right(tr)右上
// bottom-right(br)右下
// bottom-left(bl)左下
rect(a, b, c, d, tl, tr, br, bl)
// 左上圆角 = 20, 右上圆角 = 15,
// 右下圆角 = 10, 左下圆角 = 5
rect(30, 20, 55, 55, 20, 15, 10, 5);
1)身体的各个部位长和宽随机
// 随机一个系数
const mult = random(0.7, 1.05);
// 根据屏幕宽高确定一个单元格尺寸
const u = min(width, height) * 0.15 * mult;
// face width 脸部宽度
const fw = u;
// face height 脸部高度
const fh = u;
// mouse width 嘴巴宽度
const mw = random() > 0.5 ? u * 0.5 * random(0.8, 5) : u * 0.5 * random(0.5, 1.3);
// mouse height 嘴巴高度
const mh = u * 0.5 * random(0.5, 1.8);
// body width 身体宽度
const bw = u * 2;
// body height 身体高度
const bh = bw;
// tail height 尾巴高度
const th = u * 2 * random(0.5, 1.5);
// 中间点x
const cx = width * 0.5;
// 中间点y
const cy = height * 0.55;
// 鸟的左上角横坐标x,用于translate后使用相对位置
const x = cx - (bw) / 2;
// 鸟的左上角纵坐标y,用于translate后使用相对位置
const y = cy - (fh + bh);
// 树木宽度
const treeLen = width * 0.6;
2)颜色分为4个颜色组,每次随机一个颜色组,并且将颜色组内颜色进行 shuffle 洗牌打乱操作,这样即使随机到了同一个颜色组,也会尽量避免出现鸟颜色一模一样的情况
const URL = [
"https://coolors.co/eb300f-fe7688-fff566-212121-2eb254",
"https://coolors.co/550527-688e26-faa613-f44708-a10702-e1d6de",
"https://coolors.co/124e78-f0f0c9-f2bb05-d74e09-6e0e0a",
"https://coolors.co/564787-dbcbd8-f2fdff-9ad4d6-101935"
]
let COLS;
···
function draw() {
// 根据url中的色值创建颜色数组
COLS = createCols(URL[frameCount % URL.length]);
// 对数组进行洗牌操作,打乱颜色的顺序,这样即使使用了同一个url中的色彩值,按照数组索引编号取到的颜色值具有随机性
COLS = shuffle(COLS)
...
}
// 随机颜色
function createCols(url) {
// 找到最后的斜杠 /
let slaIndex = url.lastIndexOf("/");
// 截取斜杠后面的字符串,得到 eb300f-fe7688-fff566-212121-2eb254 这样多个使用-连接的颜色
let colStr = url.slice(slaIndex + 1);
// 将上一步的颜色字符串用 - 分割,得到如 ["eb300f", "fe7688", "fff566", "212121", "2eb254"]的颜色数组
let colArr = colStr.split("-");
// 使用数组的map方法,映射得到如如 ["#eb300f", "#fe7688", "#fff566", "#212121", "#2eb254"]的颜色数组
return colArr.map(c => "#" + c)
}
3)图案模式的随机
const UNITFUNCS = [check, triPattern, curveRect, stripe];
作者将图案抽象成了4种模式:check、triPattern、curveRect、stripe。这4种模式用于填充鸟的 body 部分的 rect。大家可以多观察观察鸟的变化,尝试修改代码,给 UNITFUNCS 只赋一个模式的值,看看具体该模式的样子。
鸟的 body 的两个 rect 的使用的是这4种模式进行随机,方法为drawRectTile
:
function drawRectTile(x, y, w, h) {
const fn = int(random() * UNITFUNCS.length);
UNITFUNCS[fn](x, y, w, h, shuffle(COLS "fn"));
}
两个 arc 使用的是多个不同直径圆叠加然后遮罩,或者是多个矩形横竖排列,方法为drawArcUnit
:
function drawArcUnit(cx, cy, wdia, hdia, sr, er) {
const cArr = shuffle(COLS);
ellipseMode(CENTER);
push();
translate(cx, cy);
noStroke();
fill(cArr[0]);
arc(0, 0, wdia, hdia, sr, er);
push();
drawingContext.clip();
const rn = random();
if (rn > 0.5) {
for (let i = 0; i < 3; i++) {
fill(cArr[i]);
ellipse(0, 0, wdia / 3 * (3 - i), hdia / 3 * (3 - i));
}
} else {
const ishori = random() > 0.5 ? true : false;
for (let i = 0; i < 6; i++) {
fill(cArr[i % cArr.length]);
if (ishori) rect(-wdia / 2, -hdia / 2 + hdia / 6 * i, wdia, hdia / 6 + 1);
else rect(-wdia / 2 + wdia / 6 * i, -hdia / 2, wdia / 6 + 1, hdia);
}
}
pop();
pop();
}
其中 face、body等部分,用到了 tile 网格的思路,将他们看成一个单元格,只是内部填充不同的图案
1)确定要绘制的目标,本篇是一个鸟。
2)将鸟尽可能地进行基本图元拆分,如本篇的rect
、arc
、circle
、ellipse
和triangle
。
3)使用随机因子
真不容易,恭喜你,亲爱的读者,居然能够读到这里还没有关掉页面。既然都读到这里了,不妨给自己命个题,使用同样的思路绘制一个其他动物,如何?敢试试么?
小菜将源码注释放在了 Processing 100 天速写 系列的 Day_023中,地址见 https://github.com/xiaocai-laoniao/Processing100DaysSketch/blob/main/Day_023/Day_023.js[4]
小菜与老鸟后期会不定期更新一些 Processing 绘制的代码思路分析,欢迎关注不迷路。
如果有收获,能一键三连么?
[1] rect: https://p5js.org/reference/#/p5/rect
[2] arc: https://p5js.org/reference/#/p5/arc
[3] circle: https://p5js.org/reference/#/p5/circle
[4] Processing 速写Day_023: https://github.com/xiaocai-laoniao/Processing100DaysSketch/blob/main/Day_023/Day_023.js