本节摘要:程序化建模是声明式范式——描述"结果满足什么规则"而非"每一步怎么操作"。Houdini 用节点依赖图实现它:每个节点是纯函数,改参数只触发受影响路径的重算。本节用一个"返工实验"对比命令式与声明式的差异,讲清非破坏性、可迭代性与版本化能力。
沿用 1.1 的山体网络。现在假设美术总监说:"山脚太平了,压一点下去。"
命令式做法(传统雕刻软件的思路):进入点级别,框选山脚区域,手动下拉几百个顶点,缝合、修法线。总监第二天说"还是放回去吧"——你重做一遍,这次往上调。
声明式做法(你现在的网络):在 Mountain 后插一个节点,按高度做映射——高于阈值保持,低于阈值往压。总监反悔?删掉这个节点。前后十秒。
第二种做法里,你没有"操作山",你修改了山的定义。这就是声明式建模:网络是一份可执行的规格说明书,几何体只是它某一次求值的结果。
命令式:操作序列 → 最终状态(历史丢失,返工=重做操作) 声明式:规则网络 → 每次求值出结果(历史即网络,返工=改规则)
为什么改 Mountain 的参数,Scatter 会自动重算,而 Grid 不动?Houdini 内部维护一张依赖图:每个节点记录自己吃谁的数据、喂谁。参数一改,该节点被标"脏",脏标记沿下游传播,被标脏的节点在下次显示时重算,上游没脏就不碰。
这个机制解释了三个日常现象:
💡 关键直觉:节点不是"操作的记录",是"函数的组装"。你会用函数式的眼光看它:同样的输入+参数,永远得到同样的输出。
| 能力 | 在 Houdini 里的样子 | 没有 it 时的代价 |
|---|---|---|
| 非破坏性迭代 | 任何阶段都能改参数回上游 | 返工即重做 |
| 规模化 | 种子/参数扫一遍,出 100 座山 | 手做 100 次 |
| 协作与交接 | 网络即文档,别人能读懂你的意图 | 靠口头与截图传递 |
第三点常被低估。一个结构清晰的网络,接手的人从左到右读一遍就知道你做了什么、在哪改。这也是第 7 章 HDA 能成立的前提——封装的前提是逻辑可见。
声明式不是免费午餐。两条代价要心里有数:
⚠️ 常见坑:为了一个小改动把参数暴露得到处都是,网络变成"参数迷宫"。规则的分层要在设计时想好,而不是靠事后加旋钮弥补。
下一节把镜头拉远:这套节点机制在整个软件里是怎么按车间分层的。
范式三要素(规则、参数、随机性)的融汇可以用一个"碎裂"小实验检验:给定一块面板,让裂纹随种子与密度参数变化。先用 Voronoi Fracture 得到碎块,关键在后续的参数化边缘处理:
// Attribute Wrangle (Run Over: Primitives) —— 碎块按"距中心衰减"缩放 vector c = getpointbbox_center(0); // 整体包围盒中心 vector pc = prim(0, "P", @primnum); // 用 centroid 更稳: 逼近即可 float d = distance(c, pc); float shrink = fit(d, ch("inner_r"), ch("outer_r"), 1.0, ch("gap")); f@shrink = shrink; // 传给 Transform Pieces 的 scale
用 Transform Pieces 按 @shrink 缩放每块,gap 参数立刻控制裂缝宽度,seed 改变裂纹形态。注意范式的分工:Voronoi 是规则、gap/inner_r/outer_r 是参数、seed 是随机性——三者任何一方缺失,资产都无法进入迭代流程。
范式对照实验 破坏式(传统): 直接改网格顶点 -> 上游一变, 修改全部蒸发 程序式: 修改记录为序列节点 -> 上游变化时, 序列重新作用于新数据 验证法: 在裂纹实验上游换一个不同形状的面板(圆盘->六边形), 全链不重搭, 结果自动适配 —— 这就是"声明优于命令"的工程含义 边界: 非破坏性有代价(内存/依赖追踪), 何时"冻结"(Bake)成 破坏式快照, 是第八章性能工程要回收的问题
关于范式的常见误解也要趁早澄清:程序化不是"自动生成",参数不是"随机按钮"。一个健康的程序化资产里,随机性占比通常不超过两成,其余八成是确定性的规则与映射——随机只负责打破完美对称、制造个体差异,从不负责决定结构本身。判断一个资产范式是否健康的简易标准是"导演测试":需求变更(山往西、路加宽、房子少三层)到来时,你是改参数、改规则,还是重搭网络?前两者占比越高,范式越成熟。这个测试把"程序化"从工具问题提升为设计问题:你在设计的不是某个结果,而是一族结果的生成空间,参数轴的选取本身就是创作。