简介:一份面向CityEngine初学者的入门笔记PDF,系统梳理ESRI公司基于CGA语句的程序化建模思路,将建筑高度、层高、窗宽、纹理等参数以代码方式定义,支撑大规模批量建模。内容涵盖属性定义、句柄调整、模型构建、常数定义、随机纹理五个核心模块,并配有CGA语法示例,适合城市规划、游戏场景、电影模型等领域的建模人员快速上手。资源为单个PDF文档,压缩包仅1.42MB,轻量便携,可随时翻阅。目前已有949人学习下载。笔记从@Group分组属性、@Range取值限制、@Handle句柄交互,到comp/split等面分割规则均有介绍,示例由简单建筑体块逐步递进至立面分割,配合注释讲解各属性、函数与常量的作用,读者可借此理解CE参数化建模流程,减少从官方文档入门的摸索成本,快速构建可调节的建筑原型。
1. CityEngine 入门,卡在哪一步才最值得死磕
见面先说结论:cityengine 入门 的核心,不是学会点按钮,而是学会读和写 CGA 规则。第一次打开这个软件的人,十有八九会迷失在三维视口和一堆选项卡里,翻半天菜单也做不出一栋像样的楼;反过来,只要理解了一条“地块→挤高→切面→对面做开窗”的规则递推,后面几十栋楼、整个街区,都是同一套逻辑的批量复制。这篇笔记从界面识别、数据导入、CGA 语法到踩坑排查,按我处理真实项目的顺序把完整路径写出来。适合拿到的是一堆 Shapefile 和卫星底图、要做规划方案或游戏场景原型、却被手工建模折磨到想放弃的人。
2. 环境与界面盘清:把第一块 Shapefile 变成一座建筑
CityEngine 桌面版给人的第一印象是“视图真多”。它不像 Blender 那样只有一个主视口加一堆面板,而是把规则编辑、场景图层、属性检查和三维预览拆成了好几个窗口。新手最容易犯的错,是先去研究每个按钮干什么,结果一晚上过去还在视口里转镜头。我一般建议只先认识四个地方:三维视口、图层窗口、CGA 编辑器、属性检查器。围着这四个窗口走通一遍流程,再回头看不认识的按钮也不迟。
2.1 先认识四个窗口:场景、图层、CGA 编辑器与检查器
三维视口在正中央,负责显示地形、地块和生成后的模型;鼠标左键旋转、中键平移、滚轮缩放,这个操作跟大多数 DCC 软件一致。左下方或独立停靠的图层窗口列出当前场景里的所有图层,比如地形图层、地块图层、道路图层,每个图层前面有一个小眼睛控制显隐。CGA 编辑器在底部或侧边,是一个纯文本编辑区,写规则、保存规则都在这里,语法错误会直接显示在它下方的消息区。
属性检查器通常在右侧,选中任意一个地块后,它会列出这个地块的属性,包括来自数据源的字段和规则里定义的属性。属性检查器在入门阶段容易被忽略,但它恰恰是调参最爽的地方:你在 CGA 里写好attr floorCount = 10,保存并应用后,右侧会出现一个数字输入框,把 10 改成 20,模型立刻重新生成。这个“写规则→拖动参数→实时看结果”的循环,就是 CityEngine 和普通建模软件最本质的差别。我把这个循环称为规则驱动的工作流。
2.2 导入 Shapefile:坐标系、单位和字段名决定了第一印象
拿到一份地块数据后,最常见的操作是File → Import → Shapefile。这个导入过程看起来简单,但有一件事必须在导入前确认:.prj文件是否存在。.prj记录了数据的坐标系,缺失时 CityEngine 会弹一个对话框让你选择坐标系。这里的典型错误是随手选一个,或者选成经纬度坐标系的 WGS84,结果导入后地块跑到几公里外。我一般会在导入前用 QGIS 打开 Shapefile,在图层属性里看坐标系和单位,如果是经纬度就先投影成 Web Mercator 或当地高斯投影,再导出一次。
单位不一致是另一个大坑。有些数据虽然坐标系正确,但几何单位是英尺或不知道什么单位,导入后地块尺寸和地形比例对不上。判断方法很简单:导入后选一块已知边长的地块,在检查器里看它的尺寸是多少。如果一块真实宽度为 30 米的地块显示成 3000,说明数据单位是厘米级偏差,需要利用软件里的单位换算或重新投影。字段名也值得提前检查,因为后面规则里要直接用字段:比如地块高度字段叫height还是H,楼层数字段叫floors还是FL,规则里写错了名字,生成时不会报错,只会按默认值执行,这种静默失败最难受。
小区块测试是个好习惯。我拿到任何数据,会先在图层上删到只剩三五块地,导入测试跑通后再把完整数据替换进去。这样做的理由是:完整数据动辄上千个面,一旦出现规则错误或者数据拓扑问题,很难判断到底是规则写错还是数据源有问题;只留三块地,所有变量肉眼可见。CityEngine 有撤销功能,但规则生成的结果在部分版本里撤销并不彻底,小范围测试是最稳妥的后悔药。
3. CGA 规则语法:形状是怎么被一层层推出来的
CGA 是 CityEngine 的规则语言,全称 Computer Generated Architecture。它和 Python、JavaScript 这类通用语言完全不同,不写循环、不定义函数返回值,而是用“形状替换”来表达逻辑:一个形状被规则匹配后,被替换成若干个新形状,新形状再被下一条规则匹配,层层递推直到所有形状都被处理成最终模型。理解不了这个递推模型,后面写规则很容易卡住。
3.1 规则的基本结构:形状、规则名与替换
一份 CGA 文件是一个纯文本文件,后缀通常为.cga。文件里每条规则的基本形式是“左侧一个形状名,冒号,右侧一串操作”。例如下面这段代码是完整的入门级规则:
@StartRule Lot --> extrude(world.y, 10) Mass Mass --> comp(f) { front : Facade top : Roof side : Facade }第一行@StartRule是入口标记,它告诉 CityEngine 规则从这里开始执行。Lot是输入形状的默认名称,也可以改成地块图层的名字。第二行extrude(world.y, 10)把当前形状沿世界 Y 轴拉伸 10 米,生成一个体块;随后这个体块进入名为Mass的规则。在Mass里,comp(f)把体块按面拆分为前面、顶面、侧面三部分,各自进入新的规则处理。这条规则背后的思想是把一个复杂建筑拆成“整体→面→局部面”的层级,每层只管一层事。
这里要特别说明@StartRule的重要性。如果规则文件里没有这一行,选中地块后点击应用规则,软件可能毫无反应,也不给任何提示。这是个非常容易劝退新手的点。一个规则文件里可以只写一个入口,也可以写多个,但选用的规则文件必须保证入口存在。我把这个入口视为所有地块的“大门”,没有大门,建筑永远生成不出来。
3.2 三大核心操作:extrude、comp 与 split
extrude的作用是拉伸。常见写法是extrude(world.y, 高度),表示沿世界坐标 Y 轴方向拉伸指定高度。也可以传入数值表达式,比如extrude(world.y, height),这里的height可以是规则里定义的属性,也可以是 Shapefile 里的字段。当数据源有高度字段时,这个写法就是“批量生成不同高度建筑”的关键。需要提醒的是,extrude生成的是封闭体块,顶面和底面也会被创建,如果不处理它们,后续贴图阶段会出现底面也贴墙面材质的情况。
comp的作用是拆分。comp(f)表示按面拆分,f是 face 的缩写。括号里的大括号内是分面规则,标准用法如front : Facade、top : Roof、side : Wall,冒号左边是面方向,右边是下一步要进入的规则。除了f,还有e表示按边拆分、v表示按顶点拆分,入门阶段用到最多的是f。需要注意方向关键字front、back、left、right、top、bottom是基于形状局部坐标系的,不是世界坐标;如果一个地块旋转过,front可能朝东也可能朝南,出问题时不要怀疑规则写错,先看形状的局部坐标系方向。
split的作用是切割。这是做立面最常用的操作,常见写法是split(y) { ~3 : Floor }*,意思是沿局部 Y 方向切割,每层高约 3 米,*代表重复切割直到剩余高度不足为止。波浪号~表示“近似分配”,软件会根据实际总高度自动微调每一段的尺寸,避免最后一段高度和其他层差太多。如果想要等宽切割,可以写成split(x) { 2 : A | 1 : B },竖线是分隔符,按从左到右的顺序依次切出 2 米宽的 A 段和 1 米宽的 B 段,然后再循环,通常要配合*使用。
3.3 用参数和随机数控制立面:attr 与 case
写死高度的规则只能应付固定场景。实际项目里,楼层数、层高、窗户宽度、屋顶形式,每一项都可能变。CGA 的属性机制把它解成了“规则文件 + 参数”两层:
version "2024.0" @Group("建筑控制") @Range(3, 40) attr floorCount = 10 @Group("建筑控制") @Range(2.5, 4.5) attr floorHeight = 3.2 @StartRule Lot --> extrude(world.y, floorCount * floorHeight) comp(f) { front : Facade top : Roof }@Group让属性在检查器里归到名为“建筑控制”的分组下;@Range(3, 40)定义滑块的最小值和最大值。保存规则、应用规则后,检查器里会出现这两个属性,拖动滑块就能改变楼高和层高。这种方式比改代码高效得多,尤其在给客户或非技术同事展示方案时,一个滑块就能解释“为什么楼高能调”。
随机化是 CGA 另一项利器。case关键字可以根据条件选择不同分支:
Window --> case random() < 0.4 : set(material.colortex, "assets/textures/curtain_a.png") else : set(material.colortex, "assets/textures/window_b.png")random()返回 0 到 1 之间的随机数,配合case可以让每扇窗户有 40% 的概率用 A 贴图,60% 用 B 贴图。这种随机性在远距离看街区时特别重要,否则一整条街的窗户纹理完全相同,一眼看去非常假。随机数不是每次点击都变化的,它与形状的种子值相关;想固定随机结果时不要动地块形状顺序,想换一批随机结果时,选中生成后的模型重置再生成即可。
4. 从地块到街区:一条能落地的入门建模流程
前面三章理解了,这一章是完整的“抄作业”流程。我会把一个街区生成过程拆成三步:准备数据、写规则、调参出图。每一步完成什么、失败看哪里,都写清楚。
4.1 准备地块数据:把图层清理到能导入的状态
最常见的做法是从 OpenStreetMap 或规划部门拿到地块边界,然后再做三步清理。第一步,目视检查有没有重复面、重叠面、自相交。CityEngine 对重叠面很敏感,两块地重叠会导致生成结果互相穿插。第二步,检查地块边界是否闭合。Shapefile 在转换过程中偶尔会出现边界不闭合的情况,这种面在 CityEngine 里表现为生成楼体时侧面缺一块。第三步,确认地块面方向。面有正反之分,法线朝下的面会让楼体往地下长。这个可以用 QGIS 的“修复几何”工具批量处理,也可以在 CityEngine 里选中地块后右键检查法线方向。
地形数据也要提前确认坐标系和范围。常见流程是导入地形 TIF 或 DEM 栅格,然后通过Layer → Create Terrain生成地形模型。需要注意地形的单位必须和地块一致,否则地块要么悬浮在空中,要么埋进地里。我的习惯是:地形作为底图,地块作为生成层,两者分开管理,别把地块自动落在地形上,否则后续调地块高度时会被地形高度干扰。
4.2 写一份中等复杂的街区规则并应用
下面这份规则比入门版复杂一些,它能完成“沿街立面、顶层收口、开窗”三个动作,你可以在 CGA 编辑器里直接粘贴运行:
version "2024.0" @Group("楼层") @Range(2, 30) attr floorCount = 8 @Group("楼层") @Range(2.5, 5.0) attr floorHeight = 3.2 @Group("立面") @Range(2, 6) attr windowWidth = 3.0 @StartRule Lot --> extrude(world.y, floorCount * floorHeight) comp(f) { front : Facade top : Roof side : Facade bottom: NIL } Facade --> setupProjection(0, scope.xy, 4, 3.2) set(material.colortex, "assets/textures/wall_brick.png") color("#c8b090") split(y) { { ~floorHeight : Floor }* } Floor --> split(x) { { ~windowWidth : WindowTile | 0.4 : Pillar }* } WindowTile --> case random() < 0.7 : set(material.colortex, "assets/textures/window.png") else : set(material.colortex, "assets/textures/wall_brick.png") s(3.6, 2.8, 0.4) t(0, 0.15, 0.1) Pillar --> set(material.colortex, "assets/textures/pillar.png") Roof --> color("#5a5a5a")逐段说明一下。最开始的几个attr定义了楼层数、层高和窗宽,这是后面所有计算的公共参数。Lot规则先把地块拉伸成体块,再用comp(f)拆分六个面。注意我写了bottom: NIL,这是常用的写法,表示底面直接丢弃,不生成任何几何体,否则底面会在渲染时和地形重叠,产生黑暗色块。
Facade规则先用setupProjection(0, scope.xy, 4, 3.2)设置 UV 投影方式:按局部平面投影,贴图尺寸为 4 米乘 3.2 米,这是为了控制砖墙纹理的重复密度。set(material.colortex, ...)是给当前形状指定纹理,color("#c8b090")是整体着色,相当于给模型一个淡黄褐色基调。然后split(y)按层高切出每一层,并交给Floor规则。
Floor规则按窗宽切分,每段约windowWidth米宽,中间夹 0.4 米宽的柱。WindowTile里用random()决定这块区域是窗户还是实墙,然后再横向缩放并偏移,做出窗台厚度。最后Roof只是简单给了个深灰色。这份规则跑通后,你会看到一排有窗、有柱、有颜色的建筑排在一起,视觉上已经接近规划方案的效果图底稿。
应用规则时,在场景中选中所有地块,然后在 CGA 编辑器工具栏点“应用规则文件”,或者直接右键选中面选择应用规则。规则应用完成后,视口里立刻生成大量模型;如果模型没出现,回到 CGA 编辑器的消息区看日志,最常见的错误是extrude参数传入了字符串而非数字。日志里一般会给出具体行号,双击错误行可以直接跳到对应位置。
4.3 调参出图:检查器里的滑块就是后悔药
规则应用成功后,右侧检查器会列出floorCount、floorHeight、windowWidth三个滑块。把floorCount从 8 拖到 16,整条街的楼层数立刻翻倍;把windowWidth拖到 5,窗户变宽,柱子间距变大。这种实时反馈就是 CityEngine 最值得付出的学习成本。
需要注意,属性修改后只要没有保存规则文件,滑块调整的结果是临时的。一旦保存规则文件,下一次重新应用时滑块值会被规则文件里的默认值覆盖。想保留调好的参数,建议规则文件里使用默认值,然后依赖检查器里的属性来做方案比选,不要混着改,避免“为什么保存后又变回去了”的困惑。这个坑我踩过不止一次,后来养成的习惯是在规则文件里固定最合理的默认值,项目参数全部通过检查器调整,一切有迹可循。
5. CityEngine 避坑排查:五个真实翻车现场与解法
规则写多了,翻车是家常便饭。有些问题报错明确,有些完全沉默,沉默的最可怕。下面这五条都是项目里反复遇到过的,按“现象、原因、解决”拆开写,你遇到时可以直接对照。
5.1 应用规则后什么都没生成,也不报错
这是最常见的入门级故障。选中地块、点击应用规则,视口毫无变化,日志窗口一清二白。排查顺序是这样:先确认规则文件里有没有@StartRule入口,没有入口规则无法启动,这是 60% 的情况。再看地块图层是否被隐藏或者锁定了,图层隐藏时规则能应用但视口不显示,看起来就像没生成。最后检查是否选中的应用规则文件是对的,文件名和路径都不要有中文或空格,某些版本对中文路径支持不佳,规则文件读不出来。
5.2 建筑只有一侧有窗户,其他面全是实墙
现象是挤出的建筑体块,只有某个朝向有窗有门,其他立面空荡荡。原因在于comp(f)的面方向判定。front是形状局部坐标的前方向,如果地块旋转了 90 度,front就不再是街道朝向。解决方法是先确认地块的局部坐标系:选中地块,观察视口里的方向指示,如果有必要,可以将规则里的方向改为多个方向映射到同一个立面规则,比如front : Facade | right : Facade | left : Facade | back : Facade,这样无论地块怎么转,四个方向都有完整立面。
5.3 楼体穿地或者浮空
楼体穿地的典型场景是地形有起伏,但地块是平面,地块上的建筑按海平面标高生成,结果遇到上坡路面被埋住。原因有两个层次:一是地形和地块数据本身的坐标系不一致,二是地块没有真的贴在地形上。解决方法是先检查两者坐标系,如果一致,再在 CityEngine 里把地块图层和地形图层对齐,常见做法是打开地块图层的“对齐到表面”选项,让地块面整体贴在地形表面上。另一个经验是少量穿地不必太较真,只要建筑一层地面线和道路衔接不是太离谱,最终效果图看不出来;如果穿得厉害,直接抬高地块或调整地形数值即可,别让问题延后到导出阶段才暴露。
5.4 split 规则只切出第一层,后面不执行
在split(y) { ~floorHeight : Floor }*这种写法里,很多人会漏掉末尾的*,结果只生成了第一层楼板,整栋楼只有一层。原理是缺少*时,split 按从左到右执行一次就结束;加上*才会循环切分直到高度余量不足。这个报错不一定出现,属于沉默型故障。另一个相似问题是用|分隔多个切割段时,顺序写错导致后续段永远不触发。解决方法是把切割顺序从头到尾读一遍:从左到右依次切,*表示重复,~表示近似分配,三者配合时先手算一小段验证逻辑,不要直接在大场景上跑。
5.5 导出到其他软件后纹理丢失或发黑
CityEngine 里看起来正常,导出成 FBX 或 OBJ 后,在其他软件里打开,模型在但纹理全黑或缺失。原因是纹理贴图路径没有被正确传递。CityEngine 默认用绝对路径引用贴图,比如assets/textures/wall.png在导出时被写作C:/Users/xxx/project/assets/textures/wall.png,换一台机器或移动项目目录后就找不到文件。解决方式是把项目整体打包成.rpk规则包,或者导出时勾选“嵌入纹理”选项,让贴图跟随 FBX 文件走。另一种稳妥做法是导出前把所有贴图复制到项目根目录下的assets/textures文件夹,并保证规则里用的是相对路径。这个坑往往在交接文件时爆发,合作方收到模型打开全黑,免不了又要解释一轮。
6. 进阶收尾:把规则库和纹理下载沉淀成自己的资产包
走到这一步,你已经能用 CGA 规则批量生成一条街了。下一个值得投入的方向是建立自己的 cityengine 规则库。我现在的习惯是:每个项目结束后,把那些可复用的规则抽出来,统一结构放到一个固定目录里,比如rules/common/、rules/residential/、rules/commercial/,同时把用得到的重复贴图整理到assets/textures/下,按砖墙、玻璃、屋顶、百叶窗分类。这样下次新项目只要拷贝规则库和纹理库,写新规则时只需引用现成资产,效率能提升一大截。
纹理下载一直是很多人卡住的点。刚开始我会去各类免费纹理站下载整包贴图,但很快发现不同来源的贴图画风和分辨率参差不齐,用在同一个项目里会显得很突兀。后来我定了一条原则:一个项目只用一到两个来源的纹理,并且优先下载无缝拼贴(seamless)版本,因为 CGA 立面几乎全靠 UV 平铺,普通纹理一旦平铺就会露馅。下载时还要注意看许可证,路径不要带中文,贴图尺寸统一压到 1024 或 2048,这对导出体积和视口预览都有帮助。
最后分享一个验证规则库是否成熟的小技巧:在小场景里放置三种不同形状的地块,一种狭长、一种方正、一种带斜边,然后把你的通用规则应用上去。如果三种地块上生成的建筑都没有穿帮——窗户尺寸合理、屋顶没有悬空、墙角没有裂缝——这个规则才算“出门可用”。我会定期把这样的测试场景存成一个底座文件,任何新规则都先在底座上跑一遍,而不是直接放到大项目里碰运气。这个习惯帮我省掉了大量返工,也希望帮到你。
本文还有配套的精品资源,点击获取