说实话,第一次看到"BlooomMedusas"这个名字的时候,我愣了几秒。六个字母重复排列、水母的复数拼写,乍一看像个手滑打错的单词,又像某个乐队没想好的专辑名。但把"Blooom"拆开看,确实有意思——它比Bloom多了两个字母,像是在强调"持续绽放"的过程,而Medusas本身就是水母与神话美杜莎的双关。一个在缓慢呼吸、绽放,另一个则意味着幻觉般的危险美感。直觉告诉我,这是一个生成艺术项目的代号。
我花了一周多的时间,把"BlooomMedusas"从脑中的意象变成了一个可交互的实时数字水母群落:几团半透明的光影漂浮在深色背景里,伞盖一收一放像花朵呼吸,触手在流体中摇曳,颜色在色环上缓慢漂移。这篇文章就是把整个从灵感到落地的过程完整拆开,包括设计思路、核心算法、代码实现、踩坑记录,以及后续扩展的几种方向。想看代码的直接跳到第3节,想理解"为什么这样写"的建议从头顺着读。
1. 项目起因与整体设计思路
1.1 意象拆解:为什么是"水母"与"绽放"的组合
做生成艺术的人都会遇到一个共同的困境:空有技术没有意象。你熟练掌握了噪声、粒子系统、贝塞尔曲线,但打开编辑器时不知道该画什么。blocks、shapes、patterns,做多了都像数学公式的视觉化练习,没有温度。
"BlooomMedusas"恰好解决了这个困境。水母本身就是近乎完美的生成艺术载体:身体是半透明的N层叠加,触手是天然的自由曲线,游动轨迹是典型的流体运动,而发光和渐变又能把数字媒介的优势发挥到极致。更妙的是水母的伞盖收缩——那一下一下的脉冲,本身就是"bloom"的过程。我把这个脉冲看作一朵花在反复开放和闭合,而不是简单的水母游动。
这个意象组合带来的直接好处是:你不需要刻意加特效去"美化"它。水母的真实运动节奏已经足够优雅,你要做的是用代码模拟它的生物节律,然后把它放大到视觉层面。我甚至觉得这是生成艺术里最健康的一种工作方式——图像逻辑先于技术逻辑,先想明白"它应该是什么感觉",再决定"用什么技术去表达它"。
1.2 核心审美目标:优雅、错觉、呼吸感
在动手之前,我先给自己定了三条审美原则,全程没有改过:
第一,优雅优先于复杂。市面上很多水母生成作品喜欢堆粒子,一条水母配几千个粒子,场面确实壮观,但动态细节一塌糊涂。我的目标是让每条水母在低粒子数下依然耐看,靠运动本身产生观赏性,而不是靠数量硬撑。
第二,制造半透明错觉。水母最迷人的地方在于它"既在这里又不在这里"。真水母的身体是半透明的胶质,光线穿过时会有一种虚实的暧昧感。在数字环境里,我要用多层低透明度的叠加来模拟这种通透感,而不是简单地把opacity调低——前者是光的折射错觉,后者只是透明度参数。
第三,呼吸感是灵魂。我对自己的要求是:即使你把画面截图静止下来,也能看到"它在呼吸"的痕迹。水母的伞盖缩放、触手的摆动幅度、粒子扩散的半径,全部要关联到一个统一的节奏源上,形成一套协调的"呼吸"系统。观感上不能是一堆零件各自乱动。
1.3 技术路线选型:为什么用p5.js而不是Processing或Three.js
确定意象之后面临技术选型。我不想用Processing,虽然Processing在桌面端编程方面成熟稳定,但还要额外处理Java运行时和窗口管理的问题。没有用Three.js的原因更简单——2D构图更能接近"深海薄膜"的质感,而且我可不想为了体积感而烧掉过多精力在光照模型上。最终选择了p5.js,理由是三个:
一是上手快,JavaScript语法对前端背景的人没有台阶,而且图形API足够直接。二是Web端传播容易,浏览器打开就能运行,不需要装环境。三是p5.js的2D渲染在配合globalCompositeOperation和shadowBlur时有足够的视觉效果空间。
当然p5.js也有它的短板:2D性能上限比WebGL低很多,粒子数量超过一定阈值就会掉帧。我的应对策略很明确——不追求大粒子量,把性能预算花在"每一条水母的精细化"上。做这个项目的核心经验就是:如果你的视觉目标能用2D优雅地解决,就没必要为了"技术含量"硬上3D。
2. 核心技术选型与原理拆解
2.1 运动的核心:Perlin噪声驱动,而不是物理模拟
水母游动看起来应该"悠然自得",像在水中漂浮但又有自主方向感。很多初做水母的开发者第一反应是上物理引擎:浮力、阻力、重力、流体场。我可以明确地告诉你,这是杀鸡用牛刀。物理引擎做出来的运动偏"死重",水母游动需要的是生物性的柔和随机感,不是精确的牛顿力学。
我的方案是用Perlin噪声驱动速度和方向:每条水母维护两个噪声偏移量(x方向和y方向),以时间为增量采样噪声值,映射到速度向量。核心是这一小段逻辑:
const noiseScale = 0.0008; const t = frameCount * 0.02 + this.seed; this.vel.x = (noise(xBase, this.seed) - 0.5) * this.speed; this.vel.y = (noise(yBase, this.seed) - 0.5) * this.speed; this.pos.add(this.vel);为什么用噪声而不是随机?因为Perlin噪声的采样值在时间维度上是连续的——前几帧的数值和后几帧的数值是平滑过渡的,产生的运动轨迹天然带有柔和曲线的特征。而随机函数是白噪声,每一帧的值完全独立,生成的运动轨迹就是高频抖动,像触电一样,完全没有优雅可言。
这里有个关键细节:this.seed必须确保每条水母有唯一值。如果所有水母共享同一个噪声种子,它们会以完全相同的节奏游动,看上去像克隆军队。给我的每条水母在构造函数里分配一个0到1000的随机种子,噪声采样时加上种子的偏移量,就能让每一条水母的轨迹独立演化。
2.2 触手的画法:三次贝塞尔曲线的连续扰动
水母的触手是最难处理的部分:既要有曲线的柔美感,又不能像两根弹簧一样僵直地甩动。一开始我试过用三角函数叠加画波动曲线,效果很丑——所有触手的摆动频率相同,看起来机械;随后换成了贝塞尔曲线,但静态贝塞尔曲线没有动态。
最终方案是用三阶贝塞尔曲线,并让中间两个控制点随时间做平滑扰动。具体逻辑是:每条触手根部固定在伞盖边缘下方的锚点,末端自由浮动。控制点A和B的位置分别加一个基于时间的噪声偏移:
const baseA = createVector(this.pos.x + len * 0.3, this.pos.y + len * 0.4); const baseB = createVector(this.pos.x - len * 0.2, this.pos.y + len * 0.8); const noiseA = p5.Vector.fromAngle(noise(t, this.seed) * TWO_PI); const noiseB = p5.Vector.fromAngle(noise(t + 50, this.seed) * TWO_PI); ctrlA = baseA.add(noiseA.mult(swayAmplitude)); ctrlB = baseB.add(noiseB.mult(swayAmplitude));你可以把控制点想象成一条触手内部的"脊梁"——位置不断微调,曲线整体摆动感就有了。触手末端的细节也值得追求:我在末端加了一个渐细的处理,用strokeWeight逐步衰减宽度,再用低透明度描边模拟触手末端的半透明感。如果你不做这个细节,画出来的触手就是一条粗细均匀的线条,远远看去像海带,完全没有水母的轻盈。
2.3 Bloom节奏系统:一个函数统一所有动画节奏
这个项目最重要的突破是把"绽放感"变成一个统一的节奏系统——我起名叫breathCycle。它本质上是一个正弦波函数,周期控制着伞盖的张合、触手摆动的幅度、粒子扩散的半径,三者的节奏完全同步:
const breath = map(sin(radians(frameCount * breathRate)), -1, 1, 0.6, 1.2); this.umbrellaScale = breath; this.swayAmplitude = 20 * breath; this.particleSpread = 15 * breath;你可能会问,把所有动画都同步到同一个正弦波上不是会显得单调吗?动笔之前我也担心这个。实际效果恰恰相反——水母本身就是靠统一的脉冲节律来生存的生物,所有部位共享同一套节奏反而增强了"它是活物"的真实感。而且我并没有让每条水母的breathRate完全相同:每条水母有一个随机的相位偏移,导致它们的绽放节奏互相错开,即使共用节奏函数,整个群落的视觉层次依然丰富。
2.4 色彩设计逻辑:HSL漂移代替固定色板
水母的色彩是塑造气质的主要因素。我见过很多水母生成作品用固定的霓虹色板——紫配蓝配粉。不能说不好看,但看多了容易腻,而且这种高饱和配色容易让画面显得低龄化。我的方案是让每条水母的色相在时间轴上缓慢漂移,整体色域限定在深蓝、青、紫、粉这个区间。
核心是用HSL的色相角度做连续旋转:每条水母初始化一个基线色相,然后随时间做微小增量:
this.hue = (this.baseHue + frameCount * this.hueDrift) % 360;hueDrift控制色相漂移的速度,我把它设在每帧0.01到0.03之间——视觉上几乎是觉察不到的渐变,但当你在屏幕前坐五分钟,会感受到整群水母的色调在不知不觉地过渡。这种"没有察觉的变化"才是高级的,它能潜移默化地维持新鲜感,又不会像霓虹闪烁那样引起视觉疲劳。
我给每条水母的饱和度和明度也做了差异化:饱和度控制在50%~75%之间,明度控制在55%~75%之间。这个区间在高对比的深色背景上刚好能保持通透感,又不会刺眼。
3. 实操过程:从零构建BlooomMedusas
3.1 环境准备与项目结构规划
开发环境我选择了最稳妥的一套:VSCode + p5.js CDN + Live Server插件。不需要装任何额外的依赖。项目结构非常清爽:
blooom-medusas/ ├── index.html ├── style.css └── sketch.jsindex.html里只需要引入p5.js和两个文件,没有框架、没有构建工具。我实际使用中发现p5.js 1.9.0稳定,2.x版本目前API变动较大,如果只是复刻这个项目建议锁版本:
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script> <script src="sketch.js"></script>style.css我做了两件事:把body的margin清零、overflow设为hidden,确保画布全屏无滚动条;再把canvas设为display:block,避免页面出现意想不到的间距问题。不想在样式上花时间的可以直接把这三行贴进去:
html, body { margin: 0; padding: 0; overflow: hidden; } canvas { display: block; }sketch.js是整个项目的核心,我把所有逻辑都放在这一个文件里,一共约200行。对单一页面项目来说,不用拆分成多文件反而更利于阅读和调试。等代码量超过500行再考虑模块化也不迟。
3.2 搭建基础类结构:Medusa与Particle
我把整个项目抽象成两个角色:Medusa和Particle。Medusa负责水母本体的运动、伞盖绘制、触手生成;Particle负责围绕水母的微粒特效——我把它设计成围绕伞盖边缘漂浮的细小光点,随着"绽放"节奏向外扩散再被收回。
这种职责分离很重要,它保证每条水母的状态是自包含的:位置、种子、颜色、呼吸相位都在构造时初始化。渲染循环只需要遍历数组,调用update()和display()。如果你把粒子和水母的逻辑混在一起,后续想调整粒子密度或者加交互逻辑会变得非常痛苦。
Medusa类的初始化核心参数如下:
class Medusa { constructor(x, y) { this.pos = createVector(x, y); this.seed = random(1000); this.baseHue = random(220, 320); this.hueDrift = random(0.01, 0.03); this.speed = random(0.25, 0.5); this.size = random(40, 80); this.tentacles = 8; this.breathRate = random(0.8, 1.4); this.breathPhase = random(TWO_PI); } }各参数的语义很直观,需要解释的是baseHue区间。我的主视觉背景是深蓝紫色调(hue大约220~280),水母本身的色相也锁定在这个色域里,但往更高的色相值延伸——偏粉、偏紫红。这么设计是为了让前景和背景构成一个同色系的层次关系,而不是撞色的跳脱感。同色系配色在深色背景上天然带有高级感。
Particle类的设计更简单,核心是记录它所属的水母引用、距离伞盖中心的偏移角度和半径,以及当前扩散的程度。在Medusa.update()里调用particle.update(breath),让粒子半径随时间增长或收缩。
3.3 主程序:初始化、绘制循环与交互反馈
setup()和draw()在整个项目里是总控,负责创建画布、初始化水母群、绘制背景和处理鼠标交互。我直接给出关键代码,每一段后面附注释说明:
let medusas = []; let bgGradient; function setup() { createCanvas(windowWidth, windowHeight); colorMode(HSB, 360, 100, 100, 100); frameRate(60); for (let i = 0; i < 8; i++) { medusas.push(new Medusa(random(width), random(height))); } }colorMode(HSB, 360, 100, 100, 100)是我特别要提的一点。p5.js默认是RGB色彩模式,但水母的色彩设计全是在HSL维度上操作的,如果坚持用RGB,我要手动维护三组通道值的连续变化,写作复杂度成倍上升。切换HSB模式后,我只需要控制色相一个维度,饱和度和明度区间设好就不用管了。
draw()循环的骨架:
function draw() { drawBackground(); medusas.forEach(m => { m.update(); m.display(); }); handleMouseInteraction(); }背景我是用深色半透明矩形叠加实现的,这种"拖尾效果"是生成艺术里常见的技巧:
function drawBackground() { push(); noStroke(); fill(230, 30, 6, 12); rect(0, 0, width, height); pop(); }这里有个一般人容易忽略的细节:fill的alpha值(最后一个参数12)决定了画面残影的持久度。alpha越大,背景覆盖越彻底,画面越干净;alpha越小,前几帧的物体会有更长的拖尾。我试了很多个值,最终锁定在10到15之间——这个区间的残影刚好够制造"余晖"感,又不至于让画面糊成一片光污染。如果你想要干脆利落的运动画面,把这个数调到30以上。
3.4 关键代码:伞盖绘制与光晕叠加
伞盖是水母的视觉重心。我的实现是画三到四层半透明椭圆,每层的半径和透明度不同,叠加出通透的透镜感。核心代码如下:
displayUmbrella() { const layers = 3; for (let i = 0; i < layers; i++) { const layerScale = 1 - i * 0.2; const layerAlpha = 35 - i * 8; push(); translate(this.pos.x, this.pos.y); scale(this.umbrellaScale); fill(this.hue, this.sat, this.brightness, layerAlpha); ellipse(0, 0, this.size * layerScale, this.size * 0.7 * layerScale); pop(); } }为什么是三层而不是五层?因为每层的计算成本相同但视觉收益递减。三层以上,叠加后的颜色会过度偏向白色,失去水母应有的半透明胶质感。而椭圆的长宽比用1:0.7,这是我从大量水母照片里观察到的平均比例,视觉上最接近实际水母的伞部。
光晕(glow)效果是这个项目的点睛之笔。p5.js 2D模式里可以通过drawingContext.shadowBlur制造外发光:
drawingContext.shadowBlur = 25; drawingContext.shadowColor = `hsla(${this.hue}, 80%, 70%, 0.5)`;注意这个API不是p5.js封装好的,必须通过drawingContext直接访问底层canvas 2D上下文。使用它有个性能隐患:shadowBlur的模糊计算在每帧大量绘制时开销很大。我的经验是只在伞盖的最后一层时开启光晕,不要在细节部分滥用:
if (i === layers - 1) { drawingContext.shadowBlur = 25; drawingContext.shadowColor = `hsla(${this.hue}, 80%, 70%, 0.5)`; } else { drawingContext.shadowBlur = 0; }这样整条水母有一个柔和的光晕轮廓,但内部层不参与模糊计算,性能上省了一大截。实测下来,这样优化后帧率比全程开启shadowBlur提升了大约40%。
3.5 鼠标交互的设计:扰动而不介入
交互方面我做了一个"轻量扰动"的设计:鼠标位置对周围水母产生一个排斥力,但作用范围有限且会随时间衰减。这个设计让观众感觉自己是一个观察者,而不是掌控者——你伸手去碰水母,它们散开,然后又悠悠地游回来。
实现方式是在handleMouseInteraction()里遍历水母数组,当鼠标与水母的距离小于某个阈值时,把水母的位置往鼠标的反方向偏移:
function handleMouseInteraction() { if (!mouseIsPressed) return; medusas.forEach(m => { const d = dist(mouseX, mouseY, m.pos.x, m.pos.y); if (d < 120) { const force = (120 - d) / 120 * 0.8; const angle = atan2(m.pos.y - mouseY, m.pos.x - mouseX); m.pos.x += cos(angle) * force; m.pos.y += sin(angle) * force; } }); }交互设计的原则是"留白"。我不让鼠标变成持续的力场,只在按下鼠标的瞬间产生一次"推开水母"的脉冲。这样画面大部分时间保持自然的游动,只有在观众主动介入时才出现短暂的扰动,比持续的流体场交互要更有节奏感,也更适合沉浸式的观赏场景。
4. 常见问题与排查技巧实录
4.1 水母像死鱼:噪声参数调不对
第一次跑通代码时我差点放弃。水母是动了,但看起来像一具被绳子拉动的死体,僵硬地平行移动,完全没有游泳的起伏感。排查后发现问题出在噪声采样频率上:noiseScale的值太小,导致每帧采样到的噪声值几乎没有变化,速度向量被锁定在一个固定的方向上。
噪声采样的规律需要理解:noise(x)的x参数变化越快,输出值变化越大。想让水母游动有"忽左忽右"的悠然感,噪声参数需要在一个合适的范围内变化——既不能变化太慢(僵直平移),也不能变化太快(抖动抽搐)。我的经验值是每帧采样偏移量在0.001到0.01之间比较合适,根据期望的流畅度微调。
const t = frameCount * 0.002 + this.seed; this.vel.x = (noise(t, this.seed) - 0.5) * this.speed;还有另一个细节:速度向量直接控制位置的话,水母会偶尔飘出屏幕然后消失在边缘。我没有用边界检测去夹住它,而是让噪声场在位置接近边界时产生一个指向中心的方向偏置。这叫"软边界约束",比硬边界检测优雅得多,水母会在屏幕边缘缓慢掉头,而不是被物理反弹回来。
4.2 触手像面条一样乱甩
触手控制点加噪声扰动后,出现了一个新问题:某些触手摆动幅度大了会看起来像煮过的面条,软趴趴地到处乱甩。根源在于swayAmplitude这个参数的取值范围过大。控制点的扰动幅度在20像素左右时,触手动作已经足够明显,但我为了追求"飘逸感"把它调到了50,结果动作过头了。
这个问题的教训是:生成艺术中,动作幅度和质量不成正比。过大的扰动不仅会让曲线失去水母应有的从容感,还会在画面中引入大量高频运动,让观众的视线无法聚焦。最后我把swayAmplitude锚定到24,并且加入了一个基于触手索引的衰减——靠近伞盖的地方摆动小,末端摆动大。你可以在贝塞尔曲线中看到这种渐变的动态效果,比全程统一幅度要自然得多:
const swayFactor = map(i, 0, tentacles.length, 0.3, 1.0);4.3 粒子特效吃满性能:记下这三板斧
当初给每条水母配了120个粒子模拟水中浮游生物,结果MacBook Air的风扇瞬间起飞。性能优化的三板斧总结如下:
第一,粒子数量必须按视觉优先级分布。水母周围30~40个粒子已足够,120个粒子在视觉上的增量远小于性能的损耗。第二,减少粒子的独立计算:每个粒子如果单独访问噪声函数生成运动轨迹,开销巨大。我让粒子共享所属水母的breathCycle值,只维护各自的相位和半径,视觉上效果差别很小。第三,控制shadowBlur的使用频率。这是最大的性能杀手,我的做法是给粒子图层一次性开光晕,不要在逐个粒子的绘制里开关光晕。
优化完成后,帧率稳定在55 fps以上,CPU占用比之前低了将近一半。建议你做这个项目时,开场的粒子数量从30开始,视实际性能往上加,不要一上来就追求视觉饱和。
4.4 画面发灰发脏:透明度叠加超出预期
半透明叠加的效果本来是想模拟光线的穿透,但实际运行时发现画面灰蒙蒙的,像隔着一层脏玻璃。排查下来,根源在于HSB模式下透明度的叠加规则:多个半透明层叠过多,饱和度会被稀释,颜色逐渐向白色靠拢,于是画面发灰。
解决办法有几个方向:把伞盖的层数从5层减到3层,减少颜色被稀释的次数;把每层的fillalpha值调低,让叠加后整体颜色更通透;把初始明度调低一些,给透明的叠加留出空间。颜色设计上也要注意:HSB模式下明度设置太高,叠加更多层后容易发白。把明度控制在55~70之间,饱和度控制在60~80之间,整体画面就会保持浓郁但通透的质感。
4.5 刷新率不稳定导致动画忽快忽慢
不同设备的刷新率不同,同一套帧速率逻辑在144Hz和60Hz屏幕上呈现的速度差可以达到两倍以上。如果水母的游动速度用frameCount作为时间基准,在刷新率不同的设备上会出现明显差异。
标准解法是用deltaTime做时间归一化。p5.js内置的deltaTime变量是上一帧到当前帧的毫秒数。我可以把它除以目标帧率(60)转换成一个"时间倍率",所有运动参数乘上这个倍率:
const dt = deltaTime / 16.67; // 所有与时间相关的运动参数乘以dt this.vel.x *= dt;用deltaTime有另一个好处:页面切到后台再切回来时,不会因为帧率突降导致水母突然瞬移一大段距离。我试过用frameCount做时间基准时,页面从后台恢复后水母会瞬间跳到几十帧之后的位置,观感极其突兀。换成deltaTime之后这个问题就消失了。
4.6 问题速查表
| 表现 | 触发原因 | 快速解决办法 |
|---|---|---|
| 水母僵直平移 | 噪声采样频率太低 | 将frameCount * 0.002调大到0.005 |
| 触手乱甩 | 控制点扰动幅度过大 | swayAmplitude从50下调到24左右 |
| 画面灰蒙蒙 | 半透明层数过多 | 减少伞盖层数,降低明度 |
| 帧率低 | shadowBlur滥用 | 只给最重要的图层开光晕 |
| 水母跑出画面 | 缺乏边界约束 | 加软边界,靠近边缘时给中心偏置力 |
5. 扩展方向与个人经验总结
5.1 进阶方向一:从2D到3D的视觉升级
做完2D版本后,我一直觉得水母是特别适合3D化的题材——它那半透明的身体在三维空间中的体积感、触手的空间飘动、光线的折射效果,都有巨大的表现潜力。如果你想往这个方向走,我建议用Three.js来重做,核心思路是:用SphereGeometry做伞盖,片元着色器里叠加噪声扰动顶点位置模拟触手的波形运动;触手可以用CatmullRomCurve3生成三维贝塞尔曲线,沿曲线放置一系列透明小球制造体积感。材质方面可以试试MeshPhysicalMaterial的透光属性(transmission),官方示例里已经有很多半透明材质的现成写法。
5.2 进阶方向二:音频驱动与互动演出
生成艺术发展到最后,往往会和声音产生关系。水母的呼吸节奏如果绑定到现场音乐的低频信号,视觉和听觉会形成强烈的共振。技术上并不复杂:用p5.AudioIn获取麦克风输入,通过getLevel()提取振幅,映射到breathRate和umbrellaScale上。
我试过用低频正弦波驱动水母呼吸,效果非常带感——低音炮每震动一下,整片水母群体会同步"绽放"一次,那种视听同步的震撼是同屏粒子数翻倍也换不来的。如果你有现场演出的需求,这个方向值得投入。
5.3 进阶方向三:输出序列帧做衍生品
实时渲染的项目做完了,如果只留在网页上很可惜。我尝试过把渲染结果导出为序列帧,再用After Effects合成一段短视频,效果相当惊艳。具体做法是在draw()里加一行saveCanvas('frame_' + nf(frameCount, 4), 'png'),连续记录30秒左右渲染输出为一组PNG,后期导入AE处理。
这样做还有一个好处:你可以把某个特别好看的帧单独抽出来,用高分辨率重渲染,打印成装饰画或者做成明信片。数字艺术并不是只能看屏幕的,落地的实体衍生品能带来完全不同的满足感。坐标系渲染时我建议把pixelDensity(2)加到setup()开头,输出分辨率翻倍,印刷和后期处理的画质就有保障了。
5.4 个人实际操作中的体会
做完BlooomMedusas这个项目,我最大的体会是:生成艺术品的上限不是技术而是审美决策。Perlin噪声、贝塞尔曲线、HSB色彩模型,这些技术本身的文档数不胜数,任何人花一个下午就能跑通;但要让水母有"呼吸感"、触手有"柔韧性"、色彩有"高级感",这些全是需要反复调整参数、不断靠直觉打磨的主观选择。
第二,不要太早固定技术方案。我一开始想用物理引擎做流体模拟,如果当时没忍住用了,整个项目会走向完全不同的方向。生成艺术核心在"生成"而不在"艺术"——代码给你提供的是起点和边界,最终的画面是你在参数空间里不断探索的结果。给自己留出调整的余地,审美判断的空间比技术基建的空间更重要。
第三,留白是一种能力。不管做什么生成项目,克制比堆叠更难。BlooomMedusas从最初每条水母120个粒子、15条触手,最后精简到40个粒子、8条触手,画面反而更舒服了。少即是多,在生成艺术里永远是真理。