What & How & Why

Primitives

Ver.17.5
Houdini 关于 Primitives 的相关知识点


什么是Primitives

Houdini 中,Primitive 代表了几何体的单位。Houdini 中可以表示为 Primitive 的单位有:

  • Polygon face
  • Packed primitives, Packed Disk primitives, and Packed Disk Sequence primitives
  • Polygon soup
  • Volume
  • Tetrahedron(四面体)
  • Bezier/NURBS 表面
  • Polygon/ Bezier/ NURBS 曲线
  • Metaballs

除此之外还有一些可以用 Primitive 来表示的几何体。这些集合体相比起 Polygon 更有性能上的优势。

Polygons and meshes

Polygon 是由一系列的边(Edge)组成的,而这些边则由一系列的顶点(Vertex) 组成。

Mesh 是一系列有序的 Polygons 组成的。相比 PolygonsMesh 有好几个特点:

  • 效率高于同等的 Polygons
  • 可以直接转换为 NURBS

上面的两种几何表述方法有几个重要的属性:

  • Open / Closed:ClosedPolygons 中的第一个顶点和最后一个顶点是共享的,并在 Houdini 内部被标记为 closed
  • Planar / non-planar:Polygons 分为 2D 和 3D。
  • Convex / concave:Polygons 根据水平垂直坐标相交的情况区分为

Packed primitives

Overview

Packed Primitives 是一种在渲染时生成几何体的技术。该技术主要的目的是通过减少几何体副本的数量和只装载必要信息,来减少 Houdini 的内存使用量。

Packed Primitives 内嵌几何体的信息。该信息可以是存储于内存中的几何体的“实体”,也可以是某个几何体的引用,或者是某个几何体在硬盘中存储的位置(路径)。

MantraHoudini ViewportSolver 等等都能解读该信息,并且能高效的显示 / 渲染 / 利用这些信息。需要注意的是,Packed Primitives 不能被编辑,因为他们只是引用。如果需要修改 Packed Geometry,需要使用 Unpack 节点取出需要编辑的几何体,编辑之后使用 Pack 节点对其重新打包。

Packed Primitives 在渲染 / 模拟大量几何体时非常有用。在 DOP 中,如果几何体至始至终不会发生形变,那么我们可以考虑将几何体封装起来。

Packed Primitives 的种类

In-memory packed primitives

当我们使用 Pack 节点将几何体转换为 Packed Primitives 之后,我们获得了 In-memory packed primitives,并且嵌入一个映射到当前版本源几何体的引用(该 Packed Primitives 是不可编辑的)。

  • 我们创建的所谓的 “嵌入引用的几何体” 实际上只是某块内存区域的引用。复制 Packed Primitives 实际上是一个对引用的拷贝过程。因此,所有的 Packed Primitives 实际上在分享着同一个源几何体;这也是为什么 Packed Primitives 如此高效的原因。
  • Packed Primitives 使用更少的内存,更容易进行 tramsform,在渲染和 viewport 中会更高效的显示。
  • 因为被引用的源几何体存在于“传统网络”中,因此我们可以很容易的进行程序化建模来生成几何体(比如使用 stamping 生成各种不同的 Packed Primitives )。总的来说,相较于传统的使用独立拷贝的方式,使用 In-memory packed primitives 会更加有交互性、更加用户友好。
  • 使用 unpack 节点将创建 In-memory packed primitives实体拷贝。 该方式允许我们在必要的时候使用传统的工作流程来处理 Packed Primitives
  • Packed 不是压缩,只是一个指向源几何体的引用。因此,单个的 Packed Primitives 相对于单个的实体几何体来说,并不能更加高效。 Packed Primitives 的优势在我们需要处理大量的拷贝个体的时候会变得更加明显。

    因此,我们有必要在进行 copy-stamping 的同时对几何体进行打包(packing)。当然,如果每个几何体都是独一无二的,那么我们是不可能从 Packed Primitives 中得到任何好处的;而且由于 Packed Primitives 有自身的 overhead,因此相较使用源几何体,我们可能需要消耗更多的内存。
Packed disk primitives

packed disk primitive 是指嵌入了指向存储在硬盘中文件的引用的 Packed Primitives 。在浏览 / 渲染的时候,Mantra / viewport 会从硬盘中读取该文件(而不是从内存中读取)。Houdini 内置的文件类型 .bgeoAlembic,都是非常高效的,适合随机读取的文件类型。

