All about Houdini’s dynamics networks / simulations.
在 Houdini 中,dynamics network( DOP ) 用于创建模拟(simulation)。模拟主要由两部分组成:被计算的对象(Object)与应用到对象上的的解算器(Solvers)。下图是一个非常典型 / 基础的解算示例:
<html>
<img src=“/_media/vfx/houdini/dop/dynamics_basic_1.svg” width=“750”/>
</html>
在 Houdini 中,针对不同的模拟(Rigidbody / fluid / cloth / grain etc.)有不同的 Object 和 对应的 slover。
某一些模拟还包括约束(constraints),即一个 object 的位置会受到另外一个 object 位置变化的影响。在 Houdini 的模拟中,我们将这样具有连带关系特性的存在称为关系( relationships)。关系并不单指约束,比如物体之间的碰撞(Collision)也是关系的一种。需要注意的是,关系可以在不同的对象之间发生,比如流体与刚体就可以进行碰撞。
力(Force)在模拟中也扮演了很重要的对象。同一个力可以应用到不同的对象和解算器中,同样我们也可以应用多个不同的力到一个对象和解算器上。
Houdini 中通过树形结构 来表示数据是如何应用到对象上的,通过 detail view
可以很方便的查看当前对象上已经应用的所有的数据。
如何为对象指定力
某些情况下我们需要为某些对象指定力。通过之前的示意图,我们了解到力的应用是通过节点上的灰色部分来进行传递的。因此,只需要将对应的对象连接到指定力的灰色部分就好。比如下图:
fan
只影响 sphere_object1
,但 gravity
却影响所有的对象。
时间表达式
在 DOP 中时间表达式不再使用 $F
或者 $T
(当前时间)了,而是使用 $SF
和 $ST
取代。这是因为 Houdini 的动力学引擎在某些情况下会在时间轴上前后移动来解算碰撞。当这种情况发生的时候,$F
代表了当前帧的编号,而 $SF
却代表了累积变化的总帧数。
Time step
。time step
之间更新。
在其他的 network 中,Merge 主要用于将指定的输出组合起来。但在 DOP 中,Merge 创建了关系(Relationships)。总的来说,关系是一种特殊类型的数据;这种数据描述了对象之间是如何互相影响的。
在 DOP 中,主要的关系被称为碰撞(collision)。这种关系描述了对象在互相碰撞的过程中产生的形变、位移、等等现象。另外一种关系被称为约束(constraint),这种关系主要存在于 RBD、Wire、cloth 解算中。
需要注意的是,关系是有计算量的;因此我们应该在对象真正需要交互的时候才创建关系。并且,关系分为单向 / 多向。尽可能的选择单向关系可以节省计算量。
通过 Geometry Spreadsheet
可以查看对象之间的关系。
待完善。
与 SOP 相同,DOP 中也允许节点的 bypass。bypass 的节点不会参与整个节点的模拟。bypss 的状态是可以进行 keyframe 的,我们通过对指定节点的 Activation
属性进行关键帧管理即可达到这样的效果。非零代表当前节点参与模拟,零代表不参与模拟。
Detail View 可以让我们非常方便的检查当前模拟的结构。该 view 为一个树状结构,左边部分按层级表示应用到对象上的各种数据,右边部分表示左边选中数据的细节。
该 View 对检查当前模拟的结构和细节有很大的帮助。我们可以通过该 view 查看当前帧应用了哪些数据,数据的名字和编号、及其组成部分是什么。并且,view 中的数据会根据模拟的进行而变化,从而允许我们对数据进行跟踪(会导致模拟变慢,关闭窗口则不影响)。
待完善。
DOP 中按照从上到下,从左到右的顺序依次计算。
如果节点有多个输入,那么会先对第一个输入进行从上到下的计算,再对第二个输入进行从上到下的计算。
一般来说,动力学引擎会在每一次 time-step 中更新数据。也就是说,time-step 是 Houdini 从模拟计算开始到重新模拟计算之间的时间间隔(通常情况下为 Frame 的倍数)。time-step 越多,计算的结果就越精确。该属性可以通过 DOP Network object
中的 Simulation
标签进行设置。
但当我们的模拟中存在多个解算器,而我们只想提高其中一个解算的精度的时候,提高 time-step 显然是不是一个很好的选择。这种情况下,我们选择使用 Sub-step 来提高指定解算器的模拟精度。Sub-step 参数是基于解算器的,因此只需要到指定的解算器中调整 Sub-step 的值即可。
控制 Sub-step 的两个属性为 Minimum substeps
和 Maximum substeps
。增加前者会将当前解算器的 time-step 进行进一步的细分。
Houdini 中允许使用浮点帧浏览解算结果。在整数帧浏览关闭的情况下,通过箭头按键即可在两个 sub-step之间来回移动。
当我们将指定的几何体转化为对象的时候,DOP 会通过对应的 object 节点将该几何体的信息导入到 DOP 网络中进行计算(节点处于整个 DOP 网络的顶端)。计算完毕之后,Houdini 会再次导入原有的对象,但此时的几何体自身的位置等信息已经被 DOP 修改过了。因此,此时导入的对象会处于整个 DOP 网络的最底部。
因而,我们渲染的对象实际上就是原有的、处于SOP层级的几何体;只是其自身的某些属性被 DOP 网络修改了。DopNetwork 的显示标签默认情况下是关闭的,也就是说 DOP 网络内部的东西不会参与渲染。
之所以这么做事因为 Mantra 被设定为渲染几何体,因此将解算的结果传递回原有的对象会允许我们获得对渲染的控制。当然,我们也可以在 DOP 层级进行渲染,但结果有可能会自带很多你不想渲染 guide 或者是 UI 几何体。
某些模拟工具,比如 Pyro,会创建一个新的对象来作为结果的载体。而渲染的时候,渲染器将会对该载体进行渲染,而不是将计算的结果导回到原有的几何体中。
Houdini 中使用公制单位作为模拟的刻度,默认为公斤 / 米 /秒(International System of Units)。所有的其他单位都基于此标准(比如密度单位:$kg/m^3$)。
模拟包括了对象和数据,而对象则是数据的容器。我们在 view 中看到的动力学对象(比如一个刚体球),实际上是一个包含有几何体数据的对象,而 viewer 通过渲染这些几何体数据达到显示该对象的效果。
当然,对象上的数据并不仅仅是几何数据;实际上他可以是任意的东西,可以包含任意的内容,可以被任意命名。但不是所有数据解算器都能读懂的。
解算器负责计算被模拟的对象的行为变化。通常解算器会使用对象上附带的数据进行模拟计算。
以自动创建的 Houdini shelf tool 为例,当我们创建了一个完整模拟网络后,Houdini 会自动创建三个不同层级的部分:
前面说过,解算器负责所有对象的行为变化计算。比如,RBD 解算器会寻找物理数据(密度、力、重力等等),然后将这些数据应用到对象上(或者修改其运动的位置,比如修改位置数据)。
当一个对象作为另外一个对象的一部分数据时,解算器会同时影响这两个对象。更确切的说,不同类型的对象也能互相影响。当然,一个对象不能同时被多个解算器控制(某些由多个解算器组成的解算器除外,比如 Blend 解算器)。
总而言之,Houdini 通过以下来个步骤来进行模拟:
动力学节点通常包含两种输入标签:
我们可以通过这两种方式来进行数据的传递。区别在于,灰色的方式类似于“内联(Inline)/ 对象为中心(Object-centre)”,也就是整个数据链会将数据不断的添加到对象上;而在这个过程中,对象会经过整个数据网路。而绿色的方式是以数据为中心(data-centric),也就是将所有的数据按照一定的顺序整合,最终应用到对应的对象上。
参考 DOP import / output
参考 caching
在动力学节点中,Houdini 提供了几种应用数据的方式:Set intial, Set always, Set never, Use deafult。
默认的是 set initial, 代表数据将在解算开始的第一帧导入。如果导入的值或者几何体会随着帧的变化而改变值(比如我们导入一个带变形动画的几何体,而该几何体的变形基于 time-dependent 的表达式),那么我们可以使用 set always
. 使用 set always 会显著的降低解算的速度,因为 set always 会持续的重写整个数据树,慎用。下面是应用数据的方法与详情:
Set initial | 在模拟开始的第一帧设定值,之后让模拟来控制数据的走向。 |
Set always | 按帧更新导入数据。如果你不希望导入值改变,或者不希望导入值被模拟控制(比如动画,)使用此选项。 |
Set never | 不设置 / 改变值。 |
Use default | 使用系统默认的设置进行操作(该操作可以方便的进行批量操作)。默认的设置是 set initial。 |
Houdini 通过对象之间的关系来判断解算的顺序。对于单项的关系,被影响的对象的地域优先级低于影响其的因素。当存在于多向关系 / 带有层级的关系形成的循环时,所有的解算必须同时进行。
不过实际上,计算机并不能进行”真正的同时解算”(因为计算机是按顺序执行指令的),因此,DOP 中的同时解算实际上是“伪”的同时解算((pseudo-simultaneous)。因此实际上,Houdini 是通过如下的方式来模拟同时解算的:
解算的最佳顺序:将 heavey obejcts(复杂的对象) 放置到 light objects (简单的对象)之前。自动生成的网络中假设 particle 是 light objects,因此会总将 particle 对象放置在前面。如果 particle 量非常大,那么需要做出手动调整。
Unique ID
在 DOP 中,每一个对象和数据都有一个全局唯一(Universally unique)ID(128 bit number)。该 ID 在整个解算过程中都是唯一的(即便是有多台计算机)。我们可以利用这个值来分享或者重建数据包(以 time step 为单位)。该 ID 的具体的信息可以在 detail view
中查看。
Object ID
每一个对象都有自己独特的对象编号(object ID)。该编号为整型,用于标记某一次模拟中的某个对象。与全局唯一 ID 不同的是,该 ID 在不同 DOP 中、不同电脑、不同次的模拟中均不唯一。
Object Name
每一个对象同时具有一个对象名称(obejct Name)。该名称可以用于替代 object ID 作为某个对象的标记,以便使用者更好的记忆。需要注意的是,即便是在同一次模拟中,对象名称也是不唯一的。多个对象可以拥有同一个对象名称。
比如在某些对象创建节点中(RBD),我们可以通过 Object name
属性来设定被创建对象的名称。如果在 Number of objects
设定了同时创建多个物体,那么我们可以使用本地变量 $OBJ$
来指定对象的编号。比如下面例子:
//object name pre-fix
brick
//object number
3
//Object name
brick$OBJ
//output
brick0, brick1, brick2
detail view 默认显示对象名称。可以通过设定查看 object ID.
需要注意的是,如果在模拟的同时开启了带运动模糊的渲染,那么运动模糊将导致模拟的结果的改变。这是因为模拟的 sub step 会被强制与快门间隔的长度相匹配。这样将导致模拟精度变化,从而改变模拟的结果。
我们可以通过先模拟,再渲染,通过将模拟结果缓存到硬盘来解决这个问题。
每个对象 / 数据都有自己的数据名(Data Name)。通常,解算器会按照名称(name) 来查看数据 / 对象。比如 RBD solver 会查看对象中是否存在名为 Forces
数据集。如果存在,就可以利用存在的力来更新该对象的信息。
正因为用于修改的数据都是用数据名来添加的,因此如果添加两个同样类型的数据,会导致数据的覆盖。为了避免这种情况的出现,Houdini 在每个用于修改的节点内部添加了一个名为 Unique Data Name
的选项。该选项会为当前数据名添加一个后缀,确保该数据是唯一的。
几个需要知道的:
Data Sharing 则作为控制上述行为的选项存在于各个节点中。
从上面的几点可以明白,Data Sharing 主要处理的是对象、数据之间的数据分享方式。其具体的处理方式如下:
No Sharing
houdini 不会在对象之间分享任何数据,且每个 time step 都会数据创建新的备份。
Share data across timestep
在每个 time step 之间,对象们共享数据(数据有多个拷贝,对象们使用对应 timestep 的拷贝。所有拷贝都有相同数据名)。每次 time step 会创建数据的备份。如果某数据不依赖于其应用的对象,那么使用此选项。
Share data across all time
该种模式下,数据将会作为整个模拟的独一份让对象们共享(数据只有一份,对象使用相同的数据名)。
该选项下,数据需要满足两个要求:
默认选项为 no sharing
。该选项会提供最可预测的结果。当然,如果我们知道我们需要分享的数据的情况,选择分享指定的数据会显著的降低内存的使用率。
DOP 也提供了编组的功能:我们可以:
Group 在 DOP 中一些用途 / 效果有:
Houdini RBD solver 支持两种形式的碰撞检测:volume / thin-plate。当对象不具有体积的时候,使用后者。
Volume-Based 碰撞检测会为每个对象创建一个体积的表现形式。通过对象中的 Collisions→Volume→Show collision guide geometry
可将其可视化。
对于碰撞的检测,Houdini 使用两种不同的表现形式进行几何体的相交(surface rep v.s. vol rep)。对于几何体的 surface representation,默认情况下 houdini 使用对象几何体上的 points 来表示。该方式并不适用于对象中包含比较尖锐的边,或者没有足够的点来表示集合体表面的情况。因此,Houdini 还提供了一种使用边来表示几何体表面的方式。上述这两种方式可以通过 Collisions→Surface→surface reoresentation
来选择。当然,如果使用边来表示几何体的表面,代价将更昂贵。在绝大部分情况下,点表示的方法都足以满足我们的需求。边表示表面的方式主要适用在某些特定的情况下,比如一个表面没有点的几何体。
如果几何体对象没有提及,需要关闭 Use volume based collision detection
。
如果参与碰撞几何体中同时存在 volume / thin-plate 类型的对象,那么碰撞的类型是 volume。如果只存在 thin-plate 类型的对象,那么使用三角碰撞检测(per-triangle collision test)。需要注意的是,thin-plate 类型的对象并不能很好的堆叠。如果想得到更好的效果,需要增加 minimum number of sub-steps
。
通常,Houdini 将解算的结果存储在内存里供回放使用。在 Dop network
节点中有一个属性 Cache memory
可以设置使用多少内存来 caching。
但当没有足够的内存供 Houdini 创建新缓存的时候,Houdini 会舍弃之前已经缓存过的内容。如果我们不希望丢弃之前的内容,我们可以开启该节点中的 Allow Caching To Disk
的选项。这种情况下,Houdini 不再丢弃旧的内容,而是将旧的内容按 .sim
的文件存储到指定的临时目录。当然,回放的速度会相应的变慢;但至少我们还保有整个模拟的结果。
除此之外,有两种情况需要存储解算结果到硬盘:
除了利用上面节点的 caching 功能之外,我们也可以利用 Output
节点或者 Dynamics render
节点来输出解算结果。
Houdini 会使用不同的颜色来代表当前的解算文件类型:
蓝色 | 缓存处于内存中。 |
紫色 | 缓存处于 .sim 文件中。 |
橙色 | 该缓存使用内存存储,但因为内存不足 / 改变了解算设置 导致该缓存不再存在。 |
默认情况下,如果 Allow caching to disk
选项启用,那么 Houdini 会将缓存文件写到一个临时目录中。而一旦解算文件无效以后,Houdini 会自动将其从临时目录中删除掉。
这种情况下,我们的解算文件既不安全,又难以管理。针对该情况,Houdini 提供了 Save checkpoints
的选项。该选项允许我们:
$SF
作为自动编号扩展名。5
意味着每 5
帧存储一次。
checkpoint 在规模较大的模拟中非常有用,特别是在渲染农场中。比如,如果我们的渲染在 50
帧的时候由于某种原因失败了,那么我们设定当前起始帧为 50
帧,之后再利用之前的 checkpoint 来进行解算(比如 45
帧处有一个 checkpoint),而不是从一开始重新解算。
处于 DOP 网络末端的 Output
节点可以控制如何写出模拟结果。这些结果是独立于缓存的,可以用于其他的用途。
输出的步骤:
start
和 end
决定要输出的帧数。output file
中输入对应的路径与表达式。可以使用 .sim.gz
(gzip) 或 .sim.sc
(Blosc) 的压缩格式;速度更慢但更节省硬盘空间。save to disk
or save to disk in background
。save to disk in background
会创建一个新的 Houdini 进程来运行当前的模拟。如果选择该方式,模拟之前必须存储 .hip
文件。
除了 Output
节点之外,我们也可以选择 Dynamic
来输出我们的文件(注意这个文件是在 out 中)。如果我们的模拟在 out 网络中有依赖(也就是 out 网络的一部分),那么用 dynamic 节点是非常有用的。同时,因为 Dynamics
节点不是 DOP 的一部分,因此修改其参数不会导致 recook。
当我们完成 “baking” 解算文件后,我们可以停止 DOP network 的解算并使用我们存储好的 .sim
文件。