本篇blog主要记录一下使用houdini开发程序化建筑的思路与流程。
首先,houdini制作程序化建筑的基本原理是:预先制作一些建筑的模块和零件,然后自定义一个组合的规律或形式,最后让houdini自动地拼接出一个完整的建筑出来。
所以前期的核心工作就是——定义、规划、创建建筑模块。
一、地面墙面模块规划
首先我们考虑最基础的建筑模块——地面配墙面。这种模块一般由1块地面+N块墙面组合而成(N∈[0,4])。
现在我们来枚举所有的情况:①只有地面;②地面+一个墙面;③地面+两个相对的墙面;④地面+两个相邻的墙面;⑤地面+三个墙面。
这里我们可以定义一个csv表格,来收纳所有的组合情况:
wall指的是模块名称,xb指的是有x块墙面,axbxc指的是尺寸是a*b*c(其中b是y轴 即竖直向上的轴),opp和adj分别描述相对和相邻,最后的01指的是变体编号。
当然,我们现在的模块叫做“地面配墙面”,其实还可以进一步细分成单独的墙面模块 和单独的地面模块。接下来我们在houdini中定义这些模块,首先创建一个Geo节点,在里面分别装载墙面和地板的subnet:
对于每个subnet,主要定义它的模型构成和名称:
首先创建一个grid,代表模型网格,将细分数改为2;然后连接到Null节点上,这里主要是编一个名字,格式为“模块名称_尺寸_变体编号”;
最后我们使用Attribute Wrangle节点,为我们的模型增加一个属性(什么是属性,例如顶点、法线、uv,这些都叫属性)。定义新属性需要写一个VEX代码:s@name = chs("nodename"),这样就拥有了一个叫nodename的新属性。如何给它赋值呢?我们可以在属性栏里写一个opinput,即 用该节点的0号输入端口来赋值这个nodename,这样我们Null节点定义的名字就赋值到nodename上了。
二、地面尺寸扩展
之前我们定义的都是400x400的地面,真实建筑的地面不可能都这么方正,所以我们需要扩展一些别的尺寸:400x300、400x200、400x100。这样我们可以扩展一下表格:
将各个情况都扩展出不同的尺寸(除了opp)。此时需要注意的是,对于adj的情况,分为left和right两种情况:
所以需要在后面补一个left和right的标识。
接下来我们需要更新一下houdini节点,在地板的subnet中加入不同尺寸的地面,并将它们合并输出:
到这步就可以理解了,对于wall/floor的subnet,它的意义在于得到所有尺寸的墙体/地板模型。现在我们拥有了wall和floor的subnet,我们就可以对这两者进行组合,拼接成最基础的地墙模块。
三、fbx批量输出
将wall和floor的所有面合并起来后,我们要将它们分别输出成独立的fbx模型,这样后续houdini可以读取模型进行拼接。
首先我们需要创建一个HDA(作为控制器):
一共需要两个输入参数,一个是导出fbx的路径directory,另一个是按钮button,其中后者是用来操纵python代码的,后续再说这段python代码是干嘛的。
HDA函数如下:
我们来梳理一下输出函数的逻辑。首先我们通过一段foreach迭代,来遍历每一个面,并取出当前迭代的次数,存入到面的“index”属性中,这样每个面都有了自己的标识编号:
int iter = detail(1,"iteration",0);
i@index = iter;
由于我们希望每个导出的fbx都只包含一个面,因此我们可以这样设计:对于1号帧我们输出1号面,对于2号帧输出2号面……换言之,对于N号帧,要将所有非N号的面都删除:
if(i@index != $F - 1){
removeprim(0, @primnum, 1);
}
将面清除干净后,我们可以为要导出的面准备一个属性:输出路径,路径就是前面的directory参数+每个面的name属性:
string directory = chs("../directory");
string name = prim(0, "name", 0);
s@filepath = directory + name;
有了filepath,我们就可以在rop_fbx节点中 根据filepath来输出模型:
`details(0,"filepath")`.fbx
这样导出流程就完成了。不过存在两个问题:①现在我们只能手动调节逐个帧;②虽然面上拥有了路径属性,但后面的导出fbx的节点不能获取它,导出fbx的节点获取的是它上一个null节点的名称。(也就是说,假如null节点的名称叫Null,那么fbx输出出来的模型名称就叫Null.fbx,很蠢)
所以现在我们希望:我们能控制当前的帧数,并能控制null节点的名字,这一步就必须依靠python脚本来实现了。
我们在fbx_batch_output子模块中新建一个null节点,将其属性改为python代码,写入:
import hou
node = hou.node('.')
path = node.path() + '/'
dataNode = hou.node(path + "foreach_begin1_metadata1")
dataNodeGeo = dataNode.geometry()
iterations = dataNodeGeo.attribValue("numiterations")
nullNode = hou.node(path + "output")
renderNode = hou.node(path + "rop_fbx1")
for i in range(1,iterations + 1):
hou.setFrame(i)
outputGeo = nullNode.geometry()
outputPrims = outputGeo.prims()
outputName = outputPrims[0].attribValue("name")
nullNode.setName(outputName)
renderNode.render()
nullNode.setName("output")
hou.setFrame(1)
原理很简单,首先我们通过foreach元数据节点获取 面的总个数,然后通过一个循环来获取各个帧下的面数据,并重置null节点的名称为对应面的名字,这样导出fbx时就能导出正确的面的名字了。
最后将null节点的名称还原,并将帧数还原成1。这样我们点击HDA的按钮,就导出成功了:
梳理一下我们前期的思路:
①设计建筑的地墙模块构成
②创建地墙的基础面片,并赋予代号名称
③将所有面片合并,通过foreach循环对每个面片赋予一个编号
④设置N号帧只保留N号面(即N号帧删除所有非N号面),以面片名称作为fbx文件名称输出
⑤创建一个python脚本,控制帧数,从而实现批量导出
四、组合地墙模块
上一节我们将独立的墙和地面导出成fbx了,这一节我们只需读入fbx并将它们自由组合,便可以拼接成不同的模块了。下面放一个组合示例:
首先读入fbx,对模型信息进行pack打包,attribpromote不知道啥意思先跳过,然后对顶点的法线进行一个重置,做一下transform,最后合并形成一个完整的地墙模块。这样的模块一共需要做4+8+4=16个。
五、屋顶模块完善
有了前四节的知识铺垫后,这一节可以继续扩展模块了。首先进一步扩展地板的尺寸,扩展出300x300、300x200、300x100、200x200、200x100、100x100;
然后开始制作屋顶。屋顶一共分为两种:拱形倾斜屋顶 和 平面屋顶:
屋顶的程序化生成流程比较复杂,这里只做一个简单分析。首先是拱形倾斜屋顶:
拱形倾斜屋顶的基形由三个物体组成:屋顶体、屋顶面、边栏,将它们三者合并后进行bend(弯折),然后进行旋转,即可体现出拱形倾斜的形状:
接下来就要分倾斜屋顶的类型了,包括非边缘处和边缘处,非边缘处,如下图所示:
最左侧的就是非边缘处的屋顶,比较简单;而右边这几个就是边缘处的屋顶(边缘处的屋顶需要拦一面墙),对于不同尺寸需要生成不同的模型。
至于平顶的屋顶也是类似的思路,节点实在太多就不一一分析了。在完成了Primitives节点的定义后,点击Export即可将屋顶的模型也全部导出,然后在combined节点中将所有屋顶模型重新读入拼接即可:
Comments | NOTHING