要访问教程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,然后单击Finish。
体积和立面
建筑的实际创建现在开始。首先,使用拉伸操作创建质量模型。您将使用建筑物高度的属性。
-
- 将属性高度添加到规则文件的开头。
attr height = 24
-
- 使用 extrude 命令编写起始规则,并调用形状 Building。
Lot --> extrude(height) Building
-
- 对于本示例,您只对立面感兴趣,因此您将使用组件拆分来移除除正面之外的所有面,然后调用 Frontfacade 规则。
Building --> comp(f) { front : Frontfacade}
楼层
正面被水平分割成不同的楼层,每个楼层都有一个 floor_height 属性。地板形状使用 split.index 参数化,这是地板索引。此参数将传递给子规则以确定要在特定楼层上创建哪些元素。
-
- 对于顶层,floorindex 设置为 999。这使您可以轻松识别该楼层。
-
- 定义地板尺寸的一些属性。
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
-
- 在 3D 视口中选择地块。
- 通过单击“形状” >“分配规则文件”来分配规则文件,选择您的facade_01.cga规则文件并单击“确定”。
- 单击顶部工具栏上的生成按钮(或按Ctrl-G)。
地板壁架
地板现在分为壁架和瓷砖形状。底部壁架特定于地板,因此您将使用此规则的 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 形状。这是必要的,因为一楼的门是从立面插入的。为避免门和墙之间出现孔洞,您将通过插入具有定义厚度的立方体来使用实心墙元素。因为您将在稍后的 Door 规则中再次使用此厚度,所以您将其定义为 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 文件。
资产
查看您要建模的立面照片,您会发现您需要以下资产:
- Window—用于窗口元素
- 圆形窗台——用于窗户上方的装饰品
- 三角窗台——用于窗户上方的装饰品
- 半弧——用于底层的弧
- 壁架 – 用于所有壁架
- Modillion—用于底层的窗饰和弧形饰物
这些资产已经存在于教程项目的资产文件夹中。您可以通过在导航器中选择所需的资产,在 CityEngine Inspector 中预览这些资产。
资产申报
将所有资产声明放在同一位置是个好主意。将以下行添加到您的规则文件的属性声明下方:
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 形状。
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);因此,只有 Window 被调用。
- 在二楼,您将插入一个额外的 Shape WindowOrnamentRound。由于此元素将与窗口的顶部边框对齐,因此您将当前 Scope 向上平移到 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 规则添加一个新的拆分命令。此行将为以下 modillion 资产准备范围。
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 和 Door 上设置 Wall 形状,并插入实际的弧资产。
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)
阳台
现在你将在阳台上工作。
-
- 使用楼层规则,并将阳台形状添加到二楼案例。
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)
带有几何资源的最终立面
现在你有了最终的模型。将规则应用于不同的地块形状,或在 Inspector 中探索用户属性以修改您的外观设计。
在下一节中,您将学习如何将纹理应用到这个立面。
纹理立面
本节展示了纹理化立面的技巧。
- 如果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 规则。
-
- 将建筑规则更改为以下内容:
Building --> comp(f) { front : FrontfacadeTex}
-
- 创建以下新规则:
FrontfacadeTex -->
setupProjection(0, scope.xy, 2.25, 1.5, 1)
setupProjection(2, scope.xy, '1, '1)
Frontfacade
-
- 生成外观以查看 UV 设置。
- 添加污垢通道的 UV 设置。
- 生成外观以查看 UV 设置。
FrontfacadeTex -->
setupProjection(0, scope.xy, 2.25, 1.5, 1)
texture("builtin:uvtest.png")
projectUV(0)
setupProjection(2, scope.xy, '1, '1)
set(material.dirtmap, "builtin:uvtest.png")
projectUV(2)
-
- 再次生成门面,得到如下结果:
- 要查看外观与实际纹理的外观,请将内置 uvtest 纹理与真实纹理交换。
- 再次生成门面,得到如下结果:
FrontfacadeTex -->
setupProjection(0, scope.xy, 2.25, 1.5, 1)
texture(wall_tex)
projectUV(0)
setupUProjection(2, scope.xy, '1, '1)
set(material.dirtmap, dirt_tex)
projectUV(2)
-
- 对于实际的建筑,此时您只需要设置 UV,因此将 FrontfacadeTex 规则更改为以下内容:
FrontfacadeTex -->
setupProjection(0, scope.xy, 2.25, 1.5, 1)
setupProjection(2, scope.xy, '1, '1)
Frontfacade
墙壁的纹理
回想一下,您向 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 函数。
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)