教程 9:高级形状语法

要访问 CityEngine 中的教程,请单击帮助 > 下载教程和示例...。 在选择教程或示例后,系统会自动下载工程并将其添加到工作空间。

复杂的立面模式

本教程介绍了如何根据图片对建筑物进行建模,并介绍了一些更复杂的 CGA 技术。

教程设置

如果尚未打开 ComplexPatterns.cej 场景,请打开该场景。

生成建筑物

本教程说明了如何创建一组 CGA 规则,以使用纯 CGA 根据真实照片重建建筑物。 下图显示了要建模的立面。 由于本示例中的切片和窗口布局的模式很复杂,因此您需要使用一些高级 CGA 机制,例如嵌套重复分割和参数传递。

将使用纯 CGA 重新创建的建筑物照片

生成建筑物的 GCA 透视:

生成建筑物的 CGA 透视

要提前生成建筑物:

  1. 请在 3D 视窗中选择一个地块。
  2. 单击顶部工具条中的生成按钮。

立面分析

规划新的 CGA 规则时,在开始编写规则之前,勾勒出原始布局并定义一些形状名称十分有帮助。

您要建模的立面主要由三种楼层类型组成:顶层、地面和上层。 较低的楼层由包含窗口的切片构成,而顶层仅包含窗口元素。 其他所有较低的楼层都是相同的,因此您将传递带有 Floor 形状的索引 (floorIndex),以为特定地面创建正确的外观(切片和窗口对齐)。

由于切片的模式,您将定义一个中间的 DoubleTile 形状,其中包含两个 Tile 形状,这在对楼层模式进行编码后十分有用。

复杂的模式方案

接下来,您将在切片中定义详细的子形状。 它由两个主要部分组成:MilkGlass 和 Window 形状。 Window 形状在顶部包含一个盲区和一个嵌入的 Subwindow 形状。 这些元素的位置取决于切片在楼层上的水平位置,因此您需要将此位置索引(称为 tileIndex)存储为 Tile 形状的参数,以便能够正确放置子形状结构。

切片中详细的子形状

双击导航器中的 complexpatterns_01.cga 文件,打开 CGA 编辑器,并查看创建立面的规则。

属性、变量和资产

属性在规则文件的开头定义。 这些属性在整个规则设置中被使用,并且可以通过 CGA 语法编辑器外部的 Windows > Inspector 进行修改。

// User Attribute 

@Group("Building", 1)
@Range(min=5, max=40, restricted=false) @Distance
attr buildingH = 27 // building height

@Group("Facade", 2)
@Range(min=3, max=6, restricted=false) @Distance
attr floorH = 3.5 // floor height
@Range(min=3, max=6, restricted=false) @Distance
attr groundfloorH = floorH + 1 // groundfloor height
@Range(min=1, max=4, stepsize=1, restricted=false)
attr nSymmetries = 2
@Range(min=0.1, max=1, restricted=false) @Distance
attr borderwallW = 0.3 // width of border wall stripe
@Range(min=0.1, max=0.8, restricted=false) @Distance
attr ledgeH = 0.3 // ledge height

@Group("Window", 3)
@Range(min=1, max=5, restricted=false) @Distance
attr windowW = 2.5 // window width
@Range(min=1, max=5, restricted=false) @Distance
attr milkGlassW = windowW/2 // milkglass blend width
@Range(min=0.1, max=2.5, restricted=false) @Distance
attr blindH = 0.8 // blind height
@Range(min=0.01, max=0.5, restricted=false) @Distance
attr frameW = 0.07 // frame width

@Group("Balcony", 4)
@Range(min=3, max=6, restricted=false) @Distance
attr balconyDepth = 2

@Group("Colors", 5)
@Color
attr brightblue = "#86B1C7" 
@Color
attr darkblue = "#33556C"
@Color
attr red = "#5C3F40"
@Color
attr grey ="#6B7785"
@Color
attr white = "#FFFFFF"

