CityEngine中街道和建筑的构建

这一节介绍一下相关模型的构建。

上一节说过,CityEngine中,基本的几何图元是Node、Segement、Block、Shape,其中Shape是可以赋予规则文件的多边形。



上面是街道Shape的一个例子。CityEngine中可以为每个Shape赋予一个规则文件,然后按照规则文件构建模型,下方的StartRule是起始的规则,可以理解为其它编程语言中的main函数,是个入口,类推的话,我们所编写的所有规则都可以看作是一个函数,只不过CityEngine中这些规则是按照树结构连接在一起的,CityEngine中有一个功能窗口可以显示这种结构,打开Model Hierarchy后,选中生成的模型,点击Inspect model即可:



上面是我生成一个街道的规则树,,点击每一个部分都会展示执行当前规则后会有什么效果,要说类似的功能的话大概是Unity中的帧调试器。

说这么多,下面介绍一下街道的规则文件编写。在任意一个项目文件夹下创建一个CGA文件后,默认会有注释,以及当前cityengine版本的关键字标识:

/**
 * File:    Street.cga
 * Created: 24 Apr 2020 15:44:19 GMT
 * Author:  Dragonboy
 */   

version "2019.0"   

接着cityengine中的attr关键字可以定义变量,这里我定义一些纹理以及模型的路径变量:

//road
attr color_tex = "images/Asphalt003_2K-JPG/Asphalt003_2K_Color.jpg"
attr normal_tex = "images/Asphalt003_2K-JPG/Asphalt003_2K_Normal.jpg"
attr roughness_tex = "images/Asphalt003_2K-JPG/Asphalt003_2K_Roughness.jpg"
attr occlusion_tex = "images/Asphalt003_2K-JPG/Asphalt003_2K_AmbientOcclusion.jpg"

//road line
attr roadline_color = "images/RoadLines007_2K-JPG/RoadLines007_2K_Color.jpg"
attr roadline_normal = "images/RoadLines007_2K-JPG/RoadLines007_2K_Normal.jpg"
attr roadline_roughness = "images/RoadLines007_2K-JPG/RoadLines007_2K_Roughness.jpg"
attr roadline_opacity = "images/RoadLines007_2K-JPG/RoadLines007_2K_Opacity.jpg"

//cross line
attr crossline_color = "images/RoadLines003_2K-JPG/RoadLines003_2K_Color.jpg"
attr crossline_normal = "images/RoadLines003_2K-JPG/RoadLines003_2K_Normal.jpg"
attr crossline_roughness = "images/RoadLines003_2K-JPG/RoadLines003_2K_Roughness.jpg"
attr crossline_opacity = "images/RoadLines003_2K-JPG/RoadLines003_2K_Opacity.jpg"

//busstop
attr busStopOBJ = "assets/BusStop.obj"

//lamp
attr lampOBJ = "assets/lamp.obj"

//fireHydrant
attr fireHydrantOBJ = "assets/fire_hydrant/fire_hydrant.obj"

路径是相对项目主文件夹的。

接着const关键字和C++类似,用于定义不可改变的只读变量,这里我定义一下模型的大小:

const width  = assetInfo("assets/lamp.obj", sx)
const height = assetInfo("assets/lamp.obj", sy)
const depth  = assetInfo("assets/lamp.obj", sz)

const busStopWidth = assetInfo("assets/BusStop.obj", sx)
const busStopheight = assetInfo("assets/BusStop.obj", sx)
const busStopdepth = assetInfo("assets/BusStop.obj", sx)

assetInfo可以读取第一个参数中路径对应模型的三维大小,或者是图片的宽高。三个轴向分别对应sx,sy,sz

这里只是进行展示而已,建议是在规则编写时需要什么变量再去定义,不要一股脑地写在文件最前面。

然后,规则文件中一个规则的构成是:

Rule -->
    something

-->标识,回车和制表符并不是必须的,只是个习惯而已。

本质上,规则文件中规则编写时排列的顺序并不是很重要,因为上面说过,他们按照树状结构按需调用,并从起始规则开始,不过为了直观,这里从起始规则开始编写:

Street -->
    SetUpTexture
    Split

