要访问 CityEngine 中的教程,请单击帮助 > 下载教程和示例...。 在选择教程或示例后,系统会自动下载工程并将其添加到您的工作空间。
对立面结构建模
本教程介绍了如何根据图片对建筑物进行建模,并介绍了一些更复杂的 CGA 技术。 在本部分中,您将使用 CGA 规则创建立面的基本结构。
设置
要设置教程,请完成以下步骤:
- 将 Tutorial_07_Facade_Modeling 工程导入 CityEngine 工作空间。
- 打开 Tutorial_07_Facade_Modeling/scenes/FacadeModeling_01.cej 场景。
立面建模
本工作流说明了如何写入一组 CGA 规则,以根据真实照片重建立面。 下图显示了要建模的立面:
在继续延伸规则设置时,您将继续对照片进行更详细的分析。 您将学习如何分析现有的真实立面,并将其结构转换为 CGA 语法规则。 您还将学习如何在 CGA 规则中使用预先建模的资产。
创建规则文件
要创建规则文件,请完成以下步骤:
- 单击新建 > CityEngine > CGA 规则文件。
- 确保正确设置了容器 (Tutorial_07_Facade_Modeling/rules),将文件命名为 facade_01.cga,然后单击完成。
体积和立面
现在,您将开始创建建筑物。 首先,通过拉伸操作创建体量模型。 您将使用建筑物高度的属性。
- 将属性高度添加到规则文件的开头。
- 使用 extrude 命令写入初始规则,然后将形状命名为 Building。
- 在此示例中,您仅对立面感兴趣,因此需使用组件分割来移除除正面之外的所有面,然后调用 Frontfacade 规则。
attr height = 24
Lot --> extrude(height) Building
Building --> comp(f) { front : Frontfacade}
楼层
前立面水平分割为多个楼层,每个楼层都有 floor_height 属性。 楼层形状使用 split.index(即楼层索引)进行参数化。 此参数将传递给子规则,以确定要在特定楼层上创建哪些元素。
- 对于顶层,将 floorindex 设置为 999。 这使您可以快速识别该楼层。
- 定义楼层尺寸的一些属性。
- 添加规则。
- 在 3D 视窗中选择地块。
- 单击形状 > 分配规则文件,然后选择 facade_01.cga 规则文件并单击确定。
- 单击顶部工具栏中的生成按钮(或按 Ctrl+G)。
attr groundfloor_height = 5.5
attr floor_height = 4.5
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
楼层窗台
现在,楼层分割为窗台形状和切片形状。 底部窗台应用于特定楼层,因此您将在该规则中使用 floorindex 参数。
- 底层 (floorindex 0) 没有窗台,因此只调用切片。
- 因为窗户从楼板平面开始,所以第二层没有底部窗台。 此楼层的阳台将在之后的步骤中创建。
- 所有其他楼层都有底部窗台,切片和顶部窗台区域。
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}
底层地板
底层地板由左右两边的小型墙体区域以及中间的重复切片组成。
- 在顶部添加以下属性:
- 添加规则:
- 带有脏蚀纹理的深色砖块
- 带有脏蚀纹理的明亮砖块
- 仅脏蚀纹理 - 主要用于没有砖结构的立面资产。
attr tile_width = 3.1
Subfloor(floorindex) -->
split(x){ 0.5 : Wall(1)
| { ~tile_width : Tile(floorindex) }*
| 0.5 : Wall(1) }
attr wallColor = "#ffffff"
Wall(walltype) -->
// dark bricks with dirt
case walltype == 1 :
color(wallColor)
// bright bricks with dirt
case walltype == 2 :
color(wallColor)
// dirt only
else :
color(wallColor)
切片
切片在该立面上是同质的。 您只需要区分底层切片和高层切片。
使用 door_width 和 window_width 属性设置分割尺寸。attr door_width = 2.1
attr window_width = 1.4
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 形状。 这是必要的,因为底层的门是从立面插入的。 为避免门和墙壁之间出现孔洞,您将通过插一个确定厚度的立方体在其中使用实心墙壁元素。 因为您将在稍后的门规则中再次使用此厚度,所以将其定义为 const 变量 wall_inset。 最好对多次使用的值进行声明,因为这样可以确保在不同的规则中使用相同的值。const wall_inset = 0.4
SolidWall -->
s('1,'1,wall_inset) t(0,0,-wall_inset)
i("builtin:cube:notex")
Wall(1)
声明一个函数来从楼层索引中获取墙壁类型。 查看立面,您会发现地面和一楼有深色纹理,其他则有明亮纹理。 getWalltype 函数将楼层索引映射到相应的墙壁类型。getWalltype(floorindex) =
case floorindex == 0 : 1
case floorindex == 1 : 1
else : 2
插入立面资产
接下来,您将学习如何在立面上使用预建模的资产。
- 如果尚未打开 Tutorial_07_Facade_Modeling/scenes/FacadeModeling_02.cej 场景文件,请打开该场景。
- 打开 Tutorial_07_Facade_Modeling/rules/facade_02.cga 文件。
资产
查看您要建模的立面的照片,您会发现您需要以下资产:
- 窗口 - 用于窗口元素
- 圆形窗口顶部 - 用于窗户上方的装饰
- 三角形窗口顶部 - 用于窗口上方的装饰品
- 半弧 - 用于底层上的弧线
- 窗台 - 用于所有窗台
- 飞檐托 - 用于底层的窗户装饰和弧形装饰
这些资产已经存在于教程工程的资产文件夹中。 您可以通过在导航器窗口中选择资产的方式,在 CityEngine 检查器窗口中预览这些资产。
资产声明
将所有资产声明都放在同一位置是是一种很好的做法。 将下列行添加到规则文件的属性声明下方:
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"
Windows
- 您已为窗口资产的准确位置准备好了规则。 在 WindowTile 规则中调用窗口形状。
- 添加以下规则以缩放、定位并在其后面插入窗口资产和玻璃平面:
WindowTile(floorindex) --> Window
Window -->
s('1,'1,0.2) t(0,0,-0.2)
t(0,0,0.02)
[ i(window_asset) Wall(0) ]
Glass
窗口装饰
再次查看立面照片,您会发现在不同的楼层上有不同的窗口(或窗口元素)。
您需要扩展 WindowTile 规则并触发特定于楼层索引的形状,如下所示:
- 一层和顶层没有特殊装饰(索引 1 和 999);因此,仅调用窗口。
- 您将在二层插入其他形状 WindowOrnamentRound。 由于此元素要与窗口的顶部边框对齐,因此您可以将当前的范围沿 y 轴向上平移 '1。
- 其他窗口切片(在中间楼层)具有其他 WindowLedge 形状以及 WindowOrnamentTriangle 装饰,同样沿 y 轴平移。
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
您将首先插入代理立方体,而不是直接使用最终资产。 这样更便于设置实际资产的尺寸。 您可以在这种情况下使用内置的立方体资产。
设置尺寸,将范围置于 x 轴中心,插入立方体,然后为其上色以提高可视性。WindowOrnamentTriangle -->
s('1.7, 1.2, 0.3) center(x) i("builtin:cube") color("#ff0000")
# set dimensions for the triangle window element and insert it
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,请保留立方体)。
WindowOrnamentTriangle -->
s('1.7, 1.2, 0.3) center(x) i(tri_wintop_asset)
WindowOrnamentRound -->
s('1.7, 1.2, 0.4) center(x) i(round_wintop_asset)
二楼的圆形窗口装饰缺少侧柱。 您需要向先前添加的 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 }
设置维度,并插入飞檐托资产。 请注意,通过在 y 方向上应用相对负平移 ('-1),资产的顶部与装饰的底面对齐。
WindowMod -->
s(0.2,'1.3,'0.6) t(0,'-1,0) center(x) i(modillion_asset) Wall(0)
门
门切片垂直分割为门、弧形和弧形顶部区域。
为确保非椭圆弧形,弧形区域的高度需要为门的宽度的一半(当前 x 范围)。
DoorTile -->
split(y){~1 : Door | scope.sx/2 : Arcs | 0.5 : Arctop}
在顶部区域,插入墙壁元素和覆盖的飞檐托资产。
# Adds wall material and a centered modillion
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}
在门顶和门上设置墙壁形状,然后插入实际的弧形资产。
Doortop --> Wall(0)
Door --> t(0,0,-wall_inset) Wall(0)
ArcAsset --> i(halfarc_asset) Wall(1)
窗台
对于窗台,您需要顶部窗台和底部窗台的规则。 顶部窗台使用简单的墙条,底部窗台必须在插入了窗台资产的不同楼层上进行区分。
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)
阳台
现在您将执行阳台操作。
- 使用楼层规则,然后将阳台形状添加到第二层中。
- 先执行一个简单的代理,以确保阳台的位置和尺寸。
- 现在将阳台框分割为以下几部分:梁、地板和扶手。
- 使用重复分割创建支撑阳台的梁。
- 使用在 RailingBox 上分割的组件,提取阳台扶手的必要面。
- 将尺寸插入设置为立方体以创建阳台扶手。
case floorindex == 2 :
split(y){~1 : Subfloor(floorindex) Balcony | 0.5 : TopLedge}
Balcony -->
s('1,2,1) t(0,-0.3,0) i("builtin:cube") color("#99ff55")
Balcony -->
s('1,2,1) t(0,-0.3,0) i("builtin:cube")
split(y){0.2 : BalconyBeams
| 0.3 : BalconyFloor
| 1 : RailingBox }
BalconyBeams -->
split(x){ ~0.4 : s(0.2,'1,'0.9) center(x) Wall(0) }*
BalconyFloor --> Wall(0)
# Get the front, left, and right components (faces) of the RailingBox shape
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)
带有几何资产的最终立面
现在您有了最终的模型。 将规则应用于不同的地块形状,或在检查器窗口中浏览用户属性以修改立面设计。
在下一部分中,您将学习如何将纹理应用于此立面。
对立面进行纹理处理
使用以下技术处理立面纹理:
- 如果尚未打开 Tutorial_07_Facade_Modeling/scenes/FacadeModeling_03.cej 场景文件,请打开该场景。
- 打开 Tutorial_07_Facade_Modeling/rules/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 坐标
使用形状语法进行纹理处理包含以下三个命令:
- setupProjection() - 定义 UV 坐标空间
- set(material.map - 设置纹理文件
- projectUV() - 应用 UV 坐标
您将向立面添加两个纹理图层:砖纹理和脏蚀贴图。 为了在整个立面上保持一致的纹理坐标,您需要将 UV 设置添加到立面规则中。 若要预先测试纹理设置,需新增中间 FrontfacadeTex 规则。
- 将建筑物规则更改为以下内容:
- 创建以下新规则:
- 生成立面以查看 UV 设置。
- 为脏蚀通道添加 UV 设置。
- 再次生成立面以获得以下结果:
- 要查看立面的纹理外观,请将内置的 uvtest 纹理与真实纹理进行交换。
- 对于建筑物,此时您只需要设置 UV,因此请将 FrontfacadeTex 规则更改为以下内容:
Building --> comp(f) { front : FrontfacadeTex}
FrontfacadeTex -->
setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1)
setupProjection(2, scope.xy, '1, '1)
Frontfacade
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)
FrontfacadeTex -->
setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1)
texture(wall_tex)
projectUV(0)
setupssProjection(2, scope.xy, '1, '1)
set(material.dirtmap, dirt_tex)
projectUV(2)
FrontfacadeTex -->
setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1)
setupProjection(2, scope.xy, '1, '1)
Frontfacade
对墙壁进行纹理处理
在此工作流的先前部分,您已在墙壁规则中添加了类型参数。 您现在将使用它来获得三种具有不同纹理的墙壁类型。
- 将墙壁规则更改为以下内容:
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 函数。
- 为玻璃添加镜面光泽。
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 -->
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)