将诸如 @Group 或 @Range 的注记添加到属性中,以控制其在检查器中的外观。

检查器窗口显示 Shape Lot 1012 规则

其他变量和资产在以下块中予以定义:

tileW = windowW + milkGlassW	// total tile width
const barDiameter = 0.04

// assets
const cyl_v = "general/primitives/cylinder.vert.8.notop.tex.obj"
const cyl_h = "general/primitives/cylinder.hor.8.notop.tex.obj"
const window_tex = "facade/windows/1_glass_2_blue.tif"
const milkGlass_tex = "facade/windows/blend_tex.png"

该建筑物的实际创建从现在开始。 首先,通过拉伸操作创建体量模型。 将顶层与主体分割,然后再次分割以创建倒退阳台。

Lot --> 
	extrude(buildingH)  // Extrude the building
	split(y){ ~1: MainPart | floorH: UpperPart }  // Split top floor from lower floors

// Create a set-back by splitting in the direction of the building depth
UpperPart --> 
	split(z){ ~1: TopFloor | balconyDepth: Balcony }

然后将组件分割应用于不同的体积部分,以区分正面面、侧面和顶面,并触发立面、墙面和屋顶规则。

// Create a facade on the front face, walls on the side faces, and a roof on the top face
MainPart --> 
	comp(f){ front: Facade | side: Wall | top: Roof. }  

// Create a floor (marked with -1 as top floor) on the front face, 
   walls on the side and roof on the top face
TopFloor --> 
	comp(f){ front: Floor(-1) | side: Wall | top: Roof. }

已设置阳台的尺寸。 扶手将放置在当前形状的表面上,因此您将使用组件分割来获取扶手规则的前面、左面和右面。

// Set balcony height to 0.7 meters (railing height)
Balcony -->
	s(scope.sx-2*borderwallW,0.7,scope.sz-borderwallW)
 center(x) 
	comp(f){ front: Railing | left: Railing | right: Railing }

体积建模后的原始建筑物形状:

体积建模后的原始建筑物形状

立面和楼层

现在,您将进一步细分正立面。 首次分割借助重复分割 {...}* 将立面细分为底层部分和一组较高的楼层。 分割大小前的波形符号 (~)(例如 ~groundfloorH)允许高度灵活,并确保匹配的楼层在立面上没有孔。 通过传递 split.index(代表楼层索引)作为参数,您之后可以触发特定楼层要素。

// Split the facade into a groundfloor and repeated upper floors
Facade -->
         split(y){ ~groundfloorH: Floor(split.index) 
// (all floors are marked with their split index, which represents the floor number)
                 | {     ~floorH: Floor(split.index) }* }

每个楼层的左边界和右边界都有狭窄的墙壁区域。 您将通过在 x 方向上进行简单的分割进行创建。

// create a narrow wall element on both sides of every floor.
Floor(floorIndex) --> 
//the floorIndex parameter is passed on to be used later
	split(x){borderwallW: Wall | ~1: FloorSub(floorIndex) | borderwallW: Wall }

根据楼层索引,现在使用水平分割命令为每个楼层创建特殊水平元素:

  • 较高的楼层仅有顶部窗台。
  • 顶层没有其他元素,可以直接触发 TileRow 形状。
  • 您将在以后的规则中再次使用楼层索引,因此它将再次作为 TileRow 形状的参数。
FloorSub(floorIndex) -->	
	case floorIndex == 0:      // ground floor with index 0.    
		split(y){ 1: Wall | ~1: TileRow(floorIndex) | ledgeH: Wall}  
	case floorIndex > 0:      	// upper floors
		split(y){ ~1: TileRow(floorIndex) | ledgeH: Ledge }
	else: TileRow(floorIndex) 	// topfloor with index -1.

楼层和窗台分割的立面:

楼层和窗台分割的立面

切片

