如果只看最终效果,很难想到一只不断收缩、摆动,甚至有点像在水中游动的“水母”,其实只是由 4000 个散点组成的。
最近在 MoHub 上看到一个挺有意思的科学可视化案例:把一个 Python“数字生命”脚本移植到了 MWORKS Syslab,使用 Julia 和内置 TyPlot 绘图库,通过参数方程实时计算几千个散点的位置,最终生成一只动态的“赛博水母”。
我把 cyber_jellyfish.jl 实际跑了一遍,又导出了几个关键帧。相比最终动画,我更感兴趣的是:几行数学公式,到底是怎么慢慢长成一只水母的?
一、它其实不是“画”出来的
这个案例并没有水母的三维模型,也没有预先准备好的轮廓图片。
画面里的每一个亮点,都对应一个通过数学公式计算出来的二维坐标。
整个脚本可以简单理解成三步:
生成 4000 个粒子 → 根据参数方程计算坐标 → 随时间不断重新计算并绘制。
因此,这只水母并没有固定形状。
当时间参数 t 发生变化时,4000 个点的位置也随之变化,连续播放以后,就形成了类似伞盖收缩、触须摆动的动态效果。
这类图形比较有意思的地方就在这里:最终看到的是一个复杂形态,但背后真正控制它的只是少量数学规则。
二、4000个点是怎么变成“水母”的?
脚本首先给每个粒子生成一组基础参数:
x = mod.(i_data, 200)
y = i_data ./ 43.0
k = 5.0 .* cos.(x ./ 14.0) .* cos.(y ./ 30.0)
e = y ./ 8.0 .- 13.0
d = (k .^ 2 .+ e .^ 2) ./ 59.0 .+ 4.0
a = atan.(k, e)
这些量在整个动画过程中基本不变,可以理解成每个粒子的“初始属性”。
真正让水母动起来的是后面的时间参数 t。
每一帧都会重新计算类似下面的表达式:
q = 60 – 3*sin(a*e) + k*(3 + 4/d*sin(d^2 – 2*t))
c = d/2 + e/99 – t/18
然后再把 q 和 c 转换成最终的二维坐标:
X = q*sin(c) + 200
Y = (q + 9*d)*cos(c) + 200
这里的 q 可以大致理解成粒子离中心有多远,c 则影响粒子的分布方向。
真正产生动态效果的关键,是公式中的:
d² − 2t
以及:
−t/18
随着 t 不断增加,粒子的半径和角度都在缓慢变化。
于是原本静态的一团散点就出现了连续变形。
三、实际跑出来是什么效果?
在 Syslab 中运行 cyber_jellyfish.jl 后,会出现一个黑色背景的绘图窗口。
整个画面被分成两个区域。
左侧显示核心数学公式,右侧则是由 4000 个散点组成的水母。
图1:完整运行界面
比较有意思的是,左右两边实际上使用了两个独立坐标区。
左侧公式基本保持不动,右侧水母则随着每一帧不断刷新。这样既能看到动画,也可以同时观察它背后的数学表达式。
单独截取其中一帧,可以比较明显地看到水母的结构。
图2:水母形态
上方的散点聚集成类似伞盖的形状,下方则自然拉出大量细长分支。
这里并没有专门写“生成伞盖”或者“生成触须”的程序,最终结构实际上都是参数方程共同作用产生的。
换一个时间点,形状又会发生变化。
03:水母形态演化
可以看到伞盖的展开程度和下方点云分布已经明显不同。
如果把这些帧连续播放,变化会更加直观。
图4:赛博水母循环
这时候看起来就不再像一张数学图,而更像某种不断呼吸和游动的“数字生物”。
四、为什么散点看起来会有发光感?
如果 4000 个点全部使用同一种颜色,最终效果其实会比较平。
脚本还根据粒子的 depth 和 shimmer 等参数调整显示效果,使散点呈现出不同的亮度和色彩层次。
因此最终看到的并不是一团纯白色散点,而是带有青蓝、白色以及淡紫变化的点云。
深浅不同的粒子叠在一起之后,会产生一定的空间层次,看起来就有了类似半透明水母的“发光感”。
这也说明科学可视化里一个很容易被忽略的问题:
坐标决定形状,而颜色、大小和透明度决定我们最终如何感知这个形状。
同样一组数据,换一种视觉映射,最终效果可能完全不同。
五、4000个点×480帧,代码怎么避免重复计算?
动画一共需要计算 480 帧,每帧又有 4000 个粒子。
如果每次都重新创建数组、重复计算不随时间变化的变量,虽然数学逻辑没变,但会增加很多不必要的开销。
这个脚本做了两个比较典型的 Julia 优化。
第一,把 k、e、d、a 等与时间无关的参数提前算好,动画循环里只计算真正和 t 有关的部分。
第二,提前申请存放坐标的数组:
x_data = Vector{Float64}(undef, N)
y_data = Vector{Float64}(undef, N)
然后使用 compute_frame! 直接更新数组内容,而不是每一帧重新创建一组新数组。
这种带 ! 的函数命名在 Julia 中很常见,通常意味着函数会直接修改传入的数据。
对于单次计算来说,这种差异可能并不明显;但放到“4000 个点 × 数百帧”的循环里,减少重复内存分配就更有意义。
所以这个案例除了画图,本身也是一个比较直观的 Julia 数值循环练习。
六、它算不算真正的“数字生命”?
从视觉效果看,这只水母确实有一些“活物”的感觉。
但从模型本身来说,它仍然是一套艺术化的参数方程可视化。
水母的运动不是流体计算出来的,也没有建立柔性结构、浮力、阻力或者触须动力学模型。
所有形态变化,本质上都来自:
时间参数 t 改变 → 参数方程改变 → 4000 个散点重新排列。
因此不能拿它研究真实水母的游动机理。
但如果把它作为参数方程、Julia 图形编程和科学可视化的练习,反而很合适。
例如可以继续尝试修改粒子数量、公式参数、颜色映射和时间步长,观察最终形态如何变化;也可以保留这套绘制框架,换成其他参数方程,生成新的动态图形。
七、总结
这个案例最有意思的地方,不是“用 Syslab 画了一只水母”,而是它很直观地展示了:
数学公式可以直接决定一个图形如何生长、变形和运动。
4000 个点本身没有任何特殊之处,但经过参数方程组织以后,就形成了伞盖和触须;再加入时间变量,静态点云又变成了连续动画。
同时,脚本还涉及 TyPlot 散点绘制、双坐标区、逐帧刷新,以及 Julia 中的预计算、数组预分配和原地更新。
如果正在学习 Julia 科学计算,又觉得普通的函数曲线和散点图有点单调,这种案例倒是一个比较有趣的练习方向。
本文使用的赛博水母案例可在 MoHub 查看:
https://mohub.net/model/26011/summary