我们可以使用 File 节点来进行 Packed Disk Primitives 的创建。与 in-memory packed primitive 相同,packed disk primitive 也是单个的、不可修改的 primitive。

  • 因为 packed disk primitive 生成来源是已经生成的文件,因此唯一编辑 packed disk primitive 的方式是使用 unpack,也就是从文件创建一个源几何体的拷贝,并复制到内存中。
  • viewport 不会为每个实例创建拷贝;而是通过多次在不同的位置重复绘制同一个数据点来达到显示实例的效果。因此,viewport 可以通过各种形式来描绘被引用的实例几何体,比如 point cloud / bounding box:
  • 相较于 Houdini 必须将内存中存在的几何体整个写入 IFD 中供渲染使用,packed disk primitive 只需写入实例的引用到文件里即可。这是 packed disk primitive 可以更快的生成非常复杂的场景,并节省大量的空间。
  • 总的说来,packed disk primitive 适用于场景的的组合,特别是静态的背景。当然,因为其非常小的内存消耗,packed disk primitive 也同样适用于输出占用空间巨大的对象,比如模拟的输出。

Packed Disk Sequence primitives

Packed Disk Sequence primitivesPacked Disk primitives 类似。但对于 Packed Disk Sequence primitives,其引用的是一系列几何体的文件名和该序列的目录(index)。当 Mantra 读取 Packed Disk Sequence primitives 作为场景的一部分时,Mantra 会知道读取的是整个几何体序列(而不是当前帧的一个几何体)。因此,Mantra 可以在帧之间进行插值(interpolate)供运动模糊使用。因此,Packed Disk Sequence primitives 是一种对快速装载已绑定动画的几何体的解决方案,并且可以很好的支持运动模糊。

我们可以使用 File SOP 节点将文件按 Packed Disk Sequence primitives 的方式导入。

从技术上来讲,当我们装载 Packed Disk Sequence primitives 的时候,属于目标几何体序列的帧数 $F 已经被插值了。(这个插值处于帧范围之间)。

序列的目录(Sequence index)属性指定使用哪一帧(浮点帧)。初始值是 $FF -1$。我们可以使用 Packed Disk Edit 节点对其进行编辑。

默认情况下,当渲染长度超过 Packed Disk Sequence primitives 的序列长度时,该序列会自动循环至开始。该设定可以通过修改 primitive 属性 wrap 来进行控制(string 类型属性)。该属性有四种选项如下:

  • cycle,默认属性,自动将序列索引调整到有效的范围内(也就是自动循环)。
  • clamp,将超出索引范围外的帧限制到索引内(比如序列的范围为1-5,那么大于第5帧的渲染结果将持续使用序列 5 的结果。)
  • mirror,超出的渲染帧数会在范围内来回移动(类似乒乓在范围内持续弹跳)
  • strict,超出的渲染帧会得到空的几何体的渲染结果。
Packed fragments

如果 Packed Primitives 中包含 name 属性,那么每一个具有(分享)该 name 属性的几何体都被称为一个 Packed fragment Primitives,且所有的 Packed fragment Primitives 均包含指向源几何体的引用。也就是说,所有的 fragment 分享同一个几何体,但引用其子集(比如源几何体是一堵墙,那么 fragment 就引用的砖块,所有的砖块都来自同一堵墙。)

  • Packed fragment Primitives 适用于完整的模型,但拥有很多独立的部件(比如破碎),且特别适用于这些部件都需要有独立运动的情况。
  • 如果对 fragment 使用 unpack ,那么只有那一部分的模型会被复制到内存中(very nice!8-)
  • 需要注意的是,如果删除了较多的 fragments, 那么使用 Packed fragment Primitives 将比使用源几何体更加低效。这是因为 Houdini 会一直为所有 fragments 保留整个父、源几何体的备份(即便是 fragements 只剩一点点)。因此这种情况下,相较于直接使用源几何体,Houdini 会消耗更多的内存。
    解决方案是:在 fragments 不多的情况下使用 unpacking 将剩余的 fragments 转化为实体对象使用。

