news 2026/9/18 6:59:12

用代码复现米罗风格:参数化生成“米罗水族馆”的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用代码复现米罗风格:参数化生成“米罗水族馆”的完整指南

做了个小工具,把米罗画里的那些弯弯绕绕的线条、圆点、月牙,和鱼的形态搅在一起,让它自己长出一条又一条不可能存在的鱼。朋友看了生成结果的第一反应是问我在哪学的画画。我说,没学,这段代码写的。她愣了几秒,然后把手机递过来,让我把鱼变大,她想截个图当壁纸。那一刻我知道,这事成了。

如果你也喜欢米罗那些充满童趣的符号语言,又恰好会一点代码,想做一个艺术风格极强的生成作品,那这篇东西就是为你准备的。从视觉语言拆解、鱼形参数化、色彩系统到完整代码实现,我尽量把每一步怎么想、为什么这么写都交代清楚。涉及的部分不需要你有很强的编程背景,只要愿意动手,看完全文就能复现出一套属于你自己的“米罗水族馆”。

1. 米罗的符号世界,天然就是一套编程语法

先说个很多人都有的疑问:米罗的画看起来这么“自由”“随意”,像是随手画的儿童涂鸦,怎么可能用代码去生成?这不是自相矛盾吗?其实恰恰相反,米罗的绘画系统是我见过的最适合“参数化生成”的艺术语言之一。

原因很简单:米罗的视觉词汇高度符号化、高度类型化。圆点是圆点,星是星,月牙是月牙,直线是直线,他的画里没有复杂的透视结构、没有细腻的明暗交界线、没有体积感塑造。这些东西本来就是高度“矢量”的,本质上就是几何元素和线条的组合,只是组合的韵律和节奏非常个人化。这就是为什么很多人看米罗的画会有一种“我上我也行”的错觉,但自己真画起来又总觉得哪里不对劲——因为难的不是元素,而是组合的韵律。

把这个观察转换到代码世界,就是一个很清晰的映射:

  • 定点圆和自由线 -> 点和圆函数
  • 月牙和三角形 -> 可旋转的路径图形
  • 粗细不一的黑线 -> 可变strokeWeight的线条
  • 铺色块 -> 半透明或不透明的填充形状

换句话说,米罗的每一张画都可以被看作一串非常优美、充满张力的绘图指令序列。我们做生成艺术时,只需要构造一套“符号生成器”,让程序自动生成这些指令序列,再控制序列里的节奏感,就能复现出很强的米罗气息。

这也是我把这个项目取名为“MiroFish”的原因。MiRo是米罗的谐音引用,Fish是鱼的外形骨架。整个项目的内核就八个字:用鱼的形态,承载米罗的视觉语法。艺术风格提供审美秩序,鱼形提供结构框架,生成算法提供无穷变化——这三者缺一不可。

顺着这个思路,我把“米罗风格”拆成了三个可以在代码里精确控制的核心属性:

属性视觉表现代码映射
形状有机感、无棱角、像软糖一样贝塞尔曲线和圆角多边形
符号圆点、眼点、月牙、小星、竖线独立绘制函数,按概率出现
色彩红黄蓝绿等高饱和原色,分区铺陈预置色板 + 随机权重抽取

每一只鱼本质上就是这三个属性的一个随机组合实例。代码跑一次就生成一条鱼,跑一万次就是一万条鱼,理论上没有穷尽。

2. 鱼形怎么来:贝塞尔曲线的生物感与控制点博弈

鱼形是整个项目里最关键的一步,也是最容易搞砸的地方。太写实了就和米罗风格冲突,太抽象了又看不出是鱼。我调了两三个晚上才找到合适的平衡点,这里直接把方法和思考过程分享出来。

2.1 为什么不直接用椭圆或基础几何图形拼鱼身

很多人做这种项目时,第一步想到的肯定是:鱼不就是一个椭圆加个三角形尾巴吗?的确,最基础的鱼形可以这么凑合,但生成出来的东西往往缺乏生命力,像一个简笔画图标,不是一个有感觉的图像。因为真实生物的形态充满了不对称、起伏和微妙的变化。

