What & How & Why

Dynamics

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 却代表了累积变化总帧数

Top Dynamics aspects

基础概念

模拟包含了对象、解算器、力
  • 对象Objects):存储了需要被解算的数据,比如密度(density),温度(temperature),摩擦力(friction),初始位置 / 速度 / 旋转等等。
  • 解算器Solver):计算被解算对象的行为。计算的间隔被称为 Time step
  • Force):添加到指定对象上的额外的信息,也就是“某对象被某力所影响”。解算器会寻找对象对应的力,在每个 time step 之间更新。
解算器的种类
  • Rigid bodies
  • Grid-based Fluids
  • Particle fluids
  • Particle system
  • Cloth / wires
Merge的作用

在其他的 network 中,Merge 主要用于将指定的输出组合起来。但在 DOP 中,Merge 创建了关系Relationships)。总的来说,关系是一种特殊类型的数据;这种数据描述了对象之间是如何互相影响的。

在 DOP 中,主要的关系被称为碰撞collision)。这种关系描述了对象在互相碰撞的过程中产生的形变、位移、等等现象。另外一种关系被称为约束constraint),这种关系主要存在于 RBD、Wire、cloth 解算中。

需要注意的是,关系是有计算量的;因此我们应该在对象真正需要交互的时候才创建关系。并且,关系分为单向 / 多向。尽可能的选择单向关系可以节省计算量。

通过 Geometry Spreadsheet 可以查看对象之间的关系。

模拟的可视化

待完善。

DOP 中的 Bypass

与 SOP 相同,DOP 中也允许节点的 bypass。bypass 的节点不会参与整个节点的模拟。bypss 的状态是可以进行 keyframe 的,我们通过对指定节点的 Activation 属性进行关键帧管理即可达到这样的效果。非零代表当前节点参与模拟,零代表不参与模拟。

Detail View

Detail View 可以让我们非常方便的检查当前模拟的结构。该 view 为一个树状结构,左边部分按层级表示应用到对象上的各种数据,右边部分表示左边选中数据的细节。

该 View 对检查当前模拟的结构和细节有很大的帮助。我们可以通过该 view 查看当前帧应用了哪些数据,数据的名字和编号、及其组成部分是什么。并且,view 中的数据会根据模拟的进行而变化,从而允许我们对数据进行跟踪(会导致模拟变慢,关闭窗口则不影响)。

多个对象与多个解算器

待完善。

DOP 网络中模拟的顺序

DOP 中按照从上到下,从左到右的顺序依次计算。

如果节点有多个输入,那么会先对第一个输入进行从上到下的计算,再对第二个输入进行从上到下的计算。

Sub-stepping

一般来说,动力学引擎会在每一次 time-step 中更新数据。也就是说,time-step 是 Houdini 从模拟计算开始到重新模拟计算之间的时间间隔(通常情况下为 Frame 的倍数)。time-step 越多,计算的结果就越精确。该属性可以通过 DOP Network object 中的 Simulation 标签进行设置。

但当我们的模拟中存在多个解算器,而我们只想提高其中一个解算的精度的时候,提高 time-step 显然是不是一个很好的选择。这种情况下,我们选择使用 Sub-step 来提高指定解算器的模拟精度。Sub-step 参数是基于解算器的,因此只需要到指定的解算器中调整 Sub-step 的值即可。

控制 Sub-step 的两个属性为 Minimum substepsMaximum substeps。增加前者会将当前解算器的 time-step 进行进一步的细分。

Houdini 中允许使用浮点帧浏览解算结果。在整数帧浏览关闭的情况下,通过箭头按键即可在两个 sub-step之间来回移动。

Data Pushing / Pulling

当我们将指定的几何体转化为对象的时候,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 Object
  • 在这个节点内部的 RBD configure object
  • 在RBD configure object 内部的数据网络

解算器会寻找在这个数据网络中自己认识的数据来进行模拟。

解算器的作用

前面说过,解算器负责所有对象的行为变化计算。比如,RBD 解算器会寻找物理数据(密度、力、重力等等),然后将这些数据应用到对象上(或者修改其运动的位置,比如修改位置数据)。

当一个对象作为另外一个对象的一部分数据时,解算器会同时影响这两个对象。更确切的说,不同类型的对象也能互相影响。当然,一个对象不能同时被多个解算器控制(某些由多个解算器组成的解算器除外,比如 Blend 解算器)。