How to

  • 转换一般几何体到 Packed 几何体:Pack
  • 分离 Packed 几何体中的子物体:Unpack
  • 将几何体按照 Packed 几何体导入 DOP :DOP Import
  • 在 VEX shader 环境中访问 Packed 几何体中的属性(比如 Cd):renderstate(packed:Cd, PackedCd)

我们只能以 material attribute 的方式在 GEO 层级使用 primitive attribute。 除了 material attribute(Houdini 按照特殊情况处理),primitive 层级的属性是不能在一般的场景中工作的,因为 Houdini 将 Packed 几何体视作单个 primitive 加上一个点。

Rendering

Packed Primitives 在渲染中是非常有用的。Packed Primitives 允许我们生成 IFD,并且渲染更快,内存 / 硬盘空间使用更少。

Material assignment

当我们对标准的几何体指定材质的时候,我们可以在两个层级下应用材质:

  • 在对象层级(Object Level)下
  • 在几何层级(SOP Level)下,使用 Martial 节点(该应用覆盖对象层级的材质)

当 Houdini 生成场景描述文件(IFD)的时候,Houdini 会检查对象和几何体中与材质相关属性;因此 Houdini 知道哪个 shader 需要包含在这个 IFD 中。 但当我们使用 Packed Primitives 的时候,Houdini 还提供了第三种渠道来应用材质:通过嵌入在 Packed Primitives 中的 Material attributes 来应用材质。该层级的材质将覆盖之前提到的几何体层级与对象层级应用的材质。

不过当 Houdini 生成 IFD 文件的时候,Houdini 并不会查看这个 Material attribute 属性(可能因为是文件太大,扫描起来太慢)。因此,Houdini 不会将该 Material attribute 对应的 shader 添加到 IFD 中。只有当 Mantra 解包(Unpack)这些 Packed Primitives 的时候(处于 render time),Houdini 才能进行对材质的搜索。因此,如果之前并无匹配的材质属性,那么渲染出来的模型很可能就会缺失材质: 为解决这个问题,我们需要告诉 Houdini 将所有场景中的 Shader 信息包括到 IFD 中(不管是什么层级中应用的)。我们可以开启 Save all SHOPS 选项(在 Mantra 中)。这样的话,只要 Packed primitives 带有 shader,那么就一定会出现在渲染的结果中。

Displacement and subdivision surfaces

Displacement shadingSubdivision Surface 的处理上,Houdini 采用了同样的策略对待 Packed Primitives 和一般的几何体。但当我们主要使用 Packed Primitives 时候,我们需要考虑 Dicing(分治?)。

  • 在渲染被置换(displaced ) / 细分(subdivided )的表面之前,Mantra 会将几何体划分为更小的块,直到每个像素都有 1 个匹配的“块”(与 ‘’shader quality‘’ 有关,这里是 1)。这意味着越靠近摄像机的对象,越细分的厉害(离摄像机越近,所占的像素区域越大)
  • 当我们使用 Packed 几何体作为实例的时候,就会出现问题了。Packed 几何体作为实例的好处是共享源几何体;但当添加了置换与细分之后,Mantra 必须要分别加载每一个划分后的部分,这就导致源几何体不再被分享了。
  • 解决这个问题需要添加 Share Displacements Between instances 渲染属性到包含实例的对象。开启这个选项会告诉 Mantra 做以下的事情:
    • 对于场景中某一个实例进行的最高级别的划分
    • 其他实例共享该划分过的实例
  • 这意味着远处的对象可能会获得更多的(不必要)的细节。这可能会导致渲染的减速;但因共享而带来的好处可能要更加有价值一些。

    在比较极端的情况下,如果上述这种“不正确”的划分导致了问题,我们可以将该用于分享的实例分解为两个对象,一远一近。这样我们就可以对不同的对象进行不同等级的细分,从而达到按照远近进行恰当划分的效果。

    同时,我们也可以解包离摄像机近的对象,将其从 highest necessary dicing level 选项中移除。
Attributes
  • Disk / Memory Packed primitives 中的实例实质上是一个简单的指针。该指针指向被分享的源几何体的内存地址(或硬盘位置),因此所有的实例都无法拥有自身的属性(除开 material / vel attribute ,Houdini 对该两种属性做了特别处理)
  • Packed fragment 实例可以拥有独立的属性,因为这些 fragment 是被“拼合”在一起的(引用的源几何体的子集)。但这也意味着 Packed fragment 的效率并不如 Disk / Memory Packed primitives 。
  • Alembic primitive 也不能拥有自身的独立属性。不过,在 Mantra 中,针对 Alembic 有一个 unshare 的选项。该选项会消耗大量的内存,但允许 Alembic 的实例拥有独立属性,并可以在 render time 使用。