米罗本人画的鱼或生物也从来不按规则几何来,他画的线条通常带有一种模仿有机生命体的柔软感,是“生长的”而不是“描绘的”。因此我的思路很明确:鱼身用曲线来定义,而不是用几何运算。

P5.js里画曲线最方便、也最好控制的做法是bezier(),四个点两两组合,起始点、终止点、两个控制点。给足控制点参数,就可以画出非常丰富的起伏。

2.2 鱼身轮廓的参数化起点

我把鱼身抽象成一条基于中点线的两边轮廓。先假想鱼从头到尾有一条中心轴,然后鱼身的上下两条边缘线分别基于中心轴生成:

// 核心思路:沿鱼轴方向取多个点,根据距离鱼头/鱼尾的位置 // 计算上下边缘到轴线的距离,也就是鱼的“半厚函数” function fishOutline(axisPoints, thicknessProfile) { let upper = []; let lower = []; for (let i = 0; i < axisPoints.length; i++) { let offset = thicknessProfile[i]; // 取垂直于轴线方向的偏移 let perpendicular = createVector(-axisPoints[i].y, axisPoints[i].x).normalize(); upper.push(p5.Vector.add(axisPoints[i], p5.Vector.mult(perpendicular, offset))); lower.push(p5.Vector.sub(axisPoints[i], p5.Vector.mult(perpendicular, offset))); } return { upper, lower }; }

上半边缘和下半边缘各是一个点的序列,然后我把这些点输入给beginShape()+vertex()+quadraticVertex()bezierVertex(),处理成平滑的轮廓线。

关键在thicknessProfile怎么设计。我的经验是不要用固定值,而是用一个类似钟形曲线的分布:贴近鱼头的地方比较窄,中间偏前的位置最宽,接近尾柄的地方快速收窄,这样鱼身的流线感立刻就出来了。

2.3 控制点抖动:让每条鱼都有自己的身体

如果每条鱼的厚度曲线都一样,那代码生成一百条鱼跟复制粘贴一百条没有任何区别。要增加生物感,就必须让形状具备随机性——但这里的随机不是毫无约束的随机,是“受控抖动”。

我的做法是给厚度数组加上Perlin噪声:

let t = noise(i * 0.12, seedOffset); let thickness = baseThickness * (1 + (t - 0.5) * 0.4);

noise()会生成平滑连续的随机值,不会像random()那样跳变,所以画出来的鱼身轮廓是自然起伏的——有的鱼背略微隆起,有的鱼肚子圆鼓鼓,每条鱼看起来都像是同一种鱼的不同个体,而不是同一张脸的复制品。

控制点坐标的整体缩放也很重要。我的鱼身轴长在80到140像素之间浮动,厚度比例在0.18到0.26之间浮动,这两个值直接决定了鱼是修长型还是胖墩型。

2.4 鱼尾和鱼鳍的抽象逻辑

鱼尾和鱼鳍是风格化的重点。米罗画里的“三角形”常常不是几何三角形,而是像折扇一样的曲线三角形。我在代码里用两条三次贝塞尔曲线拼出尾鳍,先确定尾柄末端作为三角形顶点,再分别画两片尾叶,每片尾叶都用一个曲线路径勾边:

function fishTail(basePoint, directionAngle, size, wiggle) { // basePoint是尾柄末端,directionAngle决定尾巴甩向哪一侧 // wiggle控制尾部曲线的不对称程度 push(); translate(basePoint.x, basePoint.y); rotate(directionAngle); // 左叶 noFill(); stroke(0, 0, 0); strokeWeight(2); beginShape(); vertex(0, 0); bezierVertex(size * 0.6, -size * 0.25, size * 1.1, -size * 0.6, size, -size * 0.9); bezierVertex(size * 0.7, -size * 0.5, size * 0.3, -size * 0.2, 0, 0); endShape(); pop(); }

胸鳍和背鳍则简化成飘带状。飘带感的秘诀是:线条的起点固定在鱼身上,终点随着一个正弦波时间变量或随机相位移摆动。这样出来的静态作品像截取了一个运动瞬间,画面有定格感,而不是干巴巴的标本图。

这里有我踩过的一个比较深的坑,提醒一下:鱼尾尺寸比例不要贪大。最初我把鱼尾设成身体长度的0.7倍,视觉上非常夸张,放出来朋友说像一只蝴蝶长了鱼头。后来反复试了几版,最终定在0.3到0.45倍之间,这个范围在“一眼是鱼”和“米罗式奇幻生物”之间最耐看。

3. 米罗视觉语法的程序化实现:点、眼、月牙与线

形状框架搭好之后,真正给鱼注入米罗灵魂的是那些点缀性的符号元素。米罗的画里大量使用圆点、眼睛形状、月亮弧线和类似“米”字星的结构,这些元素单独看没有什么了不起,组合起来却能有极强的辨识度。

在这一步,我做了五个可以在鱼身上随机分布的小元素,所有元素都基于鱼身的局部坐标系生成,这样鱼在移动、旋转、缩放时不会出现符号“飘”在鱼外面的问题。

3.1 圆点和双色同心圆

圆点是最简单也是最高频出现的元素。变化出在填充色和描边色上,我允许三种模式:实心小圆、白底黑边同心圆、以及红蓝双色半圆拼合。米罗作品里经常出现在纯色底上叠一个对比色小圆点的做法,这个对比关系几乎是米罗个人风格的签名。代码实现上就是用ellipse()嵌套,第一层大圆填充主色,第二层小圆填充辅色或白色,描边统一黑色、两像素宽。

3.2 月牙形

月牙是米罗符号系统里极有代表性的一个。实现方式不复杂,先画一个扇形或圆弧,再用另一个覆盖的圆“挖”掉一部分。在Canvas 2D和p5.js里更省事的方案是直接画两条贝塞尔曲线闭合形成一个新月形:

function crescent(cx, cy, radius, angleOffset, thickness) { push(); translate(cx, cy); rotate(angleOffset); noStroke(); fill(244, 196, 48); // 米罗黄 beginShape(); vertex(radius * cos(0), radius * sin(0)); bezierVertex(radius * 0.8, radius * 0.4 - thickness, radius * 0.2, radius * 0.5 - thickness, 0, 0); bezierVertex(radius * 0.2, radius * -0.1, radius * 0.6, radius * -0.3, radius * cos(0), radius * sin(0)); endShape(CLOSE); pop(); }

这段代码的核心思路是:起点在圆弧左上端,中间经过内凹点,再弧回到起点,让封闭区域形成半月形状。参数thickness控制月牙胖瘦,radius控制整体大小。我习惯在鱼身、鱼尾、甚至背景里都随机撒几颗月牙,它会快速把画面拉进米罗的气场里。

3.3 眼睛符号:米罗世界里最重要的“表情包”

米罗的眼睛符号非常标志性,有时是十字上的圆点,有时是椭圆里套圆点,有时是侧脸的“一只眼睛”形态。这类符号用到鱼身上会产生奇效——因为鱼本身没有表情,但加上米罗式的眼睛,它就有了拟人化的情绪。代码里我实现了三种眼睛:

第一种是最简单的实心圆点,第二圈用白色描边,黑色主体。第二种是一个黑点在圆内偏上位置,像人在翻白眼,俏皮感很强。第三种是我最爱用的:“线挂眼”,一条短直线垂直落下,末端顶点一个圆点,米罗很多人物肖像里都有这个细节。这个眼睛一旦出现,整条鱼的“性格”立刻跳出来,艺术感也最强。

3.4 十字小星与竖线组

除了圆、月牙、眼睛,米罗作品里还经常出现疏疏落落的小十字星点,和三五根并列的竖短线。它们不是画面主角,更像是一种呼吸感的铺陈,让画面的留白部分不至于空洞。

在我的鱼身上,这些元素通常出现在鱼身的中后段或鳍的边缘。竖线组的长度、间距、根数都做随机化,三根到六根不等。那个小十字星点上还会偶尔带一个短横线,类似音符上的符杠,画面里会多出一丝飘动的音乐感。

function starCross(x, y, size) { stroke(20, 20, 20); strokeWeight(Math.max(1.5, size * 0.08)); line(x - size / 2, y, x + size / 2, y); line(x, y - size / 2, x, y + size / 2); }

3.5 如何控制符号的密度不破坏鱼形

这是生成艺术里最常见的问题:元素一多,主体形状就被淹没,整个画面变得杂乱无章。我在实践里摸索出一条规则,简单但有奇效——把鱼身分成前、中、后三段,符号数量按比例配额

  • 鱼头前段(从吻部到鳃盖):最多两个符号,通常放眼睛系
  • 鱼身中段(最宽的区域):最多三个符号,通常放圆点或月牙
  • 鱼尾后段(尾柄到尾鳍):最多一个符号,或空着

每个符号还有最小间距约束,距离太近时会重新采样。这个规则的背后逻辑是:米罗的画虽然看起来满,但每个元素之间都有呼吸和停顿。如果像一个圣诞树一样把所有东西都堆上去,就完全丢失了他那种闲庭信步的自由感。

4. 色彩策略:不是随意上色,是计算出来的童趣

色彩是MiroFish项目里最容易被忽略、但影响最巨大的模块。同样的鱼形和符号系统,用错颜色,出来的效果像劣质儿童涂鸦APP;用对颜色,就是可以挂在客厅的装饰画。这里面的差距不在配色审美的高低,而在你有没有把色彩当成一个系统性工程来管理。

4.1 米罗色板的三层结构

米罗的用色习惯并不复杂,他偏爱高饱和度、清晰的原色色相。我统计了自己喜欢的米罗作品后,把常用的颜色归纳成三层结构:

层级颜色角色常见色值
底色层背景大色块灰白、淡黄、暖白
主色层鱼身主体色正红、正黄、钴蓝、翠绿
点缀层眼、点、鳍的对比色黑、白、橙、草绿、中蓝

每只鱼生成时,从主色层随机取一个颜色作为鱼身填色,从点缀层取一到两个颜色做符号,最后用黑描边让整个轮廓统一。

4.2 色块分离的秩序感

看米罗的油画时会注意到,他常常在平涂的大色块中保留一点“手绘感”——颜料之间会有轻微的晕染和覆盖,不是绝对干净的矢量边界。我虽然用生成代码,但也希望视觉上保留这种质感。

实现方式是给填充色增加一个两像素的羽化或者给大面积色块稍微增加一个同色系的暗部偏移。具体操作:先用深一个色调填充鱼身整体轮廓,再缩放到约90%后叠上主色,这样鱼身的边缘就自然出现了一圈“手绘阴影”,整体立刻显得有厚度而不扁平。这个技巧非常便宜,但效果超出预期。

4.3 限定色板带来的约束力与辨识度

我在设计色板时特意下了约束:主色层就五个颜色,点缀层八个颜色,绝对不动态生成任意色相。因为米罗的辨识度很大程度来自颜色的精确性,屏幕上低饱和度的细微分色反而会削弱风格感。

也就是说,限制自己反而是获得自由的方式。只用五个主色,每条鱼鱼身的色相是稳定的,但通过明度变化、与其他符号颜色的搭配,能产生丰富的组合。每一条鱼都不一样,但放在一起必然是一家人。

4.4 白底黑线:米罗风格的隐形骨架

色彩之外,最重要的不是别的颜色,而是黑和白。米罗的很多作品底色是画布本身的米白,轮廓线是硬朗的黑色。这个黑白系统像骨架一样把彩色的形体和符号串联成一个整体。如果去掉黑线,米罗的很多元素拼在一起会显得轻飘飘、像贴纸。一旦加上那根两像素黑线,整个画面立刻有了分量和秩序。

因此我的代码里有一个严格约定:所有可见形状的描边默认是黑色,描边宽度在2到3像素之间,不随图像缩放而缩放。使用stroke(0)strokeWeight(2)的优先级高于其他任何色值设定。很多初学者在这个问题上翻车,总觉得黑线丑,想改成灰色或棕色,结果风格辨识度直线下降——这其实是误解了米罗绘画中黑线的组织作用。

5. 从单只鱼到一幅完整的画:生成逻辑的工程化实现

前几部分的逻辑都是围绕“单只鱼怎么做得像米罗”,但一个作品只有一条鱼显然不够。当鱼超过三条、元素超过几十个之后,生成逻辑的工程化程度就直接决定最终画面是否干净、有序、有主次。这一节讲我从零构建整个生成流程时踩过的关键节点。

5.1 生成一张MiroFish作品的完整流程

我最终的代码是P5.js,整个生成过程分六个步骤:

  1. 初始化画布与随机种子,设定背景底色
  2. 按概率生成水面波纹和前景装饰符号
  3. 生成2到5只鱼的位置列表,确保鱼与鱼之间不重叠
  4. 对每条鱼依次执行:选色板 -> 画鱼身 -> 画鱼尾鳍 -> 画符号
  5. 叠加一层极淡的“纸纹噪声”颗粒,模拟画布肌理
  6. 输出PNG

定位鱼的位置时,我用了一个简单的overlapCheck()函数:给每只鱼一个几何包围盒,两两之间做矩形相交测试,相交就重新采样。最多重试四十次,超出后减少一只鱼的数量。在实际使用中,这个方案又快又稳定,很少触发上限。

5.2 随机种子的使用:一次生成,永续复现

生成艺术需要很强的可复现性——你可能喜欢某张的输出结果但忘记保存,或者同一设置想再生成十张兄弟版,这时候随机种子就非常关键。在P5.js里,一行randomSeed()配合noiseSeed()就能锁死所有随机数。

let seed = int(random(100000, 999999)); randomSeed(seed); noiseSeed(seed); print('当前种子: ' + seed);

我建议在画面上方把种子号也画成一行小字,或者同时输出到控制台。这样你或用户看到喜欢的图时,可以拿着种子号重新生成。做系列作品时,种子号更像是“DNA编号”,既是身份标识,也是复现钥匙。

5.3 纸纹噪声:数字生成作品的最后一毫米质感

纯数字生成的画面无论元素多精致,总会显得太新、太整齐,仿佛刚刚打印机打完。为了让它更接近手绘作品的质感,我会在所有元素画完后叠加一层很淡的随机噪声颗粒:

loadPixels(); for (let i = 0; i < pixels.length; i += 4) { let grain = random(-18, 18); pixels[i] += grain; // R pixels[i+1] += grain; // G pixels[i+2] += grain; // B } updatePixels();

噪点强度控制在正负18以内,肉眼不会觉得画面脏,但会明显消除数字绘图的“光滑塑料感”。这个技巧对于生成艺术来说价值非常高,几乎可以在任何项目中复用。

5.4 完整的骨架代码示例

由于完整实现太长,这里贴一个精炼版的骨架代码,涵盖鱼身轮廓和符号调用的主体逻辑。你可以在这个基础上扩展:

function drawFish(x, y, size, seedOffset) { push(); translate(x, y); rotate(random(-0.3, 0.3)); // 1. 鱼身轮廓 let bodyColorA = randomColor(mainPalette); let bodyColorB = darken(bodyColorA, 12); // 深色打底 fill(bodyColorB); noStroke(); fishBodyPath(size * 1.04, seedOffset); // 主色覆盖 fill(bodyColorA); fishBodyPath(size, seedOffset); // 2. 鱼尾 fill(secondColor); fishTail(size * random(0.3, 0.42), random(-0.2, 0.2), seedOffset); // 3. 符号 drawSymbolsOnFish(size); // 4. 黑色轮廓(最后统一画,保证所有曲线外层都有黑边) noFill(); stroke(0); strokeWeight(2); fishBodyPath(size, seedOffset); // 再次描边路径 pop(); }

注意我特意把黑色描边放在填充之后单独画一层,而不是在填充函数里直接开stroke()。这样做的原因是:P5.js里描边和填充是同时渲染的,如果填充和描边同时画,最后叠加其他符号时会出现描边被后续元素盖住的问题。分层绘制可以让黑线始终保持在最上层,画面轮廓更干净利落。

6. 生成鱼群时的节奏控制:稀疏与留白是米罗的呼吸感

当你能稳定生成一只好看的鱼之后,新的问题马上出现:鱼多了,画面却乱了。很多生成艺术项目做出来的单张元素都非常惊艳,一旦让多个元素同屏,画面就成了一锅粥。这背后是布局与节奏的失控。

米罗的画很少有密集堆叠的时刻,他的叙事是靠元素之间的“关系”完成的——一个圆点靠近一个月牙会形成对话,三个竖线排在一起会有音乐的节拍感。如果离得太远,各走各的,画面就散了;如果挤在一起,又失去了张力。我把这个规律总结成两句话:元素之间既要有联系,又要有距离;留白不是空,是画面结构的缓冲区。

在代码里实现这种微妙的平衡,我参考了三大原则:

6.1 主从关系:一个中心鱼的视觉重量

整幅画里必须有一条视觉上最重的鱼,它或者尺寸最大,或者颜色最跳跃,或者符号元素最丰富。其余鱼保持小一号或素色一些的姿态。没有主从关系的画面,即使所有元素都精致,看起来也只是一个目录页或素材拼贴。我的实现方式是:先随机选一条鱼作为“主鱼”,尺寸设为100到130%的基准值,其余鱼强制限制在70到90%的基准值。

6.2 疏密节奏:非均匀分布

鱼的摆放不要像九宫格一样均匀分布。我使用Perlin噪声场来决定候选位置:让x和y都经过噪声映射后,选择噪声值较高的区域作为“密集区”,让更多鱼出现在那里,而画布的其他区域保持大面积留白。这个做法的视觉结果就是鱼群像洋流一样聚散有致,而不是棋盘上的棋子。

6.3 符号与鱼的呼应

有时候我会在靠近鱼身但脱离鱼体轮廓的空白区域,额外生成几粒小圆点或小星花,这一类“碎片”是我个人非常喜欢的手法。相当于鱼在游动时脱落的视觉鳞片,既连接了鱼和背景的关系,又增加了画面层次。实测下来,在每幅画中让1到2粒碎片符号出现在鱼的附近空白处,效果最自然。

7. 调参心得:那些让画面从“像”到“对”的参数细节

这一节单独拿出来写,是因为我觉得它价值最高。很多教程教你画出“对”的作品,却很少说清楚怎么从“像某个风格”调整到“就是某个风格”。这里面的差距是几个关键参数的微调。

7.1 边缘曲线的“松弛度”

最初版本里,我的鱼身曲线用是带夹角的多边形嵌套贝塞尔,结果出来的鱼形特别“紧”,像撑开的雨伞,缺乏米罗绘画里那种软塌塌的松弛感。后来我把贝塞尔曲线控制点之间的夹角放宽、让控制臂加长,鱼的轮廓才变得懒洋洋起来。最终确定的参数是:曲线的控制点长度在鱼身宽度的0.4到0.6倍之间,小于这个值鱼形紧绷,大于这个值会扭成奇怪形状。

7.2 符号大小与鱼身的比例

符号的大小对视觉冲击力影响极大。在米罗的作品里,部分圆点或星星并不比主体小多少,甚至和主体一样大,这也构成他画面的童趣感。因此在鱼身上,我允许个别符号达到鱼身长度的12%甚至15%;其余符号保持在5%到8%之间。大小差异拉开后,画面一下子有了戏剧性。

7.3 鱼身的颜色和相关“脏色”处理

副色不能抢主色的戏,但如果两个颜色属于同一色系且深浅太接近,鱼身就会显得平平无奇。我的折中方案是:副色从“主色的互补色或近似互补色”色板中抽取。比如鱼身偏红时,符号选黄或绿;鱼身偏蓝时,符号选红或橙。颜色之间的对冲会制造出一种视觉上活跃的颤动感,这与米罗作品中色彩的跳跃感非常统一。

7.4 微调时最容易忽略的旋转

旋转是很多生成艺术初学者的盲区。我的经验是,鱼的整体倾斜角在-15到20度之间随机,过大就有失衡感,过小则无方向感。鱼尾的摆动角度控制在30到50度之间。当鱼与鱼之间的倾斜角差异在40度以上时,画面会像一个真实的水族箱——鱼群往不同方向游去。如果角度太接近,它们就像排队过马路的鸭子,看着很刻意。

8. 规模化生产一套“米罗水族馆”系列作品的实操方案

当单张画能稳定生成之后,我开始思考这个项目更大的可能性——能不能直接生成一系列作品,形成一套视觉上统一、内容上丰富,可以直接输出成装饰画或做小展览的作品集?这个阶段的问题不再是“怎么画”,而是“怎么批量生产又能保持每一张的独特质量”。

8.1 建立“配方”文件:用配置控制风格倾向

我放弃了在代码里写死所有参数的做法,改成用一份JSON配置驱动生成过程。配置里可以指定主色色板、辅助色色板、鱼数量的上下限、符号密度、背景底色等。这样每次生成一批作品时,只要切换配置,就能在“非常接近的系列感”和“风格跨度极大”之间自由滑动。

const recipe = { canvas: { width: 1600, height: 1200, bgBase: "#f5efe2" }, palette: { main: ["#e63b2e", "#f5a623", "#2b7de9", "#9b3aa3", "#2e9e5c"], accent: ["#f7e05e", "#f28e2b", "#4aa3df", "#d0d3d4", "#1f1f1f"] }, fish: { countMin: 2, countMax: 5, primaryScale: 1.3, bodyThickness: [0.16, 0.24] }, symbols: { density: 0.55, minDistance: 16, includeCrescent: true } };

我强烈建议你从一开始就用配置化的思路管理这些参数,而不是把随机范围散落在代码各处。配置文件的另一个好处是,当你生成了一百张作品以后,可以随时回溯某一张图的确是用了哪一套配方生成的,方便做展览归档或系列整理。

8.2 成品筛选:不是每张生成图都值得展示

生成艺术总是伴随大量废稿。哪怕是调好参数的系统,也有大约12%的输出会因为布局不协调、鱼群叠太多或者风格倾向不明确而不适合展示。我的做法是生成后人工快速浏览一遍,从中挑选精品,再把种子号和配方文件存档。这个筛选环节不可省略——你会逐渐发现自己的审美也在参与生成过程,这是生成艺术创作者和纯画家的一个本质区别:你写的是概率和秩序,但选择依然由你做。

8.3 系列命名的意义感

作为一个从个人项目出发的创作者,我给每个批次的作品加上系列名。同一配方的二十张作品属于一个系列,系列之间有统一的色彩情绪或鱼形倾向。这个动作看起来很简单,但对作品的完整度和传播力影响巨大。当你拿着一个“系列”而不是“一堆图”去展示时,观者的态度会有明显不同。

这里我不再展开太多商业层面的东西,只分享一个原则:创作工具的意义不在完美复刻某一位艺术家的风格,而是让他的语言成为你表达的起点。米罗可以成为你切入生成艺术的那扇门,但这道门后面有无限条路。鱼的题材、鸟的题材、植物的题材、抽象的构图……每一类都可以嫁接到这套生成逻辑上。

9. 踩坑横评:从废物参数到高质感输出之间,我走过的弯路

最后分享几个我在调试过程中走过的弯路。这些坑每个都耗费了我不少时间,如果你正打算做类似项目,绝对能用上。

9.1 全随机参数并不等同于风格化

刚开始时,我把鱼的长宽比、颜色、符号数量、曲线控制点全部丢进随机函数里,结果十张图里能出两张好的已经是幸运。问题出在“风格”其实是由大量约束构成的:控制点角度固定、厚度比例固定范围、符号数量的配额限制、色相明度的取值范围。没有这些约束,生成结果只是一堆随机图形的堆积。风格的本质不是放得开,而是收得住。

9.2 “看起来简单”的贝塞尔路径,实际调试成本最高

鱼身轮廓的贝塞尔曲线调试成本极高,因为我需要同时保证:曲线闭合、没有尖角、没有自交。自交问题在鱼尾附近尤其常见——控制点让曲线折返形成一个闭环。我的调试方案是在开发模式里开启显示控制点,逐个观察曲线走向,并加上一组后处理校验:将曲线采样成精密的点,检查相邻点之间的方向角,如果转角超过规定值,就认为存在异常并重新生成。这个方法比较笨拙但可靠。

9.3 Perlin噪声的种子隔离

这是一个非常容易忽略的细节。如果鱼身厚度的噪声和鱼身位置随机没有隔离,最终输出往往会出现难以解释的联动效应,比如鱼更大时身体也更肿,或者颜色和位置异常相关。正确做法是把噪声和随机分成两条线:所有空间变化使用noise()并传递独立的种子偏移,所有离散选择使用random()。单独使用一套会造成混乱的隐形关联。

9.4 描边顺序真的要分层

前面说到黑线最后统一描,这是我在反复实验中提炼出来的关键顺序。如果你把每段路径画的时候就直接加描边,符号之间重叠的部分就乱作一团,尤其是鱼身和鱼尾、鱼身和符号之间。分层格式化输出也让后续的动画化(比如每只鱼身体律动、尾巴摆动)变得更容易,因为黑线轮廓和彩色填充可以分别更新。

9.5 屏幕色彩到打印色彩的偏差

把作品输出到纸上时,屏幕上的高饱和红蓝绿会变灰不少。如果你打算出实体装饰画,建议在生成时就预留一个“打印色板”配置——颜色饱和度稍高几个点,或者输出后统一做一轮轻微的色彩增强。我就因为没注意这个细节,第一批打印作品显得色彩寡淡,后面重新调整才找回预期中的跳跃感。

结束语:让生成艺术替你说话

对我来说,MiroFish这个项目最迷人的地方不在于能生成多少张鱼,而在于它验证了一件事:艺术风格并非不可分割的整体,而是可以被拆解、理解、程序化重组的一套语言系统。米罗的画看上去自由不羁,但他强大的风格正是建立在极其个人化、极其稳定的语法之上。而这份稳定,恰好是代码最擅长的事。

如果在阅读过程中,你脑海中已经浮现出“我这个选题是不是也能拆成几个可生成的元素”这种念头,那这篇内容就值了。欢迎拿这个项目做基础,去尝试你自己的风格迁移和符号生成实验。生成艺术是一扇门,推开之后会发现,风格本身也不过是另一种可以被编写的语言。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 6:58:18

数据资产化五大核心策略与商业价值解析

1. 数据资产化的商业价值觉醒三年前我接手一个零售企业的数据治理项目时&#xff0c;遇到一个典型场景&#xff1a;市场部抱怨"我们系统里存了五年客户数据却用不起来"&#xff0c;技术部反驳"每天光处理订单数据就耗尽资源"。这种数据"富矿"与&…

作者头像 李华
网站建设 2026/9/18 6:54:31

MCP Server进阶实战:错误处理、流式输出与TypeScript工程化部署

说实话&#xff0c;很多人把 MCP server 写到“能跑通”就停了。但你把 server 交给真实用户、接到 Cursor、接到自己的 Agent 框架里&#xff0c;问题就全来了&#xff1a;调用失败客户端只会看到一个干巴巴的 error&#xff1b;耗时工具跑几秒都没反馈&#xff0c;用户以为卡…

作者头像 李华
网站建设 2026/9/18 6:51:40

RQ60轧球机CAXA图纸解析与应用指南

1. 项目背景与核心价值在机械加工领域&#xff0c;轧球机作为一种专用设备&#xff0c;其设计图纸的完整性和精确度直接关系到生产效率和产品质量。RQ60轧球机作为中型轧球设备的典型代表&#xff0c;其图纸资料对于设备维护、技术改造以及逆向工程都具有重要参考价值。这套包含…

作者头像 李华
网站建设 2026/9/18 6:47:53

基于Java SSM与Vue.js的医院住院管理系统设计与实现

1. 项目背景与核心价值医院住院管理系统是医疗机构信息化建设的重要组成部分。传统手工管理模式存在效率低下、数据易丢失、统计困难等问题。基于Java SSM框架与Vue.js的前后端分离架构&#xff0c;能够有效提升住院管理流程的自动化程度。这个毕业设计项目实现了从患者入院登记…

作者头像 李华
网站建设 2026/9/18 6:47:44

医疗文本数据治理与NLP应用实践

1. 医疗文本数据的特殊性解析医疗领域的文本数据可能是所有行业中最为复杂的一类非结构化数据。从门诊病历、出院小结到影像报告、病理描述&#xff0c;这些文本既包含专业医学术语&#xff0c;又掺杂着医生的个人表达习惯。我在三甲医院信息化建设项目中&#xff0c;曾遇到过同…

作者头像 李华