5.2 基于解算器的程序化演进


5.2 基于解算器的程序化演进

本节摘要:Solver 节点是一个反馈回路:第 N 帧的输出作为第 N+1 帧的输入,让你用 VEX 写任意自定义"物理"。凡是"每帧在上一帧基础上累积/扩散/生长"的效果——藤蔓蔓延、锈迹扩展、湿痕晕开、自定义侵蚀——都是它的地盘。本节写两个迷你解算器:污渍扩散与藤蔓生长。

先看懂 Solver 的结构

放一个 Solver(SOP 层),双击进去:有 prev_input 与输出各一。时间轴播放时,上一帧穿过 Solver 内部的网络,结果成为本帧的 prev_input——反馈回路是它与其他一切节点的本质区别。这也解释了 2.1 留下的问题:为什么 Wrangle 不能做累积——它没有上一帧的记忆,Solver 有。

帧 N 几何 → [Solver 内部网络(可读 prev_input)] → 帧 N+1 几何 → …… ↑______________________________________| 反馈:本帧输出=下帧输入

实验一:污渍扩散(最小状态机)

在一块平面上随机画几个种子点(Cd 设红),Solver 里放一个 Wrangle:

// 每帧:向随机邻居传染,概率随"菌龄"衰减 @age += 1; float infect = 0.4 * exp(-@age * 0.02); if (rand(@ptnum + $F) < infect) { int nb[] = neighbours(0, @ptnum); foreach (int npt; nb) { if (vector(point(0, 'Cd', npt)) == {1,0,0}) { @Cd = {1, 0.6, 0}; // 被感染:变污色 } } }

播放——污渍从种子处不规则地漫开,边缘因随机而毛糙。换个角度想:这就是细胞自动机。Solver 是把任意"局部规则+时间"变成 Houdini 节点的通用容器

实验二:藤蔓生长(受限随机游走)

散布几个起点,每帧长出一小步、偏向朝上且贴着表面:

// 在 Solver 内对"生长前沿"点每帧前进一步 vector step = sample_direction_uniform(rand(@ptnum * 999 + $F)); step = normalize(step + {0, 0.6, 0}); // 向上偏好 vector target = @P + step * chf('len'); vector closest, uv; int prim = xyzdist(1, target, uv, closest); // 输入1=表面 if (prim >= 0) { @P = primuv(1, uv, prim); // 拉回表面 addpoint(0, @P); // 留下轨迹 }

几百帧后藤蔓布满石面。把 3.4 的 L-System 思想对照看:L-System 是全局改写,Solver 是局部行为模拟——前者像文法,后者像生命。两者常组合:Solver 定大势,文法补细节。

图 5.2-1 两类演化的心智对比

性能与工程纪律

Solver 的账单和 DOP 一样按帧累积,三条纪律:

  • 数据量进回路前先裁:只把"演化需要的点/属性"接进 Solver,装饰性几何在外面合并回来;
  • 早停条件:用 detail 属性记录"活跃点数",为零时冻结回路(否则几百帧空转);
  • 同 DOP:认结果即缓存。反馈系统对初始条件敏感,重跑不保证重现。

⚠️ 常见坑:在 Solver 里引用外部输入(非 prev_input)却忘了它每帧都会重新求值——外部动,回路里历史就断了。外部数据要么烘焙成常数再进回路,要么明确接受其每帧刷新。

💡 关键直觉:DOP 是"别人写好的物理",Solver 是"你自己的物理"。判断标准只有一条——你想要的规律在牛顿力学里吗?在就用 DOP,不在就自己写。

本节要点回顾

  • 反馈回路是 Solver 的本质:本帧输出即下帧输入;
  • 局部规则+时间=万物演化:扩散、生长、侵蚀、传染皆可写;
  • 与 L-System 互补:文法定全局形态,Solver 演局部过程;
  • 回路内极简、认结果即缓存、写早停是三条纪律;
  • 外部引用进回路前先烘焙。

资产会生成也会演化了。但每次只算一个变体——第 6 章把整条产线规模化。

廕伸:Solver 的三帧缓存纪律

Solver SOP 把上一帧结果作为输入 1 回馈自身,这个结构极强也极危险——缓存污染会以"从某帧起整个几何系统异变"的形式出现。三条纪律防患:

[Solver 纪律] 1. 演化状态显式命名: @age/@state 而非依赖 @Cd 之类多义属性 2. 输出前 Clean: 删除 @tmp_* 临时属性, 防止它们参与下帧逻辑 3. 锁帧审计: 需要回改上游时, 记录当前帧号并重解至该帧 而非在 Solver 内部"打补丁"——补丁会让缓存与参数失同步

廕伸:确定性检验与种子迁移

// 演化型网络的回归测试: 固定帧抽样指纹 // (放到输出后, Python 参数脚本或 Python SOP) import hou n = hou.pwd(); fp = n.geometry() sig = sum(p.number() for p in fp.points()) ^ len(fp.points()) n.setUserData("sig_f%d" % hou.frame(), str(sig)) # 对比种子A/B 下的指纹序列: 差异应只出现在设计预期的区段

演化系统的工程化从"能复现"开始:同样参数同样种子必须逐帧同输出。指纹抽样法轻量且能定位"第一次分岔的帧",配合第八章的性能测试一起构成程序化系统的回归防线——解算器带来的时间维度,让"测试"从静态对比升级为轨迹对比。

关于 Solver 还有一个值得体会的设计哲学:它把 Houdini 从"状态机"变成了"时间机"。普通节点的 cook 是无记忆的纯函数(同输入同输出),Solver 引入反馈后网络有了历史——这意味着程序化的可重复性多了一个时间维度上的责任,也是本章强调确定性检验的原因。顺带一提,生长动画、风化模拟、群体行为这些"没有物理正确性包袱"的演化,用 Solver 手写往往比套用动力学解算器更轻更可控;判断标准就一条:你要的是"物理对"还是"演化对",前者交给解算器,后者自己写规则。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U