AI 城市生成器:先路网,再地块,最后建筑
生成的城市在航拍图里成立,走到街上就散了。路网等级、街区尺寸、临街面宽与路口——让城市读起来像城市的那些数字。
城市生成器都是在 200 米高空被评判的,而那恰好是所有城市生成器看起来都很称职的唯一高度。把相机降到 1.7 米,站到一个街角:人行道有没有绕过转角,相交的两条路对路缘高度有没有共识,街角那栋建筑知不知道自己在街角。城市能不能读成城市,取决于这个视角——而这件事在任何人开始建模房子之前,早就定下来了。
城市首先是一张路网
这个顺序是一条依赖链,不是风格偏好。路网把地面切成街区,街区细分成地块,而地块的临街面宽、进深和退线决定了那里能站什么建筑。改路网,下游全部跟着变;改一栋建筑,上游毫不在意。
把顺序颠倒的生成器——先堆一批建筑,再从缝隙里穿几条路——产出的是「截图城市」。从上往下看体块很像那么回事,到了街道高度什么都不朝向什么,因为放置的那一刻根本没有可朝向的东西。Parish 与 Müller 发表于 SIGGRAPH 2001 的论文,此后大多数程序化城市工具的祖先,就是先生成街道网络,再由街道把土地划成地块,最后才在地块上放建筑。
道路有三级,不是一级
真实路网是有等级的,而这个等级在人眼高度上就能读出来,哪怕你不知道它的名字。主干道承担穿越交通、几乎不直接连接地块;集散道路把片区内的车流汇集起来送上主干道;地方道路的存在意义就是抵达门口。在美国路网中,主干道加集散道路合计承担约 70% 至 80% 的车辆行驶里程,而地方道路占据了里程总长的大部分:少量宽阔、繁忙、连续的街道,加上大量狭窄、短促、安静的街道。
只生成一个等级道路的生成器,做出来的是棋盘:街道一样宽,路口一样重要,左转右转没有任何理由。给三个等级不同的车行道宽度、不同的人行道、不同的路灯间距和不同的沿街建筑,玩家就会开始靠手感导航,而不是靠小地图。
可以量出来的宽度
站得住的数字,单位为米,取自 NACTO 的《Urban Street Design Guide》:
- 车道:城市街道 3.0 米,公交与货运通道 3.3 米。到 3.6 米,街道就开始读成公路。
- 两侧带停车位的双向地方道路:路缘到路缘 7.3–8.5 米。典型的社区街道是 15 米道路红线里的 9 米车行道。
- 人行道通行带:住宅区 1.5–2.1 米,市中心 2.4–3.7 米——而这只是真正用来走路的部分。真实人行道还有靠路缘的设施带(路灯、行道树)和贴着建筑的临街带(门、台阶)。把它建成一条平板,两者都会丢掉,所以生成的街道看起来像被打扫得一干二净。
- 路缘石高差:0.10–0.15 米。最先消失的东西,而路缘与车行道齐平,整条街就变成了一张画上去的平面。
真正遵守这个顺序的工具并不多。基于节点的程序化系统——Houdini 的节点图、从那篇 2001 年论文一脉相承的规则式城市工具——确实从街道图开始,代价是规则要你自己写。单次生成的模型根本没有路网,它们输出里的道路是纹理而不是拓扑。「资源库加摆放」类工具通常从一张网格开始,也就是只有一个等级的路网。由智能体驱动真实编辑器是第四种情况,它从路网开始的理由很朴素:画一条主干道是一个操作,摆四百栋建筑是四百个操作。Cuberta 正是这样工作的——编辑器通过 Model Context Protocol 开放场景层级的操作,于是智能体先画主干道,再画集散与地方道路,然后切分街区,并在每一步之间把场景读回来。这是否胜过手写的规则图,取决于你愿意自己写多少,而这本身就是另一场讨论。
街区第二,地块第三
街区就是路网围合出来的东西;里面发生什么是另一个决定,也是大多数生成器整个跳过的一步。6 米面宽加零退线,得到的是联排;18 米面宽加 7 米退线,得到的是郊区。同一套建筑库两种都做得出来。
| 样本 | 街区 | 读起来是 |
|---|---|---|
| 俄勒冈州波特兰 | 61 × 61 米 | 路口不断,步行体验极好,道路占地昂贵 |
| 巴塞罗那 Eixample | 113 × 113 米,20 米切角 | 致密而规整;每个转角都是一个小广场 |
| 曼哈顿 | 80 × 274 米 | 横街短、大道长;方向本身带有含义 |
| 战后郊区 | 150–250 米,曲线路,尽端路 | 路口很少,步行时几乎无法辨认方位 |
该拿走的是比例,不是可以照抄的数字。波特兰每 61 米一个路口,按 1.4 米/秒的步速,每 45 秒遇到一个;曼哈顿一个方向每 80 米、另一个方向每 274 米,所以两条轴线感觉像两座城市。对游戏而言,可用区间是每边 60–120 米:小于 60 米,地面面积大半都铺成了路;大于 120 米,玩家在两次决策之间走得太久,而经过的正是那些长长的、空白的街区立面——生成城市最露馅的地方。
退线、面宽与层高
退线是把商业街和居住街区分开的那一个数字:连续商业界面没有退线,联排住宅有 2–4 米前院,郊区独栋有 6–8 米加一条车道。站得住的面宽是:联排住宅 5–7 米(经典的布鲁克林褐石屋坐落在 6 × 30 米的地块上),城市公寓楼 15–25 米,独栋住宅地块 12–20 米。转角地块几乎总是比邻居更宽:转角更值钱,店铺归它,而 Eixample 干脆把这个差别写进了规划——20 米的切角。
竖向上,有一条约定能让屋顶线保持诚实:住宅层高 3.0–3.3 米,办公 3.6–4.2 米,商业首层 4.5 米起,所以四层住宅到檐口大约 12–13 米。生成的建筑如果在这件事上没有共识,相邻立面就会对楼板标高各执一词,街道读起来就成了拼贴。
路口是生成器崩塌的地方
路口是唯一必须把两条道路的几何合并成一个连续表面的位置,而两条道路各自的定义里都没写该怎么合。
- 面重叠。两个道路网格交叉产生共面多边形,路口正中出现闪烁色斑,第一帧就看得见。
- 人行道互相穿过。路缘线必须转过弯去;两条沿自身中心线挤出的带子只会交叉。
- 转角什么都没有。标准路缘半径为 3–4.5 米,城市住宅街道降到 1.5–3 米,有些城市甚至做到一米以内。全场用同一个半径,要么在联排里做出赛道弯角,要么在主干道上做出刀锋。
- 宽度不匹配。12 米主干道遇上 7 米地方道路需要一段过渡;没有过渡,地方道路的路缘线就悬空停在还差 2.5 米的位置。
- 标线角度不对。在夹角 70 度的路口按 90 度盖上斑马线,整个路口立刻读成贴图作业。
- 转角建筑不像转角建筑。真实城市的转角地块是商店、酒吧、更大的楼、切角。若只是把街段中间那栋楼旋转 90 度,路口就没有地标,片区也就没有导航。
检验只需要一次相机动作:视点放到 1.7 米,站在路口正中,转满 360 度。挺过去了,其余都挺得过去;挺不过去,再好的天际线也救不回来,因为玩家整关都待在这个高度。
重复是最大的破绽
生成城市暴露自己靠的是重复,而且比几何暴露得早得多。沿着 100 米长的街区立面,玩家一眼能看到十到十五个建筑正面;其中出现两个一模一样的,幻觉就结束了。市面上提供的多样性大多是假的:
- 立面套件。六到十块墙板重新组合:行业标准做法,确实有效——直到同一根窗棂连着出现在五栋楼上。
- 颜色抖动。在 albedo 上做色相偏移。在航拍截图里很有说服力,在 10 米处毫无用处,因为底下的几何一点没动。
- 旋转与镜像。镜像后的房子还是同一栋房子;眼睛读的是轮廓和比例,不是手性。
- 随机缩放。四者中最糟的一种,因为它破坏的恰恰是原本正确的东西:门不再是 2.0–2.1 米,楼板线不再与隔壁对齐。
挣来的多样性长自规划本身。地块宽度不一样,是因为细分本身就不一样。同一条街上有不同年代的建筑,层高不同,还有一栋塞进拆除空档里的窄插建。屋顶线随地面台阶而台阶,沿联排哪怕只有 1:40 的坡度也会把檐口逼成一段阶梯。再加上几栋完全不属于这一类的建筑——变电房、教堂、立体停车楼。三百栋规模的片区里放五个这种,对可信度的贡献超过再加二十种立面变体。
分开真假的规则是:你解释得出来的多样性才读得出来。如果你说不清这栋楼和隔壁差在哪里,玩家也说不清,差异只会变成噪声。规则式生成器只能变化它的规则集暴露出来的东西——写得好的暴露很多,赶工的只暴露颜色。而对编辑器里的智能体,你可以直接说:在西北角那个街区放一座小教堂,让联排顺着地势向河边逐级下降。这句话会变成五个具体的物件,而不是又一个参数。
一个片区到底需要多少栋建筑
建筑数量不是你选的。你选的是街区尺寸和临街面宽,数量是算出来的。80 × 110 米的街区周长 380 米,用面宽去除就得到临街建筑的数量:6 米联排 63 栋,12 米联排别墅 31 栋,22 米公寓 17 栋,或者围合内院的一整栋周边式建筑 1 栋。
一个可步行的片区大约 400 × 400 米,按 1.4 米/秒走完不到五分钟。街道间距 100 米,就是十六个街区:各 17 栋是 270 栋,各 31 栋是 500 栋,各 63 栋是一千栋,那已经是另一个项目了。两百到四百栋临街建筑是实用区间:足够填满每条街的每一个视野,又少到你能亲自把它们全看一遍。
独立网格是另一个数字,而且小得多。如果一个街道视野里有十二个正面,那么在重复变得不可见之前,你大约需要十五到二十种不同的建筑体量,外加两条摆放规则:相同的建筑不相邻,也不同时出现在一个视野里。二十种体量配三种屋顶变体和一套立面套件,能撑起三百次摆放;四种体量加颜色抖动什么都撑不起来,摆多少次都一样。
怎么把一座城市描述给智能体
不管由什么来生成,描述都应该读起来像一份规划任务书,而不是情绪板;顺序和内容同样重要:路网、街区、地块、建筑、街具、光照。按这个顺序写,每一句都在约束下一句——这正是几何本来就必须遵守的那条依赖链。
市中心边缘片区,400 × 400 米,地形平坦。一条主干道南北向从中间穿过:每向两条 3.3 米车道,2 米绿化中央分隔带,两侧 3.5 米人行道。两条集散道路东西向与之相交,间距 130 米,路缘到路缘 8 米。其余由地方道路填满,间距约 90 米,路缘到路缘 7 米,单侧停车。街区 80 × 110 米。主干道沿线:连续的五到七层砖石建筑,零退线,首层 4.5 米带橱窗,以上各层 3.1 米,面宽 14 至 26 米。地方道路沿线:三层砖砌联排,面宽 6 米,3 米前院配矮栏杆。转角地块用更宽的建筑,首层做切角。与主干道相交处路缘半径 4 米,其余 2 米。主干道路灯每 30 米一盏。其中一个街区是公园,一个是地面停车场,西北角街区有一座小教堂。上午晚些时候的阳光,从东南方来。
每一句都能在视口里核对,这才是关键性质:结果不对时,你可以指着被忽略的那一句说「集散道路的间距是 90 米,不是 130 米」,而不是把整段描述重写一遍再赌一次运气。用结构代替形容词,这条原则适用于任何由文本生成的场景,只是城市对含糊的惩罚更重,因为需要互相对上的部件更多。
不写什么同样重要:每一条约束都是你之后必须去核对的一条,而四十个数字核对起来比片区搭起来还慢。如果智能体驱动的是编辑器而不是网页表单,任务书还可以分批下达——先圈出允许它施工的范围,让它铺路网,你走一个路口看看,然后再放它去填街区。在 Cuberta 里每一步都可以撤销,所以在第一个路口就抓出车行道宽度不对,代价是一分钟,而不是重做一遍。
室内:到底需不需要
对城市片区来说,多数情况下不需要。按成本从低到高分三档:
- 不透明玻璃。窗面上一层深色微反射材质,后面什么都没有。掠射角下读成玻璃幕墙办公楼,这算诚实;5 米处正面看就是一块实心体。
- Interior mapping(室内映射)。Joost van Dongen 于 2008 年发表的着色器技术:像素着色器对窗后假想的地板与墙面做射线求交,在命中点采样房间纹理,于是窗户获得真正的视差,却不需要任何室内几何,也不需要额外存储。大多数开放世界城市的窗户都靠它撑着;按投入的每小时计算,这是对生成片区回报最高的一件事。
- 真正建模的室内。墙、地面、家具、灯光,以及一扇在正确高度开向人行道的门。
真正重要的不是你有多少栋楼,而是玩家能走进几栋——在多数城市关卡里是几百栋里的三到十栋。把这几栋做到家具级别,剩下三百栋交给着色器。给所有建筑都生成室内,会把物件数量放大约二十倍,换来没人会进去的房间,还让真正重要的那十栋变差,因为那十栋需要一个人来决定沙发放哪儿。Cuberta 既会搭片区,也会搭带家具的室内;这份力气该花在可进入清单上,而不是花在天际线上。
这份清单要在生成片区之前定。可进入的建筑有立面不具备的要求:门要对齐人行道,首层要落在路缘高度加一级台阶上,占地不能是 6 × 30 米的纯走廊,还得有让玩家进去的理由。把这些补进一栋当墙纸生成的楼里,比一开始就建对要贵。
第一个小时花在哪里
在生成任何一栋建筑之前,先建路网,然后站到一个路口上。道路切出街区,街区分出地块,地块摆出建筑,每一步都约束下一步。遵守这个顺序的生成器,做出的城市从空中看很无聊、走在街上很可信——这是对的那一面,因为玩家永远不会看到那张航拍图。颠倒顺序的生成器给你相反的结果,而相反的结果无法在下游修好:没有任何一种对建筑的操作,能把它放回一条它从未依附过的街道上。