How Material / Vel works

Mantra 会为 Packed primitives 建立一棵虚拟的对象树,然后会将对应的 material attribute 拷贝到该书上的每一个虚拟的对象(如果这些虚拟的对象都没有 material attribute 的话)。因此 几何体对象上的 materials 将会正确的应用到 Packed primitives 上;vel 同理。

可以通过 Render State(VOP)/ renderstate (VEX)访问在 Packed primitives 上的属性。

默认情况下 Houdini 不会查看 Packed primitives 中的 material 属性。在 Mantra 中启用 Declare all SHOPs 可以修复该问题,但也会导致 IFD 体积增大。

Polygon soup

Polygon soup 也是 Polygon 的一种。这种几何数据结构特点在于:

  • 在内存中的存储非常高效和紧凑,但与此同时,它要求其所有的 Primitives 必须有同样的 Primitives 属性。
  • 在拓扑结构不改变(不添加顶点之类的)的情况下,节点可以共通编辑 Polygon soup(类似于 pass by ref);相比之下,对普通 Polygon 的处理则是每一个节点对应一个副本(类似于pass by value)。

由于 Polygon soup 独特的结构,它的建模和渲染速度会更快,而使用的内存会更少

不过 Polygon soup 对于面数较小的几何体并不是一个比较好的选项:因为 Polygon soup 有较大的管理开销,如果面数较小,处理速度反而会普通的 Polygon 慢一些。

一些需要注意的事情:

  • 将 Alembic 加载为 Polygon soup 会大大提高加载时间。
  • Polygon soup 要求拓扑的一致性,因此一些对 PrimitivesVertex 进行操作的节点和可能会改变 Polygon soup 原本描述的意思。
  • Polygon soup 中的顶点小于面数,因为它将很多位置相同的顶点合并在了一起。这要导致一些依赖于顶点的节点不能使用,比如 UV projects 就不能正确的计算出贴图的坐标。
  • 凡是需要改变几何体拓扑结构的操作,都不能对 Polygon soup 使用:比如 Poly ExtrudePoly Split 等等。
  • 使用 Polygon soup 的时候,我们可以现将其转换成普通的 Polygon soup 处理,处理完以后载将其转换回来。


具体对比的细节可以参考 When to use polygon soups, packed primitives, or regular polygons?

MetaBall

Metaball 实质上是一个隐式曲面(Implicit Surface),其一大特点是可以平滑的合并两个曲面。这使得 Metaball 在制作血液或者外表和怪异的几何体上有明显的优势。

NURBS and Bezier splines

注:有关的详细信息请查询WIKI Pedia。

NURBS,全名 Non-Uniform Rational B-Spline,是一种用控制顶点Control Vertices)定义的光滑曲线。 NURBS 所描述的曲线总是光滑的,连续的,无论描述其的顶点处于任何位置。因此 使用 NURBS 维护曲面有一个很明显的优势:你可以随意的拖动曲线中的顶点,而这些操作并不会导致几何体的纠缠(比如面相交)或者几何体的不连续(比如破面)。







Bezier 曲线相对与 NURBS 更加简单,而且允许定义的曲线在顶点处不连续,使用起来更加方便。

Parameterization

上节说过 NURBS 是由控制顶点形成的一种曲面;而控制顶点如何形成不一样的曲面呢?这一点是通过一个向量 Knots 来实现的。

待完善

顺序和角度

待完善

Quadratic primitive shapes

Houdini 支持 Primitive Spheres / Ellipsoids, Circles / Ellipses, 和 Tubes / Cone。这些几何体都被一个点定义:他们的中心点。这些几何体都支持移动,旋转,和缩放。
这些几何体都非常轻便,可以在如下的应用中使用:

  • Copy Instance
  • 粒子的容器

需要注意的是,Quadratic primitive shapes 并不具有拓扑结构,因此一些基于拓扑结构的操作节点是不会起作用的。

参考文章