概述

For-Each框架可以用来做两件事:

  1. 为一个对象多次重复一种操作
  2. 为对象中的每个点或者面,都进行一种操作

这样来看,For-Each的使用场景就是:做一件重复数次的事,或者为多个点或面同时做一件事。一个简单的场景案例就是,为一个box连续挤出6次,这就是做一件重复的事。为box的每个面做挤出,这就是为多个面做一件事。For-Each的意义在于可以让Houdini帮我们做这种重复劳作,比如挤出6次,我可以在box下连接6个extrude节点,但是挤出600次呢?这种有规律的重复劳作最适合计算机来做了。

如果Tab键入For,会出来For-Each的六个节点,实际上这些节点都只是同样的节点不同参数的预设。他们本质上是同样的节点,只是为了方便我们针对不同情况下使用。这就类似于键入Vop,会有Attribute Vop、Point Vop和Primitive Vop,实际上也是同一个节点,不同参数的预设而已。

所以我们先把For-Each当作一种节点,来看下它的构成。再把这6种预设分开看,看看是在哪些情况下适用。然后在了解每种预设适用情况后,再回头整体理解一下For-Each的使用意义,最后再补充一些没有提到的地方。

框架构成

这6种预设,共有的节点就是Block Begin和Block End节点,这两个节点也构成了最基础的For-Each框架。所以我们也可以自己创建这两个节点,来搭建For-Each框架。

只不过创建出来的两个Block节点,并没有被框在一起。这是因为Block End节点不知道谁才是它的Block Begin,需要我们手动把End节点拖入到Begin节点的Block Path中,这样它们俩才能互相认识,形成一个框架。如果严谨一点,还需要把Begin节点拖入到End节点中的Default Block Path和Piece Block Path中,这两个Path之后再说。

它的数据流是这样的,想要处理的数据从Block Begin传入,Block Begin负责决定怎么使用这个数据,是循环重复一件事?还是为每个点或面做一件事?在Begin和End中间,我们可以连接多个任意节点,每次数据经过的时候,就会被这些节点处理,然后由End吐出,End决定是只显示最后一步操作的结果,还是把之前每次结果都显示出来。

理解这个数据流,可以大体理解For-Each的处理概念,具体的作用不理解没关系,我们还会针对每个预设做一个讲解,然后还会具体分析每个参数的意义。

适用情况

Each-Loop with Feedback

按着理解的难易程度,先从Each-Loop with Feedback开始吧。Each loop就是典型适用于多次重复一件操作的情况。这句话的核心是多次重复。Feedback就是反馈的意思,Begin会使用上一次循环重复的结果(End)作为再一次循环重复的起点。比如说我想要一个茶壶向X轴移动1m,移动10次

那么就可以这样连接,transform的Translate.x设定为1,就是向X轴正向移动1m,End节点中的Iterations改为10.Iterations就是决定重复的次数,而Begin和End中间的节点,就是要重复的操作,这里就是transform了。这样,茶壶就会第一次向X轴移动1m,第二次在移动1m的基础上再次移动1m,重复十次后它应该处于10m的位置。

操作正确的话,就能看到茶壶已经被移动到了x=10的位置。现在显示的是最后一次重复的结果,如果说我们想要看到每一次重复的结果,就需要把End节点中的Gather Method改为Merge Each Iteration,你就能看到10个茶壶。但是看不到最初的在原点,作为输入数据的茶壶。因为它只是提供了数据流,并没有参与到重复循环本身。想要它也显示的话,只能用merge节点让它和End节点连在一起了。

但是如果不仅仅是移动1m,同时还旋转35°,那最终的结果并不是茶壶排成一条直线,依次旋转,而是整体旋转成一个圈。这是因为在进行第一次重复的时候,茶壶向前移动1m,同时旋转35°,这时候茶壶的自身X轴方向就变了,不再是跟世界X轴相同了。第二次重复的时候,是基于上一次的结果,茶壶又沿着自己的X轴向前移动1m,同时又旋转了35°。于是这一排茶壶就画起了圈。

那如果想要上面这个效果,要怎么做呢?这个就需要每次重复,都是基于输入的状态重复,也就是原点的状态。第一次基于输入状态移动1m,旋转35°,第二次仍然是基于输入状态,移动2m,旋转70°。也就是说,每次重复都基于第一次输入状态,移动N*1m,旋转N*35°。N就是重复的次数。

