要访问 ArcGIS CityEngine 中的教程工程,请启动 CityEngine,然后在主菜单中单击帮助 > 下载教程和示例。 在选择教程或示例后,系统会自动下载工程并将其添加到 CityEngine 工作空间。
在本教程中,您将学习如何根据图片建模建筑物,包括创建立面结构、插入资产以及向建筑物添加纹理。 在执行此操作时,您将探索一些复杂的 CGA 技术。
对立面结构建模
以下工作流将向您展示如何编写一组 CGA 规则,以根据以下真实世界的图片重新创建立面:
在本部分中,您将使用 CGA 规则创建立面的基本结构。 在继续延伸规则设置时,您将继续对照片进行更详细的分析,您还将学习如何在 CGA 规则中使用预先建模的资产。
创建规则文件
要创建规则文件,请执行以下操作:
体积和立面
现在,开始创建建筑物。 首先,通过拉伸操作创建体量模型。 您将使用建筑物高度的属性。
- 将 height 属性添加到规则文件的开头:
attr height = 24
- 使用 extrude 操作写入初始 Lot 规则,然后将形状命名为 Building。
Lot --> extrude(height) Building
- 由于您仅对立面感兴趣,请使用 Building 规则中的组件分割来移除正面之外的所有面,然后调用 Frontfacade 规则:
Building --> comp(f) { front : Frontfacade }
添加楼层
根据上述立面照片,您可以分析不同类型的楼层,如下图所示:
- 在 height 属性后定义楼层尺寸的属性:
attr groundfloor_height = 5.5 attr floor_height = 4.5
- 将 Frontfacade 规则添加到 Building 规则下:
Frontfacade --> split(y) { groundfloor_height : Floor(split.index) // Groundfloor | floor_height : Floor(split.index) // First Floor | floor_height : Floor(split.index) // Second Floor | { ~floor_height : Floor(split.index) }* // Mid Floors | floor_height : Floor(999) // Top Floor, indexed with 999 | 0.5 : s('1, '1, 0.3) LedgeAsset } // The top ledge just below the roof
前立面将水平分割为多个楼层,每个楼层都具有 floor_height 属性。 Floor 形状将使用 split.index 形状属性(即楼层索引)进行参数化。 此参数将传递给子规则,以确定要为特定楼层创建的元素:
顶层的楼层索引设置为 999。 用于标识此楼层。 请注意中间楼层的重复分割。 由此建筑物可以动态适应不同的高度,并使用中层楼层填充剩余的垂直空间。
请注意,CGA 编辑器窗口将显示一条警告,指示未定义 Floor 和 LedgeAsset 规则。 这没问题,因为您稍后将在教程中引用这些规则。
- 按 Ctrl+S 以保存规则文件。
- 将 Navigator 窗口中的 facade_01.cga 规则拖放到视窗窗口中的地块上。
由此将首次生成立面:
您可以按下 Z 键导航到立面的前方。
另一种分配规则和生成模型的方法是单击检查器窗口中的分配,选择 facade_01.cga 规则,然后单击生成 (Ctrl+G)。
添加楼层窗台
现在,楼层分割为窗台形状和切片形状。
- 使用 floorindex 参数添加 Floor 规则:
Floor(floorindex) --> case floorindex == 0 : Subfloor(floorindex) case floorindex == 2 : split(y) { ~1 : Subfloor(floorindex) | 0.5 : TopLedge } else : split(y) { 1 : BottomLedge(floorindex) | ~1 : Subfloor(floorindex) | 0.5 : TopLedge }
楼层索引用于处理不同楼层的特定窗台:
- 底层 (floorindex 0) 没有窗台,因此只调用切片。
- 由于窗户从楼板平面开始,因此第二层没有底部窗台。 此楼层的阳台将在之后的步骤中创建。
- 所有其他楼层都有底部窗台,切片和顶部窗台区域。
- 保存规则文件并生成:
添加底层地板
底层地板由左右两边的小型墙体区域以及中间的重复切片组成:
- 在规则文件的顶部添加以下属性以及其他属性:
attr tile_width = 3.1
- 在规则文件的底部添加 Subfloor 规则:
Subfloor(floorindex) --> split(x) { 0.5 : Wall(1) | { ~tile_width : Tile(floorindex) }* | 0.5 : Wall(1) }
这将楼层水平分割为重复的切片和两侧的墙壁。
- 在其他属性旁边添加 wallColor 属性:
attr wallColor = "#ffffff"
- 在规则文件的底部继续添加具有 walltype 参数的 Wall 规则:
Wall(walltype) --> case walltype == 1 : color(wallColor) case walltype == 2 : color(wallColor) else : color(wallColor)
将添加参数化的 Wall 形状。 这在后续步骤中为立面添加纹理时非常重要。 在立面照片中,有三种墙壁类型:
- 带有脏蚀纹理的深色砖块
- 带有脏蚀纹理的明亮砖块
- 仅脏蚀纹理 - 用于没有砖结构的立面资产。
- 保存并生成:
如您所见,目前上述 Wall 规则中的墙壁样式生成了相同的输出。 如前所述,当您稍后为不同的墙壁类型添加不同的纹理时,这将发生变化。
添加切片
切片在该立面上是同质的。 您只需要区分底层切片和高层切片。
- 定义 door_width 和 window_width 属性以设置分割尺寸:
attr door_width = 2.1 attr window_width = 1.4
- 在 Subfloor 规则后面添加 Tile 规则:
Tile(floorindex) --> case floorindex == 0 : split(x) { ~1 : SolidWall | door_width : DoorTile | ~1 : SolidWall } else : split(x) { ~1 : Wall(getWalltype(floorindex)) | window_width : WindowTile(floorindex) | ~1 : Wall(getWalltype(floorindex)) }
对于底层切片,添加一个新的 SolidWall 形状,而非 Wall 形状。 这是必要的,因为底层立面楼层是从立面插入的。 为避免门和墙壁之间出现孔洞,您将通过插一个确定厚度的立方体在其中使用实心墙壁元素。 因为您稍后将在 Door 规则中再次使用此厚度,所以将其定义为常量变量 wall_inset。
最好对多次使用的值进行声明,因为这样可以确保在不同的规则中使用相同的值。
- 在属性和 Lot 规则之间添加 wall_inset 常量:
const wall_inset = 0.4
- 将 SolidWall 规则添加到 Wall 规则下:
SolidWall --> s('1, '1, wall_inset) t(0, 0, -wall_inset) i("builtin:cube:notex") Wall(1)
- 在 Wall 规则文件中声明一个函数来根据楼层索引获取墙体类型:
getWalltype(floorindex) = case floorindex == 0 : 1 case floorindex == 1 : 1 else : 2
查看立面照片,可以发现地面和一楼有深色纹理,其他则有明亮纹理。 getWalltype 函数将楼层索引映射到相应的墙壁类型。
- 保存并生成:
要查看此时场景和规则的外观,请打开 FacadeModeling_02.cej 场景和 facade_02.cga 规则文件。
插入立面资产
接下来,您将学习如何在立面上使用预建模的资产。
资产
再次查看立面的照片,您会发现您需要以下资产:
- 窗口 - 用于窗口元素
- 圆形窗口顶部 - 用于窗户上方的装饰
- 三角形窗口顶部 - 用于窗口上方的装饰品
- 半弧 - 用于底层上的弧线
- 窗台 - 用于所有窗台
- 飞檐托 - 用于底层的窗户装饰和弧形装饰
这些资产已经存在于教程工程的资产文件夹中。 要预览这些资产,在 Navigator 窗口中双击它们或将其选中,右键点击并选择文件预览。
在规则文件的属性声明下面添加对这些资产的引用:
const window_asset = "facades/elem.window.frame.obj"
const round_wintop_asset = "facades/round_windowtop.obj"
const tri_wintop_asset = "facades/triangle_windowtop.obj"
const halfarc_asset = "facades/arc_thin.obj"
const ledge_asset = "facades/ledge.03.twopart_lessprojection.obj"
const modillion_asset = "facades/ledge_modillion.03.for_cornice_ledge_closed.lod0.obj"
添加窗口
要添加窗口,请按照以下步骤进行操作:
- 您已为窗口资产的准确位置准备好了规则。 在 WindowTile 规则中调用 Window 形状,将其添加到 Tile 规则后面。
WindowTile(floorindex) --> Window
- 接下来,添加 Window 规则以缩放、定位并在其后面插入窗口资产和玻璃平面:
Window --> s('1, '1, 0.2) t(0, 0, -0.18) [ i(window_asset) Wall(0) ] Glass
- 保存并生成:
添加窗口装饰
再次查看立面照片,您将发现在不同的楼层上有不同的窗口(或窗口元素)。
- 扩展 WindowTile 规则并触发特定于楼层索引的形状,如下所示:
WindowTile(floorindex) --> case floorindex == 1 || floorindex == 999 : Window case floorindex == 2 : Window t(0, '1, 0) WindowOrnamentRound else : Window WindowLedge t(0, '1, 0) WindowOrnamentTriangle
- 第一层和顶层没有特殊装饰(索引 1 和 999);因此,仅调用 Window 形状。
- 您将在第二层插入 WindowOrnamentRound 元素作为附加形状。 由于此元素要与窗口的顶部边框对齐,因此您可以将当前的范围沿 y 轴向上平移 '1。
- 其他窗口切片(在中间楼层)具有其他 WindowLedge 形状以及 WindowOrnamentTriangle 装饰,同样沿 y 轴平移。
您将首先插入代理立方体,而不是直接使用最终资产。 这样更便于设置实际资产的尺寸。 您可以在这种情况下使用内置的立方体资产。
- 在 WindowTile 规则后添加以下规则,通过设置尺寸、将范围居中于 x 轴、插入立方体并对其着色,创建代理立方体以提高可见性。
WindowOrnamentTriangle --> s('1.7, 1.2, 0.3) center(x) i("builtin:cube") color("#ff0000") WindowOrnamentRound --> s('1.7, 1.2, 0.4) center(x) i("builtin:cube") color("#00ff00") WindowLedge --> s('1.5, 0.2, 0.1) t(0, -0.2, 0) center(x) i("builtin:cube") color("#0000ff")
- 保存并生成:
将代理换成实际资产
要将代理替换为真实资产,请按照以下步骤进行操作:
- 窗口装饰的尺寸看起来很合理,因此您现在可以插入资产而不是立方体(对于 WindowLedge,请保留立方体),并使用每个规则调用 Wall 形状来替换颜色:
WindowOrnamentTriangle --> s('1.7, 1.2, 0.3) center(x) i(tri_wintop_asset) Wall(0) WindowOrnamentRound --> s('1.7, 1.2, 0.4) center(x) i(round_wintop_asset) Wall(0) WindowLedge --> s('1.5, 0.2, 0.1) t(0, -0.2, 0) center(x) i("builtin:cube") Wall(0)
- 保存并生成:
- 由于圆形窗口装饰在第二层上缺少侧柱,因此需要扩展 WindowOrnamentRound 规则以进行分割操作。 这将为以下的飞檐托资产准备范围:
WindowOrnamentRound --> s('1.7, 1.2, 0.4) center(x) i(round_wintop_asset) Wall(0) split(x) { ~1 : WindowMod | window_width : NIL | ~1 : WindowMod }
- 在 WindowOrnamentRound 规则后添加 WindowMod 规则: 插入飞檐托前,需设置尺寸:
WindowMod --> s(0.2, '1.3, '0.6) t(0, '-1, 0) center(x) i(modillion_asset) Wall(0)
请注意,通过在 y 方向上应用相对负平移 ('-1),资产的顶部与装饰的底面对齐。
- 保存并生成:
添加门
门切片垂直分割为门、弧形和弧形顶部区域。
继续在 Window 规则下方添加以下规则。
- 为确保非椭圆弧形,弧形区域的高度需要为门的宽度的一半(当前 x 范围):
DoorTile --> split(y) { ~1 : Door | scope.sx/2 : Arcs | 0.5 : Arctop }
- 在门的顶部区域上,插入墙壁元素和覆盖的飞檐托资产。
Arctop --> Wall(1) s(0.5, '1, 0.3) center(x) i(modillion_asset) Wall(1)
- 再次分割弧区域,并插入了两个弧形资产。 使用先前定义的 wall_inset 变量进行插入。 在插入右半弧形资产之前,还需要将其旋转以正确定向:
Arcs --> s('1, '1, wall_inset) t(0, 0, -wall_inset) Doortop i("builtin:cube") split(x) { ~1 : ArcAsset | ~1 : r(scopeCenter,0,0,-90) ArcAsset }
- 在 ArcAsset 规则中插入实际的弧形资产,并设置 Wall 类型以及 Doortop 和 Door 规则:
ArcAsset --> i(halfarc_asset) Wall(1) Doortop --> Wall(0) Door --> t(0,0,-wall_inset) Wall(0)
在 Door 规则中,将门设置为向后退离墙壁。
- 保存并生成:
添加窗台
顶部窗台使用简单的墙条,底部窗台必须在插入了窗台资产的不同楼层上进行区分。
- 为顶部窗台和底部窗台添加规则:
TopLedge --> WallStripe BottomLedge(floorindex) --> case floorindex == 1 : split(y) { ~1 : Wall(0) | ~1 : s('1, '1, 0.2) LedgeAsset } case floorindex == 999 : split(y) { ~1 : WallStripe | ~1 : s('1, '1, 0.2) LedgeAsset } else : WallStripe WallStripe --> split(x) { 0.5 : Wall(1) | ~1 : Wall(2) | 0.5 : Wall(1) } LedgeAsset --> i(ledge_asset) Wall(0)
- 保存并生成:
添加阳台
现在您将执行阳台操作。
- 编辑 Floor 规则,然后将 Balcony 形状添加到第二层中:
case floorindex == 2 : split(y) { ~1 : Subfloor(floorindex) Balcony | 0.5 : TopLedge }
- 通过在 LedgeAsset 规则之后添加 Balcony 规则,从添加颜色的简单代理开始,以确保阳台的位置和尺寸:
Balcony --> s('1, 2, 1) t(0, -0.3, 0) i("builtin:cube") color("#99ff55")
- 保存并生成:
- 将阳台框分割为以下几部分:梁、地板和扶手,如下图所示:
- 通过将阳台分割为 BalconyBeams、BalconyFloor 和 RailingBox 元素,替换 Balcony 规则中的绿色:
Balcony --> s('1, 2, 1) t(0, -0.3, 0) i("builtin:cube") split(y) { 0.2 : BalconyBeams | 0.3 : BalconyFloor | 1 : RailingBox }
- 保存并生成:
- 通过将阳台分割为 BalconyBeams、BalconyFloor 和 RailingBox 元素,替换 Balcony 规则中的绿色:
- 使用重复分割创建支撑阳台的梁:
BalconyBeams --> split(x) { ~0.4 : s(0.2, '1, '0.9) center(x) Wall(0) }*
BalconyFloor 形状仅定义墙壁类型。
BalconyFloor --> Wall(0)
- 使用在 RailingBox 规则上分割的组件,提取阳台扶手的必要面。
RailingBox --> comp(f) { front : Rail | left : Rail | right : Rail }
- 要设置阳台框的尺寸,请插入立方体以创建阳台扶手:
Rail --> s('1.1, '1, 0.1) t(0, 0, -0.1) center(x) i("builtin:cube") Wall(0)
- 保存并生成:
现在您拥有已放置几何资产的最终模型。 您可以将规则应用于不同的地块,或在检查器窗口中浏览用户属性以修改立面设计。
打开 FacadeModeling_03.cej 场景和 facade_03.cga 规则以查看最终结果。
在下一部分中,您将学习如何将纹理应用于此立面。
对立面进行纹理处理
现在,您需要将纹理应用于此立面。
创建纹理资产
要创建纹理资产,请完成以下步骤:
- 在资产定义后的规则文件顶部添加要使用的纹理:
const wall_tex = "facades/textures/brickwall.jpg" const wall2_tex = "facades/textures/brickwall_bright.jpg" const dirt_tex = "facades/textures/dirtmap.15.tif" const doortop_tex = "facades/textures/doortoptex.jpg"
- 对于窗口和门的纹理,定义一个函数,从相应的资产文件夹中获取随机纹理字符串,无需单独列出纹理。 在纹理之后添加以下行:
randomWindowTex = fileRandom("*facades/textures/window.*.jpg") randomDoorTex = fileRandom("*facades/textures/doortex.*.jpg")
设置全局 UV 坐标
使用 CGA 进行纹理处理分为以下三个步骤:
- setupProjection() - 定义 UV 坐标空间。
- set(material.map) 或 texture() - 设置纹理文件。
- projectUV() - 应用 UV 坐标。
添加纹理图层
您将向立面添加两个纹理图层:砖纹理和脏蚀贴图。 为了在整个立面上保持一致的纹理坐标,您需要将 UV 设置添加至 Facade 规则。 若要预先测试纹理设置,需新增中间 FrontfacadeTex 规则。
- 将 Building 规则更改为以下内容:
Building --> comp(f) { front : FrontfacadeTex }
- 在 Building 规则后创建新的 FrontfacadeTex 规则:
FrontfacadeTex --> setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1) texture("builtin:uvtest.png") projectUV(0)
setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1) 定义纹理通道 0(颜色通道)的纹理坐标。 UV 坐标将沿着范围的 xy 平面投影,并在 x 方向上每 2.25 单位和 y 方向上每 1.5 单位重复一次。 texture() 操作是 set(material.map) 的快捷方式。 在此情况下,它将色彩映射表设置为 builtin:uvtest.png,这是一种可以快速检查 UV 坐标的纹理。 然后通过烘烤通道 0 的 UV 坐标来应用 UV 坐标:
- 保存并生成立面以查看 UV 设置:
- 为脏蚀通道添加 UV 设置:
FrontfacadeTex --> setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1) texture("builtin:uvtest.png") projectUV(0) setupProjection(2, scope.xy, '1, '1) set(material.dirtmap, ("builtin:uvtest.png")) projectUV(2)
该纹理应覆盖整个立面,因此您将使用相对运算符 '1 和 '1(立面的尺寸)进行 UV 设置。
- 保存并生成:
- 要查看立面的纹理外观,请将 builtin:uvtest.png 纹理与真实纹理进行交换。
FrontfacadeTex --> setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1) texture(wall_tex) projectUV(0) setupProjection(2, scope.xy, '1, '1) set(material.dirtmap, dirt_tex) projectUV(2)
UV 坐标适用于立面。
- 对于建筑物,此时您只需要设置 UV,因此请将 FrontfacadeTex 规则更改为以下内容:
对墙壁进行纹理处理
在此工作流的先前部分中,您已在 Wall 规则中添加了 walltype 参数。 现在,您将使用它来为每种墙壁类型分配不同的纹理。
- 查找 Wall 规则并将其更改为以下内容:
Wall(walltype) --> // dark bricks with dirt case walltype == 1 : color(wallColor) texture(wall_tex) set(material.dirtmap, dirt_tex) projectUV(0) projectUV(2) // bright bricks with dirt case walltype == 2 : color(wallColor) texture(wall2_tex) set(material.dirtmap, dirt_tex) projectUV(0) projectUV(2) // dirt only else : color(wallColor) set(material.dirtmap, dirt_tex) projectUV(2)
- 保存并生成:
使用其中一种墙壁类型对所有墙壁元素进行纹理处理。
对窗口资产进行纹理处理
对于窗口资产,您将使用一组窗口纹理为玻璃窗格上色,因此需要设置 UV 坐标以覆盖整个玻璃形状。 将通过对 x 和 y 方向都使用 '1 来执行此操作。 在纹理操作中,通过调用先前定义的 randomWindowTex 函数,随机选择一个玻璃纹理。
- 在 Window 规则后面添加 Glass 规则:
Glass --> setupProjection(0, scope.xy, '1, '1) projectUV(0) texture(randomWindowTex)
- 保存并生成:
- 接下来,为玻璃添加镜面光泽。
Glass --> setupProjection(0, scope.xy, '1, '1) projectUV(0) texture(randomWindowTex) set(material.specular.r, 1) set(material.specular.g, 1) set(material.specular.b, 1) set(material.shininess, 4)
- 保存并生成:
对门形状进行纹理处理
门平面的纹理处理方式与窗口玻璃的纹理处理方式相同。 使用以下行替换 Doortop 和 Door 规则:
Doortop -->
setupProjection(0, scope.xy, '1, '1)
texture(doortop_tex)
projectUV(0)
Door -->
t(0, 0, -wall_inset)
setupProjection(0, scope.xy, '1, '1)
texture(randomDoorTex)
projectUV(0)
打开 FacadeModeling_04.cej 场景和 facade_04.cga 规则以查看最终结果。
在本教程中,您学习了如何执行以下操作:
- 对立面结构建模。
- 插入窗口、门和台阶等资产。
- 为墙壁、窗口和门应用纹理。
有关 CGA 形状语法的详细信息,请参阅基于规则的建模教程以及基于规则的建模和 CGA 建模帮助主题。
要继续学习 CityEngine,请参阅完整的 CityEngine 教程目录。