1. 项目概述与设计思路1.1 从“工艺”到“算法”我为什么起了这个名字我平时喜欢做手工也写代码。很多人觉得这两件事隔着很远一个靠手一个靠脑但我在做项目的过程中发现它们的内核完全一致都是把一堆朴素的材料通过反复推敲的工序变成一件有质感的东西。所以当我想做一个把程序化生成和视觉艺术结合的项目时我给它起了个名字叫artcraft。一方面它来自“art”和“craft”两个词的拼接想表达“艺术是需要手艺的”这个朴素观点另一方面它也是整个项目的方法论不追求一键生成的傻瓜化出图而是把每一步都当成一道工序来打磨从构图、配色、纹理到输出都自己掌控。这个项目解决的核心问题是如何用代码生成“有温度”的视觉作品。市面上有很多滤镜、AI绘画工具几秒钟就能出图但结果往往不可控风格也千篇一律。而 artcraft 走的是另一条路——把随机数、数学函数、颜色映射这些基础工具像拼积木一样组合起来最终形成一套能反复出作品、每次输出都不同的“数字手工作坊”。适合谁读如果你对程序化生成、创意编程有兴趣或者你本身就是设计师、插画师想探索新的创作媒介那这篇文章应该对你有用。我会把整个项目的思路、核心算法、踩过的坑都摊开讲你能直接照着复现一套自己的生成艺术工具。1.2 为什么选择程序化生成而不是直接用AI出图坦白说我也用过不少AI绘图工具。它们确实能给出令人惊艳的初始灵感但对我这种控制欲比较强的人来说AI生成的图有几个很难受的点结果不可复现同一组提示词每次出来的图都不一样你很难通过小幅调整来“修正”某个局部。精细控制困难AI擅长生成整体氛围但如果你想精确控制某一条线的走向、某个色块的位置它几乎做不到。审美同质化各大模型训练数据高度重合生成出来的风格越来越像缺少个人的辨识度。程序化生成恰好相反每一行代码都是你可以精确控制的随机种子决定了随机性的大小参数决定了分布的疏密。你改一个数字画面就会发生可预测的偏移。我把 artcraft 定位成一个“半自动手工工具”算法负责提供变化和惊喜人负责审美判断和参数调整。这很像传统手工艺中“三分手艺七分工具”的关系——工具再强大最终作品的好看与否还是取决于使用者对材料的理解。1.3 项目整体结构规则、随机、反馈artcraft 的代码结构其实非常简单核心就三层第一层是规则系统。规定画布有多大、背景是什么色、元素从哪开始分布。这一层相当于你铺好画布、调好颜料是所有后续步骤的基底。第二层是随机系统。但这并不是毫无章法的乱随机而是带权重的随机、带分布的随机。比如我可以让元素在画面中部的概率更高、边缘更稀疏形成一种视觉重心。这一层相当于你在作画时“手抖”的程度——它不是缺陷而是让画面活起来的关键。第三层是反馈循环。生成完一张图后我不会直接保存而是会把它丢进一个快速预览器里看缩略图。不满意就调参数重新跑满意了就导出大图。这种反馈机制非常重要因为它让整个创作过程变成了“人和算法的对话”。这三个层级合在一起才构成了 artcraft 完整的创作闭环。接下来的章节我会逐个拆开讲讲里面的核心逻辑和实操细节。2. 核心技术点解析2.1 画布模型与坐标系设计一切可控的地基很多人写生成艺术上来就铺满画布结果画面很容易乱成一锅粥。我在 artcraft 里的做法不同先定义好坐标系和画布模块再在这个框架里做文章。画布模型最重要的任务是确定“元素往哪里放”。我用的是一个分块偏移的策略。举个例子如果画布是 1200×1200 像素我先把它划分成 60×60 的网格每个格子就是 20×20 像素。然后在每个格子里放置元素时允许元素中心在格子范围内偏移一段距离。这么做的意义是元素不会完全均匀分布均匀反而死板也不会完全乱跑太乱则失去秩序感偏移量的大小可以由一个参数控制。参数小画面规整像理性主义建筑参数大画面松散像随手撒的种子。坐标系里面还有一个容易忽略的问题屏幕坐标系和数学坐标系的原点是反的。屏幕左上角是 (0,0)x 轴向右y 轴向下而常规的笛卡尔坐标系 y 轴向上。很多初学者会在这里栽跟头——明明是想在右上角画一个圆出来的结果却跑到右下角了。我在项目里封装了一个toScreen()函数把正方向朝下的映射关系做统一处理这样就可以直接使用自然的数学坐标进行创作。画布底层的代码大概长这样function toScreen(x, y) { return { sx: width / 2 x * scale, sy: height / 2 - y * scale }; }width和height是画布尺寸scale是缩放系数。所有绘制逻辑都先经过这个映射函数统一管理。还有一点值得强调gl_FragCoord 和像素坐标的问题。如果你用着色器shader做渲染GPU 的坐标也是左下角为原点的和屏幕坐标又不一样。我在项目早期用 WebGL 写采样器的时候经常输出上下颠倒的画面后来干脆写了一个通用的flipY开关在不同渲染模式间切换时才不会出错。类似的“坐标小坑”积累多了就是一套很实用的经验库。2.2 噪声与随机让程序“手抖”的艺术artcraft 中最核心的技术点其实是噪声。如果只用纯粹的随机数画面会产生很多细碎的噪点像电视机雪花屏一样而人类的视觉系统天然偏好连续、渐变的模式。所以必须引入噪声算法让相邻区域的随机性是“连续的”而不是跳跃的。我用的主力工具是Perlin 噪声和它的扩展Simplex 噪声具体到代码里用的是noise.simplex2这类函数。它的特点是输入相邻的坐标值输出的值也会相邻接近。用生活化的比喻来说掷骰子是完全独立的这次掷 6 不代表下次会接近 6而噪声更像是地形——你站在山顶往前走一步海拔还是高的再走几步慢慢下降是一个连续变化的过程。在 artcraft 里我一般用二维或三维噪声来控制元素位置的偏移量。通过噪声决定每个元素偏离网格中心的方向和幅度画面会形成有机的流动感。元素的大小变换。用噪声采样来控制半径或宽高让画面里的元素有自然的渐变而不是大小剧烈跳跃。颜色的明度变化。同一个色相下利用噪声让明度在局部有浮动避免大面积纯色带来的生硬感。还有一个经常被低估的参数频率。频率越高噪声变化越剧烈画面越细碎频率越低噪声变化越平缓画面越大气。我在画山川线条的时候会把频率调到很低让线条走势缓慢起伏而在做布料纹理时会把频率调高让细小的纤维感出来。频率这个参数我建议新手反复调试它对整体风格的影响大于绝大多数参数。2.3 参数化调色板与色彩管理如果说噪声是 artcraft 的骨架那色彩管理就是它的皮肤。代码生成的图案如果颜色配不好算法再精巧也会显得廉价。我在项目里做了一套参数化调色板系统本质是一个“颜色池 权重分配”的结构。颜色池的部分是这样实现的我会预定义几组色板每组包含 4 到 6 个颜色。这些颜色不是随手挑的而是通过 HSL 色彩空间做偏移生成的。比如我先定一个主色调H 值然后让其他颜色在不同明度L和饱和度S的维度上变化保持同色系内的和谐。const palette [ { h: 210, s: 45, l: 65 }, { h: 205, s: 70, l: 45 }, { h: 30, s: 80, l: 60 }, { h: 350, s: 60, l: 70 } ];这样做的优势是颜色之间天然有联系不会出现某个颜色突然“跳”出来的情况。权重分配的部分则跟画面结构有关。我会把颜色按不同概率分配给不同区域的元素。背景色通常是低饱和低明度的基底色占比最大视觉重点元素用亮部颜色占比最小但最突出。在这个过程中有个非常关键的实战经验不要在 RGB 模式下直接做大范围随机颜色这样出来的颜色几乎必“脏”。RGB 三个通道独立取值容易产生没有明确色相的灰色或者土色视觉上非常浑浊。用 HSL 或 Lab 颜色空间做采样颜色才“干净”。这也是 artcraft 色彩系统设计的最重要一条原则。另外色彩的数量也不是越多越好。我看过很多人做生成艺术觉得颜色越多越丰富结果画面反而失去了重点。手工调色和绘画是一个道理真正高级的配色往往只有两三个主色加上一两个点缀色。artcraft 默认色板只放 5 个颜色画龙点睛的关键在于“哪里用亮色、哪里用暗色”而不是“用了多少种颜色”。3. 实操过程从零搭起一个生成艺术工具3.1 搭建最小可运行的程序骨架这一节我会带你从零开始搭一个最精简的 artcraft 程序骨架。整个开发环境只需要一个浏览器和文本编辑器不需要安装任何重型依赖。用 p5.js 这个库来做打底因为它的语法简单、上手快而且自带噪声函数和矢量绘图能力。先创建一个 HTML 文件引入 p5.js 的 CDN 链接再写一个空的 script 标签。!DOCTYPE html html head script srchttps://cdn.jsdelivr.net/npm/p51.9.0/lib/p5.min.js/script /head body script function setup() { createCanvas(800, 800); noLoop(); } function draw() { background(240); } /script /body /html这个骨架核心就两个函数setup()在程序启动时执行一次负责初始化画布draw()在每一帧渲染时执行因为调用了noLoop()所以它只执行一次——非常适合静态生成图像。跑通这一步你就已经拥有一块 800×800 像素的空白画布了。接下来要做的就是在draw()函数里填充绘制逻辑。3.2 用噪声生成第一件作品流动的细线我建议从最简单的“细线网格”开始因为它只用到了前文提到的网格划分和噪声偏移但效果非常出片。我们来画一组网格线每一条线都由几个控制点连成。具体步骤拆解如下第一步定义网格大小和密度。比如画布 800 像素每隔 20 像素画一条线横向和纵向各 40 条。第二步确定每条线的路径。以竖向线为例它从画布底部延伸到顶部但不是直的——在每一个水平分段处x 坐标会叠加一个噪声偏移值。function draw() { background(240); stroke(40, 60, 80, 120); noFill(); let step 20; let yStart 0; let yEnd 800; for (let x 0; x 800; x step) { beginShape(); for (let y yStart; y yEnd; y step) { let offset noise(x * 0.005, y * 0.005) * 80; vertex(x offset, y); } endShape(); } }第三步的关键在于noiseOffset的两个参数。x * 0.005和y * 0.005决定了噪声的频率数值越小线条越平滑。我把两个维度都传入噪声坐标这样线条的偏移在上下方向是连续的而不是每一点都完全独立。第四步调整透明度。stroke(40, 60, 80, 120)中最后一个参数是 alpha 通道设置为 120 时线条会半透明重叠区域会出现更深的颜色形成水彩般的叠加质感。运行一下你应该能看到一组类似水波纹或织物纤维的线条。如果你觉得线条太飘可以增大x * 0.005的系数让噪声频率提高线条起伏会更频繁——不过我个人建议频率保持在 0.003 到 0.01 之间太大就会失去整体的流动感。3.3 加入随机种子让每个版本都独一无二同样的代码如果每次运行生成的噪声完全一样那这套工具的意义就打了折扣。好在 p5.js 提供了randomSeed()和noiseSeed()这两个函数分别控制普通随机数和噪声函数的随机序列。实操中我是这样做的在程序开头引入一个seed变量每次运行可以手动指定一个整数也可以让它随机生成。然后把这个 seed 展示在画面上方便回溯。let seed Math.floor(Math.random() * 10000); function setup() { createCanvas(800, 800); randomSeed(seed); noiseSeed(seed); noLoop(); console.log(Current seed: seed); }这个设计非常重要。你在调试时如果觉得某张图特别好看只要记住 seed 的值就可以随时还原。我自己的习惯是每次跑图都记录 seed然后在文件夹里按“seed值-参数值”命名导出的图片。这样积累几百张后就已经形成自己的素材库了。我在实践中发现一个细节randomSeed()和noiseSeed()是两个独立的随机序列它们互不影响。也就是说如果你改了其中一个 seed普通随机的位置会和噪声偏移的分布互相错位产生另一种完全不同的画面。所以有时候保持所有参数不变、只改噪声 seed也能收获意想不到的效果。3.4 从单张出图到批量生成细节调整与参数探索一个单独的生成脚本只能算一个玩具真正让它变成“工具”的关键是让参数可以被快速批量调整。我后来做了一版带参数面板的 artcraft把网格密度、噪声频率、偏移量、颜色透明度、线条粗细都提取成了顶部变量。批量生成时我一般会跑一个循环脚本让密度从 10 逐级变化到 50频率从 0.003 变化到 0.01每档跑 5 个不同 seed最终可能产出 100 多张候选图。这个过程不太能全自动完成——虽然程序可以连续跑但筛选和评价这件事必须人来判断。这也是 artcraft 和其他“一键生成”工具最大的区别它不会替你做审美决定。批量跑图只是帮你快速探索风格空间的边界真正的创作发生在你翻看这 100 张图、挑出能打动自己的一张然后围绕它做精细调整的那个下午。在批量生成的实际操作里我踩过的一个小坑是canvas 尺寸太小会导致图片质感不足尺寸太大又会让批量生成变得缓慢。我的折中方案是先用 800×800 的小尺寸批量探索风格选定后再用 2400×2400 的分辨率重新渲染最终版本这样既保证了效率又保证了出图质量。3.5 导出与后处理让噪点作品变成可用的素材最后一步把程序生成的图像导出成可使用的素材。直接saveCanvas()固然可以但如果你要用于印刷、海报或者进一步后期处理有几个细节值得注意建议导出 PNG 格式而不是 JPG。生成艺术一般都有大面积的纯色或渐变色JPG 的压缩算法会在色块边缘产生明显的马赛克和伪影会破坏掉生成画面中本来的干净感。PNG 是无损压缩对线条和色块非常友好。导出时分辨率要尽可能高。我通常输出 2 倍或 3 倍于预览尺寸的图像也就是 2400 或 3200 像素。这里有个小技巧如果画面的核心是细线条导出 PNG 后用图像处理软件做一次轻微的“USM 锐化”会让线条边缘更利落但如果是大色块的纹理那就不要锐化保持柔和的过渡反而更耐看。有时候我也会把程序生成的图导入到编辑软件里做二次调整调整色阶增强对比或者叠加一个纸张纹理的图层让画面更有触感。但这是创意层面的加工不是必须的。大多数情况下artcraft 直接输出的成果已经可以作为完整作品使用了。4. 常见问题与排查经验4.1 画面“太乱”或“太平”问题可能出在噪声频率上很多第一次上手的人反馈生成的画面要不就是一团乱麻要不就是死气沉沉。我排查后发现问题几乎都出在噪声频率和目标形态的匹配度上。太乱的场景通常是频率调得过高了。比如你要画山脊线结果每 5 个像素噪声就剧烈变化一次线条会像心电图一样高频抖动。解决方法是把噪声频率降到原来的十分之一再试。我习惯用的调法先设很低频比如 0.001然后一点点往上加直到画面出现“恰到好处的呼吸感”为止。太平的场景则往往是频率过低或者偏移量过小。线条太规整、太笔直缺乏手绘般的偶发感。这时要么把偏移量倍数加大要么把频率提高 3 倍。总之参数不是越大越好而是和画面主题匹配才好。这个匹配的过程必须靠肉眼反复对比无法用一个万能公式解决。还有一个补充技巧可以用两层噪声叠加一层低频做主要形态一层高频做轻微细节扰动。低频保证结构稳定高频增加质感这一招在模拟手绘笔触时效果极佳。4.2 配色“脏”了根源往往在颜色空间如果你发现生成的作品颜色灰蒙蒙、不够通透先别急着调色板里的色值。最大的可能是你直接在 RGB 空间里做了颜色的随机或插值。前面说过RGB 的问题是三个通道互相独立随机组合容易产生没有倾向性的“死色”。我在 artcraft 里已经内置了 HSL 模式的颜色采样函数这里再补充一下它的用法function getPaletteColor(i, t) { let c palette[i % palette.length]; return color( c.h Math.sin(t) * 10, c.s, c.l noise(t) * 12 ); }这里我用sin(t)给色相加了一个小幅震荡让同一个色相在相邻区域有细微变化用噪声扰动明度让颜色有明暗起伏。关键是永远不要让色相和明度同时大幅随机否则颜色会失去控。饱和度也是一个陷阱。大量低饱和度的颜色会让画面显得“灰”但所有颜色饱和度过高又会让画面“艳俗”。我的经验是画面中大部分颜色饱和度控制在 20 到 50 之间只留一到两个亮色饱和度拉到 70 以上作为点睛。这样画面既通透又有重点。4.3 性能卡顿与分辨率取舍当画布尺寸增大、网格密度提高后程序运行会变得很慢这是必然的。毕竟每一帧都在循环计算大量顶点的噪声偏移值。我的优化经验分了几个层次第一档优化是减少不必要的计算。如果你的画面不涉及动画一定要用noLoop()暂停循环如果某个噪声值在 N 个帧里保持不变就把它先算好存到数组里而不是每次重复计算。第二档优化是降低采样精度。比如在探索阶段用一个很小的画布400×400画面效果相差不大但计算量只有大尺寸的十分之一。选定方案后再切换到全分辨率出图。第三档优化是算法层面的优化。p5.js 的noise()函数虽然好用但速度不算快如果瓶颈在噪声计算可以自己写一个预计算的噪声网格或者用查表法替换。不过这就属于进阶优化了新手阶段建议先把握前两档。4.4 随机种子管理与创作回溯最后这条是长期使用 artcraft 最重要的习惯保存种子和参数。不管你是手动改代码还是用参数面板每一个版本生成的文件名里都应该带上 seed 值和关键参数。我带过的一个学生第一次做出满意的作品时因为没记录种子后来想微调同一张图却怎么都复现不出来只能从草稿重新调参花了整整一个下午。这个教训很深刻。所以我的实践是每次运行生成图都会自动保存一份 JSON 格式的参数快照里面记录种子、画布大小、噪声频率、偏移量、色板 key 等信息。这个习惯坚持下来后回头翻看自己一周前甚至一个月前的作品都可以很轻易地复现和调整创作的延续性大大提升。5. 延伸方向与个人经验5.1 从静态图像到动态生成artcraft 目前主要做静态视觉但它的底层逻辑天然适合延伸到动态。只要在 draw 函数里去掉noLoop()再引入一个时间变量就可以让噪波偏移量随时间变化生成流动的动画。这个方向我做了一个小的 demo让网格线条随时间缓慢波动就像水面的倒影。实现方式很简单噪声的第三个维度用帧序号除以帧率后叠加上去。但动画和静态有一个根本区别静态图追求的是“定格的和谐”而动画追求的是“运动的节奏”。所以动画的参数控制比静态更敏感频率稍微高一点画面就会像触电一样疯狂抖动频率太低又看不出变化。建议动态版本的噪声频率比静态版本再降低 3 到 5 倍让你的参数在“看得出变化”和“变化太突兀”之间找到平衡点。5.2 与手工制作结合数字生成 物理呈现前文提到 artcraft 的核心理念是“艺术是门手艺”这个观念也体现在它和实体手工艺的结合上。我有一个实际案例把 artcraft 生成的纹理图案印在布料上然后用刺绣的方式在关键位置补了几针。程序生成的图案提供了复杂而规律的底纹手工刺绣则提供了人的温度和随意感两者叠加后形成了非常独特的作品。这个概念也可以延伸到陶瓷贴花、丝网印刷、激光雕刻等领域。对于想尝试这个方向的朋友我的建议是先做小尺寸的实验比如 10×10 厘米的布片或木板用低价材料测试颜色映射和材质之间的配合。程序里调好的颜色在具体材质上会有很大差异——布料会吸水变得更深金属雕刻的明暗是凸起而不是颜色差异这些都需要实测后反推参数。5.3 在界面设计和视觉素材库上的应用artcraft 也具备实际商业价值最直接的是用来做设计素材。比如它可以生成背景纹理、海报底图、产品包装的图案元素。由于输出的图是矢量或高分辨率位图设计师可以自由缩放使用不用担心版权和同质化的问题。我做过一个项目就是为一家公司的品牌视觉系统生成专属的抽象纹理。核心不是用 artcraft 跑一张好看的图而是先分析品牌气质确定色调倾向和纹理密度再反过来调整生成参数。最终产出的素材和品牌调性保持高度一致且可以快速生成多个变体版本供选择。这个工作流程如果要总结就是先定审美再定参数最后定输出。顺序反了产出的东西往往好看但不好用。5.4 最后想分享的一个小技巧写到这里我想分享一个在 artcraft 迭代过程中最有价值的经验建立“失败品库”。不只是保存成功的作品更要保存那些“跑出来觉得丑”的图。因为你的审美标准是会成长的三个月前觉得垃圾的图现在回头看可能只是配色不对而那些当时“惊艳”的作品如今可能觉得套路化。把失败品和成功品放在一起定期回看你能非常清晰地感知到自己的参数调整水平和审美判断在同时进步。另外每次调参数的幅度不要太大每次只改一个变量记录前后变化。这跟做科学实验的思路一致——变量隔离才能积累有效经验。如果一次性改了五个参数画面虽然变了但你永远不知道是哪一步导致了最终的效果。这可能是 artcraft 这个项目教给我最接近“手艺”的东西不是靠灵感而是靠反复实验、积累经验和尊重每一步细微的变化。