本节摘要:程序化城市生成 = 地块划分 + 规则填充 + 变体控制。核心方法论与地形一脉相承:掩码定"哪里"、规则定"什么"、散布定"怎么摆"。本节从最小实验(一块地切分并填充建筑)走到街网生长策略,并讨论风格控制与真实城市数据两条路线的取舍。
// 在循环体内对地块做一次随机二分(示意逻辑) // 面积小于阈值则跳过,否则切一刀并把两半回填循环 float area = primintrinsic(0, 'measuredarea', @primnum); if (area > chf('min_lot')) { int dir = rand(@primnum + detail(-1, 'iteration')) > 0.5; // ...按方向调用 clip 面或返回切线(实现细节随版本而异) }
// 离市中心越近越高 + 随机起伏:城市天际线的最小模型 float d = length(v@P - chv('center')); @height = fit(d, 20, 400, 80, 8) * (0.6 + 0.8 * rand(@primnum));
十几栋楼出来后你会立刻看到"市中心高、周边矮"的天际线雏形——天际线不是逐栋设计的,是距离场的输出。
地块切分之前得先有街。常见两派:
| 策略 | 做法 | 视觉结果 | 适用 |
|---|---|---|---|
| 网格法 | 规则格网+扰动、斜穿主干道 | 曼哈顿式秩序 | 新大陆城市 |
| 生长法 | 从种子点沿吸引子延伸、遇障碍绕行 | 有机蔓延 | 老欧洲城市、村落 |
生长法与 3.4 的 L-System 一脉相承——道路网就是一棵改写规则驱动的树:每步延伸时按方向偏好(沿等高线、朝水源、避陡坡)分叉或转向。4.1 的 flow、slope 掩码在这里直接当成本函数用:地貌数据第二次上岗。
变体控制的三档投入:
⚠️ 常见坑:追求"每栋楼都程序化生成"的全自动执念。城市可信度来自宏观秩序(街区、天际线、路网逻辑),微观多样性可以靠有限的建筑变体+散布解决。规则层级越高的部分越值得程序化。
💡 关键直觉:城市不是"很多建筑",是"被交通与经济规律切分过的空间"。先模拟切分,再填充建筑——顺序反了就是楼堆。
环境长好了。第 5 章让这座山谷"动起来":约束与解算器。
程序化城市的骨架是地块(Parcel)划分,标准流水线三步走:主干道(样条+噪声扰动)切出街区,偏移(Shrink)得到建筑红线,OBB/递归切分填满地块:
[地块流水线] 1. 道网: 曲线网络(自由绘制 or L-system 街区母版) ->Curvenet 2. 街区: Curvenet 提取面 -> Fuse -> Facet -> 面积过滤(去碎片) 3. 地块: Sort by area -> For-Each: 递归对半切(Orientation 沿长轴) 直到 fit(面积, lot_min, lot_max) 命中目标区间 4. 退线: PolyExpand2D(负值) 得建筑足迹, 留出人行道
这套流水线的价值在参数化城市形态:lot_min/lot_max 调大立即从曼哈顿变郊区独栋,路网密度参数一扫,城市的"性格"连续可调。
// 楼板生成后: 立面按朝向+楼层双循环装配(VEX 顶层逻辑示意) int floor = i@floor_idx; float w = f@tile_width; int modules = max(1, int(w / ch("unit_w"))); i@style = i@floor_idx % 2; // 奇偶层交替 // For-Each module 内按 style 分派: 窗/阳台/广告位 HDA 实例 s@module_tag = choose(["window","balcony","blank"], int(rand(floor*31 + @elemnum)*3));
城市生成的黄金分层是"路网→地块→体量→立面→细节"五层各自参数化、逐层冻结缓存,任何一层的需求变更都不会点燃整条管线——这个分层法将在第六章 PDG 里升级为正式的工作单元划分。
关于城市生成还有一个容易被低估的维度:地标与记忆点。纯参数化城市最大的风险是"处处都真实、处处都无聊",因为真实城市的魅力来自历史的偶然——斜插的旧教堂、突然弯掉的主街。工程做法是"程序化打八成,手工占两成":在参数系统里预留手工地标位(指定地块直接引用美术制作的 hero 资产,跳过装配管线),并把主街路径留给构图师的曲线输入。程序化提供广度,手工提供焦点,两成的手工通过引用接口进入八成的系统而非相反——这是所有大规模生成内容管线的黄金配比。