教程 7:立面建模

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

对立面结构建模

本教程介绍了如何根据图片对建筑物进行建模,并介绍了一些更复杂的 CGA 技术。 在本部分中,您将使用 CGA 规则创建立面的基本结构。

设置

要设置教程,请完成以下步骤:

  1. 将 Tutorial_07_Facade_Modeling 工程导入 CityEngine 工作空间。
  2. 打开 Tutorial_07_Facade_Modeling/scenes/FacadeModeling_01.cej 场景。

立面建模

本工作流说明了如何写入一组 CGA 规则,以根据真实照片重建立面。 下图显示了要建模的立面:

要建模的立面照片

在继续延伸规则设置时,您将继续对照片进行更详细的分析。 您将学习如何分析现有的真实立面,并将其结构转换为 CGA 语法规则。 您还将学习如何在 CGA 规则中使用预先建模的资产。

创建规则文件

要创建规则文件,请完成以下步骤:

  1. 单击新建 > CityEngine > CGA 规则文件
  2. 确保正确设置了容器 (Tutorial_07_Facade_Modeling/rules),将文件命名为 facade_01.cga,然后单击完成
  3. 系统随即创建一个新的 CGA 文件,并显示 CGA 编辑器窗口。 除了含有某些标题信息 (/* .... */) 和版本标签 "2011.1" 外,该文件为空。

体积和立面

现在,您将开始创建建筑物。 首先,通过拉伸操作创建体量模型。 您将使用建筑物高度的属性。

  1. 将属性高度添加到规则文件的开头。
  2. attr height 			= 24
  3. 使用 extrude 命令写入初始规则,然后将形状命名为 Building
  4. Lot --> extrude(height) Building
  5. 在此示例中,您仅对立面感兴趣,因此需使用组件分割来移除除正面之外的所有面,然后调用 Frontfacade 规则。
  6. Building --> comp(f) { front : Frontfacade}
    属性的注记可选(例如 @Group 或 @Range),用于控制属性在检查器窗口中的显示。 有关 CGA 注记的详细信息,请参阅 CGA 参考。

楼层

前立面水平分割为多个楼层,每个楼层都有 floor_height 属性。 楼层形状使用 split.index(即楼层索引)进行参数化。 此参数将传递给子规则,以确定要在特定楼层上创建哪些元素。

立面水平分割成多个楼层
  1. 对于顶层,将 floorindex 设置为 999。 这使您可以快速识别该楼层。
  2. 请注意中间楼层的重复分割。 这使建筑物可以动态适应不同的建筑物高度,并用中层楼层填充剩余的垂直空间。
  3. 定义楼层尺寸的一些属性。
  4. attr groundfloor_height 	= 5.5
    attr floor_height 		= 4.5
  5. 添加规则。
  6. 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
    现在,您将首次生成立面。
  7. 在 3D 视窗中选择地块。
  8. 单击形状 > 分配规则文件,然后选择 facade_01.cga 规则文件并单击确定
  9. 单击顶部工具栏中的生成按钮(或按 Ctrl+G)。
  10. 结果看起来类似于下图:
    生成的立面

楼层窗台

现在,楼层分割为窗台形状和切片形状。 底部窗台应用于特定楼层,因此您将在该规则中使用 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}
生成的楼层窗台

底层地板

底层地板由左右两边的小型墙体区域以及中间的重复切片组成。

立面显示底层地板
  1. 在顶部添加以下属性:
  2.  attr tile_width 		= 3.1
  3. 添加规则:
  4. 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_widthwindow_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
生成的切片

插入立面资产

接下来,您将学习如何在立面上使用预建模的资产。

  1. 如果尚未打开 Tutorial_07_Facade_Modeling/scenes/FacadeModeling_02.cej 场景文件,请打开该场景。
  2. 打开 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

  1. 您已为窗口资产的准确位置准备好了规则。 在 WindowTile 规则中调用窗口形状。
  2. WindowTile(floorindex) --> Window
  3. 添加以下规则以缩放、定位并在其后面插入窗口资产和玻璃平面:
  4. 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)
生成的窗台
生成的窗台的特写视图

阳台