总而言之,Houdini 通过以下来个步骤来进行模拟:

  1. 以数据和对象组成网络(也就是我们之前提到过的树形结构)
  2. 使用解算器处理、修改该数据网络
绿标签与灰标签

动力学节点通常包含两种输入标签:

  • 灰色标签,代表了 对象+数据,也就是该连接会接收一个对象,添加数据到该对象上,并将添加后的对象交由下游处理。
  • 绿色标签,代表了 数据。该标签意味着接收一份数据,然后该数据只会应用到一个对象(或者是使用 Apply Data 节点的对象上)。

我们可以通过这两种方式来进行数据的传递。区别在于,灰色的方式类似于“内联(Inline)/ 对象为中心(Object-centre)”,也就是整个数据链会将数据不断的添加到对象上;而在这个过程中,对象会经过整个数据网路。而绿色的方式是以数据为中心(data-centric),也就是将所有的数据按照一定的顺序整合,最终应用到对应的对象上。

对象与数据的引用

参考 DOP import / output

模拟的最终处理

参考 caching

Set initial vs. set always

在动力学节点中,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 是通过如下的方式来模拟同时解算的:

  1. 通过从左到右,从上到下的顺序,依次对 DOP 中的对象应用力。
  2. 寻找 DOP 从中存在的碰撞关系。若存在任何碰撞关系,将碰撞力输入到对象中(静态碰撞对象有优化)。
  3. 循环导入,直到没有任何碰撞关系存在或者 Max feedback loops 达到上限。

解算的最佳顺序:将 heavey obejcts(复杂的对象) 放置到 light objects (简单的对象)之前。自动生成的网络中假设 particle 是 light objects,因此会总将 particle 对象放置在前面。如果 particle 量非常大,那么需要做出手动调整。

常见的 Utility Solvers
  • Multiple Solver:允许对相同的一系列对象按顺序使用多个解算器。
  • Blend Slover:同时应用多个解算器并对其效果进行混合。
  • Switch Slover:该 Slover 可以令对象按指定的路径进行解算(与 Switch 不用的是,Switch 通过自身决定选择哪种处理方式,而 Switch solver 根据对象上的 switch data 来进行选择)。
  • Script solver:允许通过 script 自定义数据。
  • Copy data solver

高级概念

Unique IDs, object ids, and object names

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.

Motion blur

需要注意的是,如果在模拟的同时开启了带运动模糊的渲染,那么运动模糊将导致模拟的结果的改变。这是因为模拟的 sub step 会被强制与快门间隔的长度相匹配。这样将导致模拟精度变化,从而改变模拟的结果。

我们可以通过先模拟,再渲染,通过将模拟结果缓存到硬盘来解决这个问题。

Data Names

每个对象 / 数据都有自己的数据名Data Name)。通常,解算器会按照名称(name) 来查看数据 / 对象。比如 RBD solver 会查看对象中是否存在名为 Forces 数据集。如果存在,就可以利用存在的力来更新该对象的信息。

正因为用于修改的数据都是用数据名来添加的,因此如果添加两个同样类型的数据,会导致数据的覆盖。为了避免这种情况的出现,Houdini 在每个用于修改的节点内部添加了一个名为 Unique Data Name 的选项。该选项会为当前数据名添加一个后缀,确保该数据是唯一的。

Data Sharing

几个需要知道的:

  • Houdini 可以为对象创建数据的备份,或者让对象共享数据。
  • Houdini 可以为每次更新(time step) 需要的数据创建备份,也可以让所有更新(timesteps) 从头到尾共享一个数据。
  • 每次数据更新, houdini 都对模拟中所有对象进行备份。

Data Sharing 则作为控制上述行为的选项存在于各个节点中。

从上面的几点可以明白,Data Sharing 主要处理的是对象、数据之间的数据分享方式。其具体的处理方式如下:

No Sharing

houdini 不会在对象之间分享任何数据,且每个 time step 都会数据创建新的备份。 Share data across timestep

在每个 time step 之间,对象们共享数据(数据有多个拷贝,对象们使用对应 timestep 的拷贝。所有拷贝都有相同数据名)。每次 time step 会创建数据的备份。如果某数据不依赖于其应用的对象,那么使用此选项。

Share data across all time