我先初始化纹理投影,并投射纹理,接着对街道进行分割,两个规则对应:

// Texture road 
SetUpTexture -->
    setupProjection(0, scope.zx, scope.sz/50, scope.sx/50)
    setupProjection(5, scope.zx, scope.sz/50, scope.sx/50)
    setupProjection(7, scope.zx, scope.sz/50, scope.sx/50)
    setupProjection(8, scope.zx, scope.sz/50, scope.sx/50)
    set(material.colormap, color_tex)
    projectUV(0)
    set(material.normalmap, normal_tex)
    projectUV(5)
    set(material.occlusionmap, occlusion_tex)
    projectUV(7)
    set(material.roughnessmap, roughness_tex)
    projectUV(8)

// InnerRect    
Split -->
    comp(f){
        all : InnerRect
    }

setupProjection()可以初始化UV投影,第一个参数是纹理通道,CityEngine中有10个通道:


可以用来完成大部分已知的效果。第二个参数是UV展开的方向,scope是一个基本图元的Bounding box,大部分时候是贴合图元的,即可以认为沿街道方向是x轴,穿过街道为z轴,指向上方是y轴(不一定),按照右旋规则去判断。很明显,这里沿zx展开UV。第三、四个参数是每个纹理单元的大小,设置为对应数量后,会根据shape整体的大小去平铺纹理单元UV。

set方法用于设定属性,这里是material属性,还可以设定属性很多,可以查看帮助文档。material本身一共有10个成员,这在上面的图中显示了,对应10个纹理通道。

projectUV方法可以将纹理映射到对应的纹理通道上。

Split规则中的comp函数可以分割点、边、面、线面等,将它们分离为单个基本体或合并,主要是用来整体使用某一规则或单独使用某一规则。这个函数特别强大,参数也很多,这里就不一一距离。comp(f)即分割面,之后用{}括起,里面为每个分离的基本体执行某一操作,这里的all表明所有的分离面合并,执行相同的操作,在:后写明。

InnerRect:

InnerRect -->
    case geometry.nEdges < 5:
        case geometry.area > 200:
            innerRectangle(edge){
                shape : Primitive |
                remainder : NIL
            }
        else : NIL
    else : NIL

InnerRect用来插入四边形。因为如果不太懂城市规划的话,设计出来的道路可能有些不太符合实际(外行人应该看不出来),还有其它因素,造成道路shape在连接node处的分段数可能特别多,且面积小,如:



我之所以要插入四边形,就是要忽略这些区域的影响。

因为执行过comp,所以每个分段的多边形就是一个独立的个体了,只不过all会让它们共享邻边,那么每个单独的区域或执行一个InnerRect,这就有点像图形学里每个片段会调用一次片元着色器一样。

case不用多说,大家在许多语言中见过,只不过规则文件的语法中没有if-else结构,只有case-else结构,所以不要搞混了。内置的属性geometry.nEdges是当前几何体的边数量,小于5来筛选掉一些几何体,然后geometry.area是当前几何体的面积,200是我在自己的场景测试的一个值。不符合条件的使用NIL,这是不执行任何操作,或者剔除掉当前几何体的意思(如果该几何体之前没有做过纹理投射操作)。

InnerRectangle函数可以在当前几何体中分段出最大的一个四边形,有两种参数,edge表明会根据最长边生成,scope表明会根据当前几何体的scope生成。生成完分化为两部分,shape是生成的最大四边形,remainder是剩余部分,注意,不同的部分间用|隔开。

生成的最大四边形会执行Primitive规则:

// Primtive to set road line    
Primitive -->       
    alignScopeToGeometry(yUp, 0, 0) 
    t(0,0.005,0)
    SetRoadLine
    InsertSplit 

因为生成的最大四边形scope轴向可能会有些混乱,尤其是我的街道本身的scope就有些不对头,所以这相当于是些修正命令。

alignScopeToGeometry函数可以修改当前scope轴向,第一个参数选择指向上的轴向,这里是yUp,第二个参数是一个面索引,第三个参数是一个边索引,该边会成为新的x轴,之所以填0是我试出来的。

之后向上平移一点,因为生成的四边形是用来贴上道路线的,不这么做的话可能会造成Z-Fighting的现象,深度测试混乱。