现在您要将楼层分割为切片。 对于顶层而言,这相对容易。无特殊的模式,仅重复窗口元素。 要稍后处理这些切片,请将这些切片标记为参数 -1。

要为主要楼层创建特殊的重复模式,您将创建一个称为 DoubleTile 的中间形状。 要在后面的步骤中正确地对齐窗口元素,您需要将楼层和切片索引 (split.index) 作为参数传递。

TileRow(floorIndex) --> 
	case floorIndex == -1:
		split(x){ ~windowW: Tile(-1) }*
// Repeating shape Tiles on the top floor, marked again with -1
	else: 
		split(x){ ~tileW*nSymmetries: DoubleTile(floorIndex,split.index) }* 
// the floor is subdivided into regular DoubleTile shapes, 
// the floor index is passed as parameter

楼层和切片索引的组合确定了窗口的对齐方式。 因此,您有两个规则,可以按不同顺序重复分割 MilkGlass 和 Tile 形状。

DoubleTile(floorIndex,tileIndex) -->    
	case tileIndex%2 + floorIndex%2 == 1: 
// windows are right-aligned
		split(x){ ~milkGlassW: MilkGlass | ~windowW: Tile(tileIndex) }* 
	else:
// windows are left-aligned 
		split(x){ ~windowW: Tile(tileIndex) | ~milkGlassW: MilkGlass }*

您将首先为将来的窗口纹理设置纹理坐标。 然后将整个 Tile 形状水平分割为窗框和中间部分。 再次分割中心部分,这次是垂直分割为框架、窗口、框架、百叶窗和框架。

Tile(tileIndex) -->
    setupProjection(0,scope.xy,scope.sx,scope.sy)
// Set up the texture coordinates for the windows
    split(x){ frameW: Frame Bracing
// This triggers the window frame as well as the bracing on the left side of the window
// the center window is split into Frame, Window, Frame, Blind and Frame from bottom to top
            |     ~1: split(y){  frameW : Frame
                              |      ~1 : Window(tileIndex)
                              |  frameW : Frame
                              |  blindH : Blind 
// frame and bracing on the window's right side
                              |  frameW : Frame }
            | frameW: Frame Bracing }

建筑物被分割为窗框和垂直中心部分,形成框架、窗户,百叶窗和支撑

窗户

对于窗户形状,DoubleTile 的切片索引用于确定子窗口的位置。

Window (tileIndex) -->

放置在窗口左半部分的右对齐子窗口。

case tileIndex%nSymmetries >= 1:  // the Subwindows are aligned depending 
                                  // on the DoubleTile position 		
     split(x){     ~1 : Subwindow("right") 
             | frameW : Frame 
             |     ~1 : Glass     } 	// right-aligned in the left half of the window

放置在窗口右半部分的左对齐子窗口。

case tileIndex%nSymmetries >= 0:
     split(x){     ~1: Glass
             | frameW: Frame 
// left-aligned in the right half of the window
             |     ~1: Subwindow("left") }

代表顶层窗口的切片索引 -1 现在用于创建不带子窗口的窗口。

else:
	split(x){ ~1: Glass | frameW: Frame | ~1: Glass}

使用左参数和右参数,将 RedWindow 放置在正确的位置。

Subwindow(align) -->
	case align == "left": 
		split(x){~3: RedWindow | ~2: Glass}	  // Put the RedWindow to the left
	else: 
		split(x){~2: Glass     | ~3: RedWindow }	// And to the right otherwise

以下规则为 RedWindow 形状创建框架和玻璃元素:

RedWindow -->
   split(x){ frameW: RedFrame   // left...
           |     ~1: split(y){ frameW: RedFrame
                             |     ~1: RedGlass
                             | frameW: RedFrame }  // ... bottom, top ...
           | frameW: RedFrame }	// ... and right frame

RedGlass -->
   split(y){       ~1: Glass
           | frameW/2: t(0,0,-frameW) Frame
           |       ~1: t(0,0,-frameW) Glass }