该种模式下,数据将会作为整个模拟的独一份让对象们共享(数据只有一份,对象使用相同的数据名)。

该选项下,数据需要满足两个要求:

  1. 数据必须不依赖该数据将应用到的对象。
  2. 数据不根据时间而变化(对于任何对象,该数据的时间长度都应一致,并且在整个模拟期间保持不变,比如重力)。

默认选项为 no sharing。该选项会提供最可预测的结果。当然,如果我们知道我们需要分享的数据的情况,选择分享指定的数据会显著的降低内存的使用率。

Groups

DOP 也提供了编组的功能:我们可以:

  • 使用组为对象进行正常编组
  • 通过 Object ID / Object name 来对对象进行编组
  • 对 group 进行编组

Group 在 DOP 中一些用途 / 效果有:

  • 方便选中指定具有相同 group name 的对象。
  • 技术上来讲,关系存在于 group 之间,而非单独对象之间。
  • 因为关系的基本单位为组,因此组内的解算需要同时进行。
Collision representations

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

Caching Simulations

概况

通常,Houdini 将解算的结果存储在内存里供回放使用。在 Dop network 节点中有一个属性 Cache memory 可以设置使用多少内存来 caching。

但当没有足够的内存供 Houdini 创建新缓存的时候,Houdini 会舍弃之前已经缓存过的内容。如果我们不希望丢弃之前的内容,我们可以开启该节点中的 Allow Caching To Disk 的选项。这种情况下,Houdini 不再丢弃旧的内容,而是将旧的内容按 .sim 的文件存储到指定的临时目录。当然,回放的速度会相应的变慢;但至少我们还保有整个模拟的结果。

除此之外,有两种情况需要存储解算结果到硬盘:

  • 如果只需要解算一次对象的运动(比如 ridid body)。
  • 当对解算结果满意,不希望更改时。

除了利用上面节点的 caching 功能之外,我们也可以利用 Output 节点或者 Dynamics render 节点来输出解算结果。

Timeline coloring

Houdini 会使用不同的颜色来代表当前的解算文件类型:

蓝色缓存处于内存中。
紫色缓存处于 .sim 文件中。
橙色该缓存使用内存存储,但因为内存不足 / 改变了解算设置 导致该缓存不再存在。

Explicit Disk Caching

默认情况下,如果 Allow caching to disk 选项启用,那么 Houdini 会将缓存文件写到一个临时目录中。而一旦解算文件无效以后,Houdini 会自动将其从临时目录中删除掉。

这种情况下,我们的解算文件既不安全,又难以管理。针对该情况,Houdini 提供了 Save checkpoints 的选项。该选项允许我们:

  • 指定存储解算文件的目录,需要使用 $SF 作为自动编号扩展名。
  • 指定多少帧存储一个 checkpoint;比如 5 意味着每 5 帧存储一次。
  • 指定 Houdini 每次存储多少个 checkpoints,超过该值的存储点会被删掉。

checkpoint 在规模较大的模拟中非常有用,特别是在渲染农场中。比如,如果我们的渲染在 50 帧的时候由于某种原因失败了,那么我们设定当前起始帧为 50 帧,之后再利用之前的 checkpoint 来进行解算(比如 45 帧处有一个 checkpoint),而不是从一开始重新解算。

Baking out sim files

处于 DOP 网络末端的 Output 节点可以控制如何写出模拟结果。这些结果是独立于缓存的,可以用于其他的用途。

输出的步骤:

  1. 选择 startend 决定要输出的帧数。
  2. output file 中输入对应的路径与表达式。可以使用 .sim.gz (gzip) 或 .sim.sc (Blosc) 的压缩格式;速度更慢但更节省硬盘空间。
  3. save to disk or save to disk in backgroundsave to disk in background 会创建一个新的 Houdini 进程来运行当前的模拟。如果选择该方式,模拟之前必须存储 .hip 文件。

除了 Output 节点之外,我们也可以选择 Dynamic 来输出我们的文件(注意这个文件是在 out 中)。如果我们的模拟在 out 网络中有依赖(也就是 out 网络的一部分),那么用 dynamic 节点是非常有用的。同时,因为 Dynamics 节点不是 DOP 的一部分,因此修改其参数不会导致 recook。

当我们完成 “baking” 解算文件后,我们可以停止 DOP network 的解算并使用我们存储好的 .sim 文件。

参考资料