接着设置道路线和插入路灯等模型。

SetRoadLine:

SetRoadLine -->
    case scope.sx > 20:
        case scope.sz >49:
            setupProjection(0, scope.zx, scope.sz/6, scope.sx/7)
            setupProjection(4, scope.zx, scope.sz/6, scope.sx/7)
            setupProjection(5, scope.zx, scope.sz/6, scope.sx/7)
            setupProjection(8, scope.zx, scope.sz/6, scope.sx/7)
            SplitTex    
        else :
            setupProjection(0, scope.zx, scope.sz, scope.sz)
            setupProjection(4, scope.zx, scope.sz, scope.sz)
            setupProjection(5, scope.zx, scope.sz, scope.sz)
            setupProjection(8, scope.zx, scope.sz, scope.sz)
            SplitTex
    else:NIL

这里的一些条件语句主要是为了防止贴图颠倒,具体情况是街道长度和宽度定。

设置道路线的一系列规则

SplitTex -->
    split(x){
        3:TexCrossline|
        0.5:TexStopline|
        ~1:TextureRoadLine|
        0.5:TexStopline|
        3:TexCrossline
    }
    
TexCrossline -->
    setupProjection(0, scope.zx, scope.sx/3, scope.sz)
    setupProjection(4, scope.zx, scope.sx/3, scope.sz)
    setupProjection(5, scope.zx, scope.sx/3, scope.sz)
    setupProjection(8, scope.zx, scope.sx/3, scope.sz)
    set(material.colormap, crossline_color)
    projectUV(0)
    set(material.opacitymap, crossline_opacity)
    projectUV(4)
    set(material.normalmap, crossline_normal)
    projectUV(5)
    set(material.roughnessmap, crossline_roughness)
    projectUV(8)
    
    
TexStopline -->
    setupProjection(0, scope.xz, scope.sx/2, scope.sz)
    setupProjection(4, scope.xz, scope.sx/2, scope.sz)
    setupProjection(5, scope.xz, scope.sx/2, scope.sz)
    setupProjection(8, scope.xz, scope.sx/2, scope.sz)
    set(material.colormap, crossline_color)
    projectUV(0)
    set(material.opacitymap, crossline_opacity)
    projectUV(4)
    set(material.normalmap, crossline_normal)
    projectUV(5)
    set(material.roughnessmap, crossline_roughness)
    projectUV(8)

TextureRoadLine -->
    set(material.colormap, roadline_color)
    projectUV(0)
    set(material.opacitymap, roadline_opacity)
    projectUV(4)
    set(material.normalmap, roadline_normal)
    projectUV(5)
    set(material.roughnessmap, roadline_roughness)
    projectUV(8)

值得关注的是split函数,它会将当前几何体沿某一个轴向分段,这里是x轴,分配的数字或数值变量有3种操作:

  • 什么符号都不加的话,就是以meter为单位分割一段,不够就不分割或要剩下的,是个绝对的值。
  • 加上'前缀的话,值会与当前几何体的scope大小相乘,然后分割,然后其它和上面的一致。
  • 加上~前缀的话,就是一个相对量,它会根据前后分割的情况自行调整分割大小。

然后是插入模型:

InsertSplit -->
    split(z){
        1 : InsertL|
        ~1 : NIL |
        1: InsertR
    }
    
InsertL -->
    10% :
        InsertLWithBusStop
    else :
        InsertLWithoutBusStop
        
InsertR -->
    10% :
        InsertRWithBusStop
    else :
        InsertRWithoutBusStop
        
InsertLWithBusStop -->
    split(x){
        {~5 :NIL|
        1 : InsertLObj|
        ~5 : NIL}* |
        8 : InsertLBusStop |
        {~5 :NIL|
        1 : InsertLObj|
        ~5 : NIL}* 
        
    }
    
InsertRWithBusStop -->
    split(x){
        {~5 :NIL|
        1 : InsertRObj|
        ~5 : NIL}* |
        8 : InsertRBusStop |
        {~5 :NIL|
        1 : InsertRObj|
        ~5 : NIL}* 
        
    }
    
