这一节介绍一下相关模型的构建。
上一节说过,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
函数是一个插入模型函数,会将参数中路径对应的模型插入到当前几何体的位置上。
r
、t
、s
分别是旋转,平移和缩放函数,不用多介绍。
将规则文件赋予后,点击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的编写修正。
以上。
网友评论