现在您将执行阳台操作。

  1. 使用楼层规则,然后将阳台形状添加到第二层中。
  2. case floorindex == 2 : 
    		split(y){~1 : Subfloor(floorindex) Balcony | 0.5 : TopLedge}
  3. 先执行一个简单的代理,以确保阳台的位置和尺寸。
  4. Balcony --> 
    	s('1,2,1) t(0,-0.3,0) i("builtin:cube") color("#99ff55")
    生成的阳台
  5. 现在将阳台框分割为以下几部分:梁、地板和扶手。
    立面显示阳台横梁、地板和扶手框
  6.  Balcony --> 
    	s('1,2,1) t(0,-0.3,0) i("builtin:cube") 
    	split(y){0.2 : BalconyBeams 
    			| 0.3 : BalconyFloor 
    			| 1 : RailingBox }
    生成的阳台显示梁、地板和扶手框
  7. 使用重复分割创建支撑阳台的梁。
  8. BalconyBeams -->			
    	split(x){ ~0.4 : s(0.2,'1,'0.9) center(x)  Wall(0) }*
    BalconyFloor 形状仅触发墙壁规则。
    BalconyFloor --> Wall(0)
  9. 使用在 RailingBox 上分割的组件,提取阳台扶手的必要面。
  10. # Get the front, left, and right components (faces) of the RailingBox shape
    RailingBox -->
    	comp(f){front : Rail | left : Rail | right : Rail}
    生成的阳台显示梁、地板和扶手框
  11. 将尺寸插入设置为立方体以创建阳台扶手。
  12. Rail --> 
    	 s('1.1,'1,0.1) t(0,0,-0.1) center(x) i("builtin:cube") Wall(0)

带有几何资产的最终立面

现在您有了最终的模型。 将规则应用于不同的地块形状,或在检查器窗口中浏览用户属性以修改立面设计。

生成的最终立面
最终立面

在下一部分中,您将学习如何将纹理应用于此立面。

对立面进行纹理处理

使用以下技术处理立面纹理:

  1. 如果尚未打开 Tutorial_07_Facade_Modeling/scenes/FacadeModeling_03.cej 场景文件,请打开该场景。
  2. 打开 Tutorial_07_Facade_Modeling/rules/facade_03.cga 文件。

纹理资产

  1. 在规则文件的顶部,添加要用作属性的纹理。
  2. 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"
  3. 对于窗口和门的纹理,可使用函数来获取纹理字符串,因此无需单独列出纹理。
  4. randomWindowTex = fileRandom("*facades/textures/window.*.jpg")
    randomDoorTex = fileRandom("*facades/textures/doortex.*.jpg")

设置全局 UV 坐标

使用形状语法进行纹理处理包含以下三个命令:

  • setupProjection() - 定义 UV 坐标空间
  • set(material.map - 设置纹理文件
  • projectUV() - 应用 UV 坐标

您将向立面添加两个纹理图层:砖纹理和脏蚀贴图。 为了在整个立面上保持一致的纹理坐标,您需要将 UV 设置添加到立面规则中。 若要预先测试纹理设置,需新增中间 FrontfacadeTex 规则。

  1. 将建筑物规则更改为以下内容:
  2. Building --> comp(f) { front : FrontfacadeTex}
  3. 创建以下新规则:
  4. FrontfacadeTex -->
    	setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1) 	
    	setupProjection(2, scope.xy, '1, '1) 	
    	Frontfacade
    setupProjection(0, scope.xy, 2.25, 1.5, 0, 0, 1) 定义纹理通道 0(颜色通道)的纹理坐标。 UV 坐标将沿着范围的 xy 平面投影,并在 x 方向上每 2.25 单位和 y 方向上每 1.5 单位重复一次。 将色彩映射表设置为 builtin:uvtest.png,这是一种可以快速检查 UV 坐标的纹理。 然后通过烘烤通道 0 的 UV 坐标来应用 UV 坐标。
  5. 生成立面以查看 UV 设置。
    生成的立面 UV 设置
  6. 为脏蚀通道添加 UV 设置。
  7. 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 设置。
  8. 再次生成立面以获得以下结果:
    生成的立面 UV 设置
  9. 要查看立面的纹理外观,请将内置的 uvtest 纹理与真实纹理进行交换。
  10. 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)
    立面纹理
    UV 坐标适用于立面。
  11. 对于建筑物,此时您只需要设置 UV,因此请将 FrontfacadeTex 规则更改为以下内容:
  12. FrontfacadeTex -->
    	setupProjection(0, scope.xy, 2.25, 1.5,  0, 0, 1) 		
    	setupProjection(2, scope.xy, '1, '1) 	
    	Frontfacade
    现在,旧的 Frontfacade 规则已为后续元素正确设置了 UV 坐标。

对墙壁进行纹理处理

在此工作流的先前部分,您已在墙壁规则中添加了类型参数。 您现在将使用它来获得三种具有不同纹理的墙壁类型。

  1. 将墙壁规则更改为以下内容:
  2. 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 来执行此操作。

  1. 调用您先前为纹理调用定义的 randomWindowTex 函数。
  2. Glass -->
    	setupProjection(0,scope.xy, '1, '1)
    	projectUV(0)
    	texture(randomWindowTex)
  3. 为玻璃添加镜面光泽。
  4. 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)
已对窗口资产和门形状进行纹理处理