InsertLWithoutBusStop -->
    split(x){
        {~5 :NIL|
        1 : InsertLObj|
        ~5 : NIL}*
    }
    
InsertRWithoutBusStop -->
    split(x){
        {~5 :NIL|
        1 : InsertRObj|
        ~5 : NIL}*
    }
InsertLBusStop -->
    t(0,0.2,-2.5)
    s(0.1*busStopWidth,0.05*busStopheight,0.04*busStopdepth)
    i(busStopOBJ)
    print(scope.tx)
    print(scope.ty)
    
InsertRBusStop -->
    s(0.1*busStopWidth,0.05*busStopheight,0.04*busStopdepth)
    r(0,180,0)
    t(-6,0.2,-3.5)
    i(busStopOBJ)
InsertLObj -->
    10% :
        t(-0.5,0.15,-1)
        s(0.15 * assetInfo(fireHydrantOBJ, sx), 
        0.15 * assetInfo(fireHydrantOBJ , sy), 
        0.15 * assetInfo(fireHydrantOBJ , sz))
        i(fireHydrantOBJ)
    else :
        r(0,-90,0)
        t(-0.5,0.15,0)
        s(0.02*width,0.02*height,0.02*depth)
        i(lampOBJ)
        
    
    
InsertRObj -->
    10% :
        t(-1.5,0.15,1)
        s(0.15 * assetInfo(fireHydrantOBJ, sx), 
        0.15 * assetInfo(fireHydrantOBJ , sy), 
        0.15 * assetInfo(fireHydrantOBJ , sz))
        i(fireHydrantOBJ)
    else :
        r(0,90,0)
        t(-1.5,0.15,0)
        s(0.02*width,0.02*height,0.02*depth)
        i(lampOBJ)

大部分操作都已经介绍过了。

10%是一个概率操作,意思是当前几何体会有10%的概率执行之后的操作。必须和else搭配。

i函数是一个插入模型函数,会将参数中路径对应的模型插入到当前几何体的位置上。

rts分别是旋转,平移和缩放函数,不用多介绍。

将规则文件赋予后,点击Generate就可以生成一个街道了:

然后人行道,这里先给出规则文件:

/**
 * File:    Sidewalk.cga
 * Created: 24 Apr 2020 15:36:45 GMT
 * Author:  Dragonboy
 */

version "2019.0"

attr sideWalkColorMap = "images/PavingStones038_2K-JPG/PavingStones038_2K_Color.jpg"
attr sideWalkNormalMap = "images/PavingStones038_2K-JPG/PavingStones038_2K_Normal.jpg"
attr sideWalkRoughMap = "images/PavingStones038_2K-JPG/PavingStones038_2K_Roughness.jpg"
attr sideWalkAOMap = "images/PavingStones038_2K-JPG/PavingStones038_2K_AmbientOcclusion.jpg"
attr nPoints = rand(2,3)
attr nPoints2 = rand(1,3)
attr trashCan = 
    33% : "assets/trash_can/trash can.obj"
    33% : "assets/trash_can/trash can2.obj"
    else : "assets/trash_can/trash can3.obj"


Sidewalk -->
    setupProjection(0, scope.xz, 1.5, 1.5)
    setupProjection(5, scope.xz, 1.5, 1.5)
    setupProjection(7, scope.xz, 1.5, 1.5)
    setupProjection(8, scope.xz, 1.5, 1.5)
    extrude(0.15)
    SidewalkFacecade
    
SidewalkFacecade -->
    comp(f){
        top : SidewalkFace |
        street.side : SideSidewalkTex
    }
    
SidewalkFace -->
    SidewalkTex
    Scatter
    
    
SidewalkTex -->
    set(material.colormap, sideWalkColorMap)
    set(material.normalmap, sideWalkNormalMap)
    set(material.occlusionmap, sideWalkAOMap)
    set(material.roughnessmap, sideWalkRoughMap)
    projectUV(0)
    projectUV(5)
    projectUV(7)
    projectUV(8)
    
    
Scatter -->
    comp(f){
        all : InnerRect
    }
    
InnerRect -->
    case geometry.nEdges < 5 :
        case geometry.area > 200:
            innerRectangle(edge){
                shape : ScatterSplit |
                remainder : NIL
            }
        else : NIL
    else : NIL
    