所以我们需要做两个处理,第一个是改变Begin节点处理数据的方式,由接受每次重复的结果,改为每次都使用原始的输入状态。第二个是我们需要一个变量N,也就是重复的次数。这就需要使用For-Each Number了。

For-Each Number

我们用同样的连接方式,把之前的节点连接到For-Each Number框架中。跟For-Each Loop不同的是,它多了一个Begin节点,名称叫做foreach_count3。它的Method参数是Metadata,而左边的Begin节点,Method是Fetch Input。Fetch Input的意义就是每次重复都拿最初输入的状态去做重复。而Metadate的意义是什么都不做,它只是四个属性的承载体:iteration、numiterations、value、ivalue。我们现在只看iteration,它代表了当前是第几次循环。也就是刚才我们说需要的变量N。第一次循环时,这个属性值就是1,第二次就是2,以此类推。

所以我们可以用这个循环次数iteration,作为变量输入transform节点参数中。位移X参数输入detail(“../foreach_count3/”,”iteration”,0)*1,旋转Y属性输入detail(“../foreach_count3/”,”iteration”,0)*35.

detail函数就可以取得某个节点的detail属性值。它一共3个参数,第一个参数是要取得的节点名字,第二个参数是要取得的属性名,第三个参数写作0,当找不到这个属性的时候,拿0作为缺省值。

所以这就是为什么For-Each Number要自动创建一个Metadata类型的Begin节点,因为没有这些个变量,每次重复都是一个固定值的话,相当于每次都对原始物体移动1m,旋转35°,这个重复就算重复100次,得到的结果每次都是一样的。因为我们只是一直在对同一个物体进行同样的操作。

For-Each Primitive

For-Each Primitive就是属于那种为物体的每个面进行同一种操作的情况。

我们可以给Box的每个面都做一个挤出,虽然这个操作没有意义,因为extrude本身就有一个individual参数,可以为每个面做挤出,但是这里我们只学习思路,所以暂时忽略这个小bug。

For-Each Primitive相比之前的两种框架,最大的区别在于它不是为某个对象重复做一种操作了,它是为输入物体的面统一进行同一种操作。它的Begin和End节点都有变化,Begin的Method方式改为了Fetch Piece or Point,这意味着Begin处理数据流的方式变为了寻找输入对象的点或者面。End的变化更多,Iteration Method的方式也改为了Fetch Piece or Point,同时也没有了Iteration这个控制参数。这是因为,我们已经不需要设定要重复几次操作了,我们的操作变成了去寻找输入的面,有几个输入的面,我们就处理几次,这个次数是由输入的数据决定的,不需要我们手动控制。

For-Each Point

这个就是针对于点的操作,要注意的是,这个操作最终输出的结果只有点。就是说哪怕输入的时候连接的是一个Box多边形,但是最终输出的只会是这个Box的8个点。一般来说,当我们只留下点的时候,我们是用这些点来做克隆。不同于一般的克隆方式,For-Each Point可以使用metadata创建iteration这个属性变量,可以操控每个点的属性呈现有规律的变化,进而影响克隆出来的物体的变化。

For-Each Named Primitive

For-Each Named Primitive和For-Each Primitive的区别是在于,Named Primitive可以给把多个具有相同属性值的面视为一个面,进行统一操作。它们之间的区别也只是在于Named Primitive的End节点,Piece Attribute参数是勾选的,并且可以输入参数。比如挤出,我们想每两个面一起挤出一次,就可以通过创建属性,让每两个面的属性值相同,这样Named Primitive就把这两个面视为一个面一起挤出。正常情况下,挤出要么是所有面一起挤出,要么是每个面单独挤出。这个方式就实现了每两个面一起挤出了。

我们来举一个例子。

emmm…算了,这个例子以文字形式真的不太好解释,这里不放例子了。我会把笔记里所有的操作都放在一个工程里,文末可以下载。

For-Each Connected Piece

这个预设其实跟For-Each Named Primitive一样的,只不过Named Piece需要我们自己创建一个属性分类,然后输入到End节点的Piece Attribute参数中。而Connected Piece相当于是这个操作的一个特例。

