简介:本资源是面向城市规划师、三维建模工程师及CityEngine初高级用户的道路规则库实战套件,专为解决复杂城市路网快速建模与风格统一难题而设计。包内含1247个文件,总计202.24MB,涵盖725张道路纹理与示意JPG/PNG图、172个带材质的道路OBJ模型、84个MTL材质定义文件、34个ArcGIS地理索引ATX文件,以及18个CGB/CGA规则脚本和1个完整CityEngine项目工程(.cej),支持直接导入使用或二次开发。已有1178人学习下载,充分验证其在实际项目中的可用性与稳定性。用户可即刻调用预设的几何生成、交叉口逻辑、车道分级、附属设施(人行道/绿化带/标线)及视觉样式规则,结合GIS数据驱动建模;同时通过CGA脚本与GDB地理表结构,深入理解规则与空间数据的耦合机制,大幅提升从概念方案到高保真可视化落地的效率。 StreetEngine 道路规则库,说白了就是一套用 CGA(Computer Generated Architecture)规则语言写好的“道路生成配方”,它把道路建模从“手动一点点拉模型”变成“写规则自动批量生成”。你给 StreetEngine 一条中心线、一个断面模板,规则库就能自动把车道、人行道、路缘石、标线、路灯、树木全部“长”出来。而且这套配方是参数化的,改一个宽度值,整条路的模型就跟着变,不需要重做。
这篇文章更适合谁看?如果你在用 StreetEngine 做城市级大场景建模,或者想从零搭一套自己的道路规则库,又或者只是想知道 CGA 规则里那些路网相关的函数到底怎么用,那这篇文章应该能帮你少走不少弯路。我会把规则库的完整结构、每个模块的实现思路、以及实际调试中踩过的坑都摊开来讲,尽量让小白也能跟着动手搭一套自己的道路规则。
1. 道路规则库的整体设计与拆解思路
1.1 为什么需要“道路规则库”而不是手动建模
先聊一个最基础的问题:StreetEngine 里已经有了手动建模工具,为什么还要搞规则库?我自己的体会是,手动建模适合做“单条样板路”,也就是你只需要一两条特别精细、要拿去渲染特写的道路;但一旦进入城市级批量生成阶段,道路里程可能上百公里,手动建模就完全撑不住了。
规则库的核心优势在于三点。第一是批量,一条规则可以同时作用于整个路网的所有路段,不管你导入的是 10 条路还是 1000 条路,生成逻辑完全一致。第二是参数化,道路宽度、车道数、人行道宽度、路缘石高度,这些全部变成规则文件顶部的变量,改参数等于改整座城市,不需要一条路一条路去翻模型。第三是可复用,一套规则库做出来,换一个城市的数据,只要坐标系和数据格式对得上,直接套用,顶多调整参数。
还有一个很多人忽略的点:规则库让模型“活”了。手动建的模型是静态几何体,改一处要手动改多处;规则库生成的模型,每次重新生成都会按照最新的规则和属性重新计算,天然适合多方案比选和迭代。
1.2 规则库在 StreetEngine 中的运行逻辑
要搭规则库,先得弄明白 StreetEngine 里道路建模的基本逻辑。它跟普通 CGA 建模不太一样,普通建筑规则处理的是 Shape,而道路规则处理的是 Graph Segments——也就是路网线段。
我简单梳理一下这条链路:
- 第一步,导入路网数据。常见的做法是把 OpenStreetMap、ArcGIS 里的路网数据导出成 Shapefile 或者 Geodatabase,然后通过 StreetEngine 的“Import”功能导入。数据导入后,路网会变成由中心线和交叉口组成的拓扑网络。
- 第二步,给路段指定规则。你可以给整个路网统一指定一套规则,也可以根据道路等级(高速、主干道、次干道、支路)分别指定不同的规则文件。
- 第三步,StreetEngine 会自动把每条路段切分成 Segment,然后 Segment 会沿着中心线方向
extrude出道路几何体。 - 第四步,规则代码里通过
Lane、Sidewalk、Street这些函数组合出道路剖面,再通过Crossing函数处理交叉口。
这里有一个很关键的点:StreetEngine 处理的不是“面”,而是“线”。它先把中心线变成有宽度的 Street Shape,然后你在 Street Shape 这个 Scope 下做细分。理解这一点,后面写规则就不会晕。
1.3 道路规则库的标准目录结构
我在实际项目里习惯把道路规则库拆成多个文件,而不是全部堆在一个.cga文件里。这样做的好处是:规则可以分开维护、分开测试、多人协作时不打架。下面这套目录结构是我多次调整后觉得比较顺手的:
规则库根目录/ ├── road_rule.cga # 总入口规则,根据道路属性分发 ├── assets/ │ ├── road_textures/ # 路面纹理 │ ├── markings/ # 交通标线纹理 │ └── props/ # 路灯、护栏等模型 ├── modules/ │ ├── lane.cga # 机动车道规则 │ ├── sidewalk.cga # 人行道规则 │ ├── curb.cga # 路缘石规则 │ ├── crossing.cga # 交叉口规则 │ ├── vegetation.cga # 行道树规则 │ └── streetlight.cga # 路灯规则 └── config/ └── parameters.cga # 全局参数,所有尺寸统一管road_rule.cga是整个规则库的总入口。它在执行时拿到路段的属性(比如streetType字段),然后判断该走哪套分支。parameters.cga则是所有全局变量,比如LANE_WIDTH = 3.5,这样各个模块引用变量而不是魔法数字,后期改起来非常方便。
2. 核心模块拆解:车道、人行道、路缘石与附属设施
2.1 机动车道模块:车道的拆分与纹理映射
机动车道是整个道路规则库里最简单的模块,但里面有几个细节值得注意。先看一段基础的 CGA 代码:
// lane.cga // 默认单条车道宽度 3.5m,双向 4 车道 @StartRule Lane --> split(u) { ~1 : LanePiece }* LanePiece --> extrude(0.2) setupProjection(0, scope.xy, 3.5, 0.2) texture("assets/road_textures/asphalt.jpg") projectUV(0)这段代码做的事情很直白:把一条 Street Shape 沿着横向split成等宽的多条车道,然后挤出 0.2 米厚作为路面层,再贴沥青纹理。
不过实际项目中,车道宽度不应该这样固定。现实中车道宽度跟道路等级强相关,快速路车道 3.75 米,普通城市道路 3.5 米,支路可能只有 3 米。所以更好的做法是,在参数文件里定义变量:
// parameters.cga LANE_WIDTH = 3.5 // 标准车道宽度(米) CURB_WIDTH = 0.2 // 路缘石宽度(米) SIDEWALK_WIDTH = 4.0 // 人行道宽度(米)然后在 Lane 规则里引用变量:
Lane --> split(u) { ~LANE_WIDTH : LanePiece }*这样改宽度就只需改一个地方。还有个小技巧:不同等级道路的纹理也可以做区分。主干道用新的沥青纹理,支路用稍微旧一点的纹理,代码里可以通过streetType属性做条件判断。
2.2 人行道与路缘石:如何让道路剖面有层次
道路剖面不只是马路中间那一块,它还包括人行道和路缘石。路缘石虽然不起眼,但在视觉上能把道路和建筑底商区分开,是提升场景真实感的关键元素。
人行道和路缘石的组合逻辑一般是这样的:先通过 Street Shape 的细分把道路分成“车行道”和“人行道”两部分,然后分别生成。我常用的写法是:
Street --> Lane Sidewalk Sidewalk --> extrude(SIDEWALK_HEIGHT) split(x) { CURB_WIDTH : Curb | ~1 : SidewalkSurface }路缘石的生成逻辑就是在人行道的外缘分出一条细长条,然后挤出不同的高度,贴上深灰色石材纹理。细节上,路缘石的高度一般在 0.15 米左右,材料适合用带防滑纹的混凝土材质,这样在近距离渲染时才不会显得假。
人行道的贴图和道路的贴图不一样,最好在规则里单独指定纹理:
SidewalkSurface --> setupProjection(0, scope.xy, 2.0, 2.0) texture("assets/road_textures/sidewalk.jpg") projectUV(0)贴图尺寸也要根据实际人行道板的尺寸调整,一般城市人行道砖的规格是 0.6 米 x 0.6 米或者 1 米 x 1 米,贴图平铺尺寸跟这个保持一致,视觉效果才自然。
2.3 交通标线:标线怎么贴才不会拉伸变形
交通标线是道路规则库里一个容易翻车的点。很多新手做标线时直接拿一张标线纹理贴在路面上,结果路一弯曲或者宽度一变,标线就被拉伸得不成样子。
正确的做法是给标线单独做一个Marking层,并且要控制好纹理的平铺参数和方向。中心黄虚线、车道白虚线、停止线这些,都要用不同的规则去生成。我举个例子,双向车道中间的黄色虚线:
Markings --> split(u) { 3.0 : Marking_Dash | 4.0 : Marking_Gap }* Marking_Dash --> extrude(0.01) setupProjection(0, scope.xy, 1, 1) centerUV(x, 0) texture("assets/markings/yellow_line.png") projectUV(0)注意centerUV(x, 0)这一行,它的作用是把纹理在水平方向上居中,这样不管车道总宽度怎么变,虚线都在车道正中。这个函数在道路标线规则里几乎是标配,一定要记住。
如果是双黄线,可以把两条线分别用offset偏移后再生成;如果是停止线,就把它摆在交叉口入口处,垂直于行车方向。标线纹理的尺寸一般要按实际长度来,虚线段 3 米、间隔 4 米是比较标准的高速公路参数,城市道路略有不同,可以按规范调整。
2.4 交叉口处理:自动生成斑马线和停止线
交叉口是道路规则库里最复杂的部分,因为交叉口不再是简单的“一条路”,而是多条路交汇。StreetEngine 对交叉口的处理有一套专门的机制——Crossing 规则。
交叉口规则会在路网相交处生成一个交叉口 Shape,你可以在这个 Shape 上单独绘制斑马线、停止线、转弯导流线等。如果规则没有特殊处理交叉口,默认情况下两条路相交时会出现路面重叠或断面的问题,看起来非常不专业。
我通常的交叉口规则会做三件事:
Crossing --> Crosswalk StopLine Crosswalk --> s(8, 1, scope.sz) t(0, 0.01, -5) i("assets/props/crosswalk.obj")斑马线我用的是一个带黑白条纹的模型,尺寸按实际斑马线标准做(宽度 8 米、长度根据道路宽度定),然后平移到路口入口的位置。停止线则是一根白色横条,放在斑马线前大概 2 米的位置。
交叉口规则的一个注意事项:不同交叉口形态(十字、T 字、不规则)出来的 Shape 几何不一样,规则必须对origin(交叉口 Shape)做兜底处理,避免在某些特殊角度时出现模型穿插。
2.5 附属设施:路灯、行道树与护栏的自动布置
道路两边只有路面和标线是不够的,路灯、行道树、护栏这些附属设施能快速提升场景的“城市感”。它们的逻辑其实非常简单——沿道路中心线按固定间距排布。
以路灯为例,间距一般 30 米到 40 米,道路两侧交替排列。CGA 里可以用split(u)配合r(旋转)和t(平移)来实现:
StreetlightRow --> split(u) { ~35 : StreetlightInstance }* StreetlightInstance --> t(0, 0, 3.0) r(0, 180, 0) i("assets/props/streetlight.obj")行道树的逻辑类似,但间距会小一些,通常是 6 到 8 米一棵。这里有一个性能上的建议:在大场景中,附属设施的模型面数不宜过高,否则整体渲染压力会非常大。我一般在 StreetEngine 里先生成带附件的“完整版本”,确认效果后,再生成一个不带路灯和树木的“基础版本”用于大场景集成,这样能节省大量资源。
3. 实操过程:从零搭建一套道路规则库
3.1 数据准备:路网数据要从哪里来
搭建规则库之前,第一步是搞定路网数据。如果你有 ArcGIS 或者 QGIS 的基础,可以用它们导出 Shapefile;如果没有,也可以直接从 OpenStreetMap 下载路网数据,然后用 ArcGIS Pro 的Export Features功能转成 StreetEngine 能识别的格式。
这里有个经验:路网数据必须要有道路等级属性。Osm 里常见的字段是highway(等于motorway、trunk、primary、secondary、tertiary、residential等),StreetEngine 导入后会把这些字段作为 Shape 的 attribute。有了这个,规则里才能区分主干道和支路,分别套用不同的参数。
如果你手上没有带属性的路网,也有变通方案:在 StreetEngine 导入时手动给不同图层指定不同的规则。但这样可维护性差,后期加路网还得手动指定,不如一开始就把属性弄好。
3.2 在 StreetEngine 中导入路网并生成初始模型
数据准备好后,在 StreetEngine 中的操作步骤如下:
- 新建一个 Scene,坐标系选择跟路网数据一致的坐标系(这点很重要,坐标系不一致会导致路网偏移到外太空)。
- 点击菜单栏的
Layer->Add Layer->Shapefile,选择你准备好的路网数据。 - 导入后,选中路网图层,右键选择
Assign Rule File,指定刚才建的road_rule.cga。 - 如果规则写好了,场景里会立刻出现带纹理的道路模型;如果没出现,检查一下规则文件有没有语法错误。
我第一次做道路规则时,在Assign Rule File之后等了半分钟没有任何反应,后来发现是规则文件里引用的纹理路径写错了。这里建议把所有的资源路径都用相对路径,避免换电脑或者换项目时找不到贴图。
3.3 参数文件与主规则文件的分工与联调
在调试阶段,我会把parameters.cga里的参数逐个修改,看场景中的模型变化,快速验证规则是否生效。比如把LANE_WIDTH从 3.5 改成 3.2,如果道路宽度立刻变化,说明规则引用参数的链路是通畅的。
主规则文件的写法要考虑到属性的分发。我一般这样设计逻辑:
@StartRule Road --> case streetType == "motorway": MotorwayDesign case streetType == "primary": PrimaryRoadDesign case streetType == "residential": ResidentialRoadDesign else: DefaultRoadDesign这样每条路会根据自身的属性走不同的生成逻辑。不同等级的道路可以拥有不同的车道数量、人行道宽度和附属设施密度。
3.4 编码实现:人行道、车道与分隔带的完整规则
下面给出一套简化的完整示例,大家可以直接复制这一个文件,配上对应贴图和模型路径就能跑通:
@StartRule Street --> Lane Markings Sidewalk StreetlightRow Lane --> split(u) { ~LANE_WIDTH : LanePiece }* LanePiece --> extrude(0.2) setupProjection(0, scope.xy, 3.5, 0.2) texture("assets/road_textures/asphalt.jpg") projectUV(0) Markings --> split(u) { 3.0 : Marking_Dash | 4.0 : Marking_Gap }* Marking_Dash --> extrude(0.01) setupProjection(0, scope.xy, 1, 1) centerUV(x, 0) texture("assets/markings/yellow_line.png") projectUV(0) Sidewalk --> extrude(0.15) split(x) { 0.2 : Curb | ~1 : SidewalkSurface } Curb --> setupProjection(0, scope.xy, 1, 1) texture("assets/road_textures/curb.jpg") projectUV(0) SidewalkSurface --> setupProjection(0, scope.xy, 1, 1) texture("assets/road_textures/sidewalk.jpg") projectUV(0) StreetlightRow --> split(u) { ~35 : StreetlightInstance }* StreetlightInstance --> t(0, 0, 3.0) r(0, 180, 0) i("assets/props/streetlight.obj")这只是最简版本,真正项目里会复杂很多,比如公交车道、非机动车道、机非分隔带、停车位等,这些都可以在 Lane 的split里继续往下细分。核心思路是:大剖面拆成小剖面,小剖面再拆成模型,层层递归。
3.5 效果检查:Local View 与 StreetEngine 场景切换
写完规则后,建议在 StreetEngine 的 Local View 窗口里查看单条路的生成效果。Local View 可以实时反馈规则执行结果,很方便调试。
如果道路在场景里出现破面或者错位,优先检查两件事:一是 SCOPE 的坐标系是否在遇到旋转后“绕晕”了;二是纹理的方向轴是否正确。很多时候问题出在projectUV之前忘记setupProjection,导致纹理坐标混乱,贴图像被揉成一团。
确认单条路没问题后,再对整个路网图层执行 Generate,做全场景的规则生成。
4. 性能优化与规则库调优技巧
4.1 减少三角面数:哪些参数会影响最终表现
道路规则库一旦应用到大范围场景,性能问题就出来了。最开始我拿一个 10 平方公里的城市区块测试,结果生成一次要十几分钟,场景缩放也卡顿严重。后来逐项排查,发现三角面数爆炸主要来自几个地方:
- 人行道挤出高度方向分段数过多(其实挤出 0.15 米高的长方体,只要 6 个面就够了)
- 路灯和树木的模型面数太高(一个路灯模型三万多面,整条路生成一百多个路灯,直接卡死)
- 纹理尺寸过大(贴图都是 4K 纹理,GPU 显存直接被打满)
优化思路要分两步。第一步是改参数:把路灯、树木这种重复模型的间距调大,把贴图全部压缩到 2K 以内,人行道和路缘石的挤出高度分段改为 1。第二步是改结构:大场景中把附属设施和路面分开两个图层,一个管大范围路网,一个管重点区域精细模型。
4.2 CGA 代码编写中的性能陷阱
CGA 代码本身也有性能差异。split操作会比extrude更消耗资源,因为 split 会生成更多子 Shape。但道路模型又离不开 split,所以关键是要控制 split 的递归深度。
写规则的一个优化原则是:尽量不要在每次生成时都重建整个模型。如果你发现某条规则每次都从最外层重新跑一遍,而它的子 Shape 已经可以缓存了,可以考虑把子规则用NIL提前终止,或者用@Hidden属性标记不需要显示的子规则。
还有一个小细节:CGA 中三维模型的导入量很高。如果你把路灯的 OBJ 文件直接用i()插入,模型文件本身的大小会直接影响生成速度。规则库的资产管理上,优先使用低模版本,必要时用 Billboard 替代实体模型。
4.3 规则库调试技巧:如何快速定位规则报错
StreetEngine 的报错机制不算太友好,经常只给一句话:“Unknown attribute”或者“Division by zero”。我的调试经验是,先用print()把关键变量的值打印出来看看。比如:
@StartRule Street --> print(streetType) print(scope.sz)这样在控制台就能看到当前路段在被规则处理时的属性值和尺寸,能很快判断是数据问题还是规则问题。
还有一个技巧是“最小复现”。当某个复杂的规则执行报错时,把规则代码一步步注释,直到只剩最简单的extrude加texture,确认能跑通后再慢慢把复杂度加回来。很多时候报错原因就藏在某个细微参数上,比如路面宽度为 0 导致split除零。
5. 常见问题与排查技巧实录
5.1 规则生成了但场景里什么都没有
遇到这种情况,第一反应先看控制台有没有报错。如果没有报错,那大概率是规则生成的模型尺寸太小或者位置偏离视口。
我碰到过一种比较隐蔽的情况:路网数据导入时坐标系对不上,导致道路生成到了离场景原点非常远的地方。这时候只需要把图层的坐标重新投影到和场景一致,问题就解决了。
还有一种情况是贴图资源路径错误,导致模型生成了但表面是纯色。这时候在规则里加上print("texture loaded")只能确认逻辑走到,不能确认贴图加载成功,建议直接在 StreetEngine 的资源管理器里双击贴图确认能否打开。
5.2 交叉口处路面断裂或重叠
交叉口问题基本都出在 Crossing 规则缺失或写法不当。如果你想快速解决,可以试试关闭交叉口自动生成,改用复杂一点的 Street Shape 端点处理逻辑,把交叉口区域直接并入两条街道的规则里。
但最佳方案还是老老实实写 Crossing 规则。注意几个要点:
- Crossing 规则的生成单元是交叉口 Shape,而不是街道 Shape
- 斑马线和停止线都放在 Crossing 的坐标系下,要通过
t()精确平移到路口 - 如果交叉口的 Shape 方向不固定,需要根据
scope.rz判断旋转
5.3 道路两端的边界有问题,如何封口
很多时候道路生成出来后,两端是开放的,能看到模型的内部结构,非常影响效果。解决方案是给道路规则加一个“封口”分支:
Street --> Lane Sidewalk EndCap EndCap --> s(scope.sx, scope.sy, 0.1) t(0, 0, -0.05) i("assets/props/road_end.obj")这里i()插入一个专门做的端头封口模型,可以是一块简单的深色面板,也可以做一个完整的道路端部建模。
5.4 关于规则库文件组织和版本管理的建议
规则库文件多了之后,版本管理也是个麻烦事。我建议把整个规则库文件夹纳入 Git 管理,贴图、CGA、模型资源全部统一版本。这个建议虽然听起来像是常识,但在实际项目中,真正做到的团队并不多。
我自己的习惯是,每次修改规则文件后,至少在提交说明里写明改了哪个模块、调整了哪个参数。等某次生成效果变差了,你就可以用 Git 回退到上一个稳定版本,对比效果。没有版本管理的话,规则库改坏了只能靠记忆慢慢往回找,那酸爽,试过一次就懂。
6. 道路规则库的扩展方向与实际应用心得
6.1 从“能看”到“能用”:数据驱动规则库
道路规则库最大的价值在于它可以和真实数据联动。如果你能拿到道路的车道数、路宽、限速、公交线路等真实属性,规则库就能生成非常准确的交通模型,而不是停留在“看起来像道路”的阶段。
做法也很简单,在路网数据的属性表里加几个字段,比如lanes、width、sidewalk,然后在 CGA 规则里读取:
case lanes > 4: WideMultiLane case lanes <= 4: NormalRoad这样的话,规则库就变成了一套“从数据到模型”的自动化工具,比手动建模的信息承载力高得多。
6.2 和 GIS 数据联动:从 JSON 或 Geojson 生成道路
StreetEngine 支持的导入格式有限,但你完全可以通过中间工具把数据转成支持格式。比如用 Python 处理 GeoJSON,转换成 Shapefile,再导入 StreetEngine。转换过程中可以顺便补充属性字段,一次搞定。
如果你做的是 WebGIS 项目,想通过 StreetEngine 生成道路模型后导出成 glTF/OBJ,再放到 Cesium 或者 Mapbox 上展示,那也是完全可行的。道路规则库生成的模型几何结构清晰,导出后在其他引擎里基本能无缝使用。
6.3 我在实际操作中的体会和几个小技巧
最后分享几个我自己总结的小技巧。
第一,规则命名要规范。给规则起一个清晰的名字,比如Lane_4Lane、Sidewalk_Narrow、Crosswalk_Standard,比叫Rule1、Rule2好用一百倍。因为规则文件一多,你根本记不住Rule1是干嘛的。
第二,善用注释。CGA 支持//注释,在每个规则文件头部写清楚这个文件的作用、修改日期、修改人,比什么都重要。
第三,不要追求绝对真实。道路建模做到一定程度,边际效益递减非常明显。与其花大量时间调整某条路的局部细节,不如把精力放在整个路网的结构正确性和视觉统一性上。把主干道、支路、人行道、标线的粗细对比做出来,整体效果就已经很好了。
第四,定期做全量重新生成。StreetEngine 支持局部更新,但局部更新有时会因为缓存导致效果不一致。每次改完规则库,建议全量重新生成一遍,确保所有路段都是按照最新规则生成的。
这几点看起来简单,但都是实打实踩过坑换来的。规则库这东西,做得越久越会发现,它比拼的不是技巧难度,而是代码组织能力和数据管理能力。就像做菜一样,菜谱谁都会看,但刀工火候,都得靠时间练出来。
本文还有配套的精品资源,点击获取