ScatterSplit -->
    alignScopeToGeometry(yUp, 0, 0)
    split(z){
        0.4 : Rubbish |
        ~1 : Manhole
    }

Rubbish -->
    scatter(surface, nPoints, uniform){TrashCan}
    
    
TrashCan -->
    s(0.15 * assetInfo(trashCan, sx), 
    0.15 * assetInfo(trashCan, sy), 
    0.15 * assetInfo(trashCan, sz))
    i(trashCan)
    
Manhole -->
    scatter(surface, nPoints2, uniform){ManholeInsert}
    
ManholeInsert -->
    primitiveQuad(1,1)
    t(0,0.001,0)
    ManholeTex

attr manholeColor = "images/ManholeCover005_2K-JPG/ManholeCover005_2K_Color.jpg"
attr manholeOpacity = "images/ManholeCover005_2K-JPG/ManholeCover005_2K_Opacity.jpg"
attr manholeNormal = "images/ManholeCover005_2K-JPG/ManholeCover005_2K_Normal.jpg"
attr manholeAO = "images/ManholeCover005_2K-JPG/ManholeCover005_2K_AmbientOcclusion.jpg"
attr manholeRough = "images/ManholeCover005_2K-JPG/ManholeCover005_2K_Roughness.jpg"
attr manholeMetal = "images/ManholeCover005_2K-JPG/ManholeCover005_2K_Metalness.jpg"
    
ManholeTex -->
    setupProjection(0, scope.xz, '1, '1)
    setupProjection(4, scope.xz, '1, '1)
    setupProjection(5, scope.xz, '1, '1)
    setupProjection(7, scope.xz, '1, '1)
    setupProjection(8, scope.xz, '1, '1)
    setupProjection(9, scope.xz, '1, '1)
    set(material.colormap, manholeColor)
    set(material.opacitymap, manholeOpacity)
    set(material.normalmap, manholeNormal)
    set(material.occlusionmap, manholeAO)
    set(material.roughnessmap, manholeRough)
    set(material.metallicmap, manholeMetal)
    projectUV(0)
    projectUV(4)
    projectUV(5)
    projectUV(7)
    projectUV(8)
    projectUV(9)

    
SideSidewalkTex -->
    setupProjection(0, scope.xy, 1.5, 1.5)
    setupProjection(5, scope.xy, 1.5, 1.5)
    setupProjection(7, scope.xy, 1.5, 1.5)
    setupProjection(8, scope.xy, 1.5, 1.5)
    set(material.colormap, sideWalkColorMap)
    set(material.normalmap, sideWalkNormalMap)
    set(material.occlusionmap, sideWalkAOMap)
    set(material.roughnessmap, sideWalkRoughMap)
    projectUV(0)
    projectUV(5)
    projectUV(7)
    projectUV(8)

需要注意的是scatter函数,它可以生成一些粒子,第一个参数是生成粒子的位置,有surface|volume|scope三种选项,这里选surface,毕竟是一个表面。第二个参数是生成点的数量,这里我使用rand函数传入了一个随机量。该函数有两种重载,如果选择的是uniform方式生成粒子的话(即粒子在几何体上是统一排布的,在概率学上来说就是分布函数很均匀),就是三个参数,第三个参数填uniform,如果选择gaussian方式生成粒子的话(粒子会靠近某一中心扩散,分布函数是高斯函数),就是四参数的,第四个参数填gaussian,第三个参数分布中心,有center|front|back|left|right|top|bottom。函数后的{}是每个粒子执行的操作。

生成人行道:


然后是生成建筑。为了方便,这里只介绍插入已有模型的建筑构建方式,如果是要手动创建的话,只要铭记分段操作即可。

/**
 * File:    Bussiness.cga
 * Created: 11 Aug 2020 07:16:43 GMT
 * Author:  Dragonboy
 */

version "2019.0"