自带的connectivity节点作用,是把连接在一起的面算作一个组,生成一个class属性。相当于是自动帮我们预先处理了一下组的分类。什么叫连接到一起的面呢?比如说我们导入了一棵树,这棵树有6000片叶子,每片叶子有6个面组成,我们想把每片叶子都缩小1倍。这时候我们怎么去定义每片叶子呢?怎么给每片叶子的面都分到一个组里呢?connectivity节点就可以做到。它识别到每个相连的面,如果相连就视为一片叶子,如果面之间不相连,那就是另一片叶子,这样就可以为每片叶子的面都设定一个属性值,默认的属性名叫class。比如第一片叶子的6个面,class属性都是1,第二片叶子的6个面属性都是2,以此类推。这样houdini就可以为相同class属性值的面做同一种操作了,也就相当于为每一片叶子做相同操作了。

总结

现在思考一下我们使用For-Each框架前,应该要怎么选择合适的预设。

1.如何处理输入的物体?

这个问题对应的是Begin节点中的Method参数,Fetch Feedback就是寻找每次重复操作之后的结果,Fetch Pieces or Point就是处理输入物体的每个点或面,Fetch Input就是寻找输入的物体,每次都是以这个初始物体作为基础进行操作。而Metadata并不是处理输入的物体,点击Create Meta Import Node就会创建一个Method为Metadata的节点,这个节点只承载了Iteration等四个属性。

2.每次操作是固定值,还是随着重复次数迭代变化?

这个问题决定了我们需不需要创建一个metadata节点。创建的话,就需要使用detail函数来获取这几个属性了。

3.是依据次数循环操作,还是依靠输入物体的点或面统一进行操作?

这个问题对应的End节点中的Iteration Method参数,依据次数就是By Count,依据输入的点或面统一操作则是By Pieces or Point。这个其实也是For-Each的区别核心,对应编程中的For循环和ForEach循环。

4.要怎么展示操作的结果?

这个问题对应着End节点中的Gather Method,把每一步操作都展示出来,就选择Merge Each Iteration。只展示最后一步的操作,就选择Feedback Each Iteration。

补充

补充下metadata的四个属性是什么意思。

在依据次数重复操作的时候,iteration就是当前重复的次数,numiterations就是一共重复的次数,而value值则是我们在End节点中设定的Sart Value值,它每次都会增加一个Increment值,相当于是我们除了重复次数数值外,还可以自定义一个数值,ivalue则是将value的数值转换为整数。

在对点或面进行同一种操作的时候,iteration表示当前处理第几个点或面,numiterations则是处理的点或面总数。value和ivalue这俩个数值似乎在这个模式下没有什么具体意义,先不讨论了。

所以你看,我们对多个点或面进行同一种操作的时候,它并不是同步一次性进行的,它处理的方式也是挨个找点,然后进行一次操作,它也是一种循环,所以才会有iteration这个属性。再换句话说,Loop是对一个物体进行循环操作,For-Each是循环物体的每个点或面进行一种操作。

因此,我们其实可以通过勾选Single Pass,可以单独查看第几次或者第几个重复的操作结果。而下面的Stop Condition,默认属性值是0,我们可以在这里输入任意表达式或者,只要输出的结果为1或者大于1,整个循环就会立即停止。

然后来看下上面的两个Path参数,开头也说了,不仅Begin需要连接End的Path来识别哪个才是自己的End节点。End本身也需要输入Begin的Path来识别哪个才是自己的输入节点。使用Each-Loop类型的时候,需要将输入节点拖拽到Default Block Path中,使用For-Each Primitive类型的时候,需要将节点拖拽到Piece Block Path中。这两个节点Default Block Path其实是可以省略的,但是使用Piece Block Path的情况下,是绝对不能省略的,不然就会报错。

这些就是想要补充的内容了,在写笔记的时候也发现,一些情况如果用视频来说,很容易讲清楚,但是文字形势下就需要费很大劲,或者根本说不清。如果有哪些我说错或者想补充的,欢迎下方留言。就酱!

工程下载
链接:https://pan.baidu.com/s/1IRKj8H_DXXUrrKfyJ-JX3g
提取码:ahbw

(完)

Leave a Reply