RedWindow 形状的详细窗口几何:

RedWindow 形状的详细窗口几何

材料

添加了颜色和纹理。

Wall --> color(darkblue)

Blind --> color(grey)

Frame --> 
	extrude(frameW) color(white)	// extrude the frame to the front

RedFrame --> 
	t(0,0,-frameW) extrude(frameW*4) color(red)

Glass --> 
	projectUV(0)  // apply texture coordinates to current shape geometry
	texture(window_tex) color(white) // and assign texture and color
	set(material.specular.r, 0.4)
 set(material.specular.g, 0.4)
 set(material.specular.b, 0.4)
	set(material.shininess, 4)
	set(material.reflectivity,0.3)
	
MilkGlass --> 
	s('1,'1,frameW*1.2) i("builtin:cube") 
	color(brightblue)
	setupProjection(0, scope.xy, scope.sx, scope.sy, 0, 0, 0)
 texture(milkGlass_tex)
 projectUV(0)
	set(material.specular.r, 0.7)
 set(material.specular.g, 0.7)
 set(material.specular.b, 0.7)
	set(material.shininess, 20)
 set(material.reflectivity,0.05)

应用材料规则后的彩色和纹理模型:

应用材料规则后的彩色和纹理模型

细节元素

您将通过添加后墙元素,具有深度的立方体和第二个作为盖板的薄立方体来优化楼层窗台。

Ledge --> 
	Wall
	[ s('1,'0.9,0.2) i("builtin:cube") Wall ]	
	t(0,-0.1,0.2) s('1,scope.sy+0.1,0.03) i("builtin:cube") Wall

插入了水平条块以创建扶手的水平部分。 通过禁用垂直修剪,可以防止下面的垂直角条块被切割。

通过使用浮动分割宽度进行重复分割,垂直条块可均匀分布。

Railing --> 
	[ t(0,scope.sy-barDiameter/2,0) HBar ]
	set(trim.vertical, false)
	split(x){ ~tileW/3:  VBar }*

插入圆柱资产以创建垂直条块和水平条块。

VBar --> s(barDiameter,'1,barDiameter) t(0,0,-barDiameter) i(cyl_v) color(white)
HBar --> s('1,barDiameter,barDiameter) t(0,0,-barDiameter) i(cyl_h) color(white)

窗口的支撑由顶部和底部固定装置以及中间的垂直条块组成。 对于固定装置,将插入一个立方体,然后 VBar 将再次触发圆柱资产。

Bracing --> 
	s(barDiameter,'1,0.15) center(x) i("builtin:cube")
split(y){ 0.01: Wall | ~1: t(0,0,0.15) VBar | 0.01: Wall }

添加了具有细节元素的最终模型,包括窗台、窗撑和扶手:

细节元素的最终模型

现在您已经有了最终模型,请继续将规则应用于不同的地块形状,或者尝试使用检查器中的用户属性来修改立面设计。

不同样式

使用样式关键字,定义了一种重新定义某些属性的新样式。 在这种情况下,您将通过重新定义颜色属性来创建颜色变化。

@Description("A Variation in Red")
style Red_Color_Theme
attr brightblue = "#FF8080"
attr darkblue = "#D20000"
attr grey = "#ECCACA"
attr red = "#361B1B"

应用了 Red_Color_Theme 样式:

使用样式关键字“红色”进行构建

下面的坎德勒和巴台农 CityEngine 场景是很好的示例,向您展示了 CGA 的不同可能性。

坎德勒大楼

  1. 打开 Candler Building.cej 场景。
  2. 在导航器中双击 Candler Building.cga 文件,然后浏览创建 Candler 建筑物的 CGA 规则。

坎德勒大楼

巴台农神庙

  1. 打开 Parthenon.cej 场景。
  2. 再次双击 parthenon.cga 文件以查看巴台农神庙背后的规则。
巴台农神庙