attr Building01 = "assets/Buildings/Building01/Building1.obj"
attr Building02 = "assets/Buildings/Building02/Building2.obj"
attr Building03 = "assets/Buildings/Building03/Building3.obj"
attr Building04 = "assets/Buildings/Building04/Building4.obj"
attr Building05 = "assets/Buildings/Building05/Building5.obj"
//attr Building06 = "assets/Buildings/Building06/Building6.obj"
attr Building07 = "assets/Buildings/Building07/Building7.obj"
attr Building08 = "assets/Buildings/Building08/Building8.obj"
attr Building09 = "assets/Buildings/Building09/Building9.obj"
attr Building10 = "assets/Buildings/Building10/Building10.obj"
attr Building11 = "assets/Buildings/Building11/Building11.obj"
attr Building12 = "assets/Buildings/Building12/Building12.obj"
attr Building13 = "assets/Buildings/Building13/Building13.obj"
attr Building14 = "assets/Buildings/Building14/Building14.obj"
attr Building15 = "assets/Buildings/Building15/Building15.obj"
attr Building16 = "assets/Buildings/Building16/Building16.obj"
attr Building17 = "assets/Buildings/Building17/Building17.obj"
attr Building18 = "assets/Buildings/Building18/Building18.obj"
attr Building19 = "assets/Buildings/Building19/Building19.obj"

attr sideWalkColorMap = "images/PavingStones038_2K-JPG/PavingStones038_2K_Color.jpg"
attr sideWalkNormalMap = "images/PavingStones038_2K-JPG/PavingStones038_2K_Normal.jpg"
attr sideWalkRoughMap = "images/PavingStones038_2K-JPG/PavingStones038_2K_Roughness.jpg"
attr sideWalkAOMap = "images/PavingStones038_2K-JPG/PavingStones038_2K_AmbientOcclusion.jpg"


Bussiness -->
    FloorTex
    FloorUp
    
FloorTex -->
    setupProjection(0, scope.xz, 1.5, 1.5)
    setupProjection(5, scope.xz, 1.5, 1.5)
    setupProjection(7, scope.xz, 1.5, 1.5)
    setupProjection(8, scope.xz, 1.5, 1.5)
    set(material.colormap, sideWalkColorMap)
    set(material.normalmap, sideWalkNormalMap)
    set(material.occlusionmap, sideWalkAOMap)
    set(material.roughnessmap, sideWalkRoughMap)
    projectUV(0)
    projectUV(5)
    projectUV(7)
    projectUV(8)
    
FloorUp -->
    extrude(0.15)
    FaceCade
    
FaceCade -->
    comp(f){
        top:
        InnerRect
    }
    
InnerRect -->
    innerRectangle(scope){
        shape:BuildingInsert
    }

BuildingInsert -->
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building01)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building02)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building03)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building04)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building05)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building07)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building08)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building09)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building10)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building11)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building12)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building13)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building14)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building15)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building16)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building17)
        cleanupGeometry(all, 0)
    5.5%:
        alignScopeToGeometry(yUp, any, longest)
        i(Building18)
        cleanupGeometry(all, 0)
    else:
        alignScopeToGeometry(yUp, any, longest)
        i(Building19)
        cleanupGeometry(all, 0)

cleanupGeometry可以优化模型,第一个参数是优化的组件,可以是vertices|edges|faces|all,第二个参数是优化的程度,0是最严格的,1最松散,[0,1]介于二者之间。

没有什么技术含量,我这里提前准备了其它软件里得来的模型和纹理资源,然后生成即可:


有几点要注意是:

  • 生成街道或城市的时候,建议是生成一部分然后导出一部分,因为在模型构建期间,CityEngine会往C盘写入大量的文件,尤其是分段数多起来的时候,所以生成一部分然后导出,删除,等待缓存文件删除,接着重复操作。当然,如果有一个超大的系统盘当我没说。这也是我先不直接在CityEngine中构建建筑的原因,因为模型一复杂的话,光是生成一个建筑就把我C盘吃满了,目前我暂时没有找到其它解决方法。
  • 建议合理利用内置的Python脚本,毕竟这么大个城市,手动搞真的费手费眼。而且上面的方法也可以用Python批量来弄。

下一节介绍一下导出选项,以及导入到Unity后一些材质的重设定和shader的编写修正。

以上。

你可能感兴趣的:(CityEngine中街道和建筑的构建)