Houdini 16.5 Geometry

Packed primitives

On this page

Overview

Packed Primitives express a procedure to generate geometry at render time. The purpose is to decrease the amount of memory used when interacting with Houdini, by reducing duplication and only loading information when necessary.

Packed Primitives have information about geometry embedded inside of them. The information could be an actual piece of geometry stored in memory, a reference to a part of another piece of geometry, or a file path to geometry stored on disk.

Mantra, the Houdini viewport, the solvers, and so on, know how to interpret the packed information, and can render/display/work with the geometry efficiently.

Packed primitives cannot be edited – they are lightweight references. If you want to edit packed geometry, you have to use the Unpack node to extract the part of the geometry you want to edit, modify it, and then optionally repack the geometry using the Pack node.

Packed primitives are useful for rendering and simulating heavy geometry, or large numbers of copies/instances. Any time the geometry will not change (for example, non-deforming RBD objects) you can benefit from packing the geometry.

Types of packed primitives

In-memory packed primitives

You get an in-memory packed primitive by converting geometry to a packed primitive using the Pack geometry node. This creates a Packed Geometry Primitive with an embedded reference to the current version of your geometry in memory. The "embedded" geometry becomes a single un-editable "primitive" with a single transform.

  • The "embedded geometry" is just a reference to content in memory. Copying a packed primitive copies the reference rather than the geometry itself. So the referenced geometry is shared among all copies of the packed primitive. This is more memory efficient than copying unpacked Houdini geometry, which creates independent duplicates of all points, primitives, attributes, and so on.

  • Copies of packed primitives use less memory, are simpler to transform, and can be drawn more efficiently in the viewport or rendered by Mantra.

  • Because the referenced geometry exists in a traditional network, you can easily generate procedural geometry which adapts to your scene, use stamping to generate variations of your packed geometry, or make interactive edits to your geometry while viewing the results live. Essentially, working with in-memory Packed Primitives is a more interactive and user-friendly version of traditional instancing workflows.

  • You can "unpack" individual copies of an in-memory packed primitive in a geometry network to create an actual copy of the referenced geometry. This allows you to generate procedural workflows which are a hybrid of traditional Houdini geometry and packed primitives.

  • "Packed" in this case does not mean "compressed" or "smaller". You are keeping the original geometry in RAM as well as using a bit of memory for each reference. A single packed primitive is not necessarily any more efficient than just using original piece of geometry. The benefit comes from the efficient representation of large number of copies that share the referenced geometry.

    This is important to remember when copy-stamping packed geometry. If every instance of your packed geometry is unique, you get no memory or performance benefits. In fact this will use more memory than "real" geometry would, because each packed primitive has its own overhead.

    (It’s possible to offset the cost of packing stamped geometry somewhat when there are limited numbers of stamped variations. See the Copy SOP's Cache stamping parameter.)

Packed disk primitives

A packed disk primitive embeds a reference to a file on disk. At display or render time, Mantra/Houdini reads the data from disk rather than always keeping it in memory. Some file formats, such as .bgeo and Alembic, make this very efficient by allowing fast random access to their contents.

You can load geometry from disk as a packed primitive using the File SOP's Load parameter to "Packed disk primitive".

A packed disk primitive is similar to an in-memory packed primitive: the “embedded” geometry appears as a single un-editable primitive with a single transform.

  • Much like in-memory packed primitives, a packed disk primitive is an excellent choice for efficiently creating copies of geometry in the viewport and Mantra. Copying a packed disk primitive just copies the reference to the disk file.

  • Because packed disk primitives simply load already-generated data from files, they are less dynamic than in-memory packed primitives. The only way to edit a packed disk primitive is to "unpack" it, copying the file data into memory.

  • The viewport does not copy the geometry for each instance, but simply draws the same data multiple times with different transforms. The viewport can also draw a much simpler representation of the referenced geometry, such as a point cloud or bounding box.

  • Like Houdini, Mantra can "stream" the data from the disk file as needed instead of copying it into memory, reducing Mantra’s memory usage.

  • Whereas Houdini must write the entire geometry for any in-memory geometry into the IFD (the scene description file it sends to Mantra), for packed disk primitives it simply writes the reference to the file on disk. This can make IFDs much faster to generate and smaller on disk for very large/complex scenes.

  • Packed disk primitives are ideal for scene assembly, especially for static background objects. Their small memory use at render time also makes them very useful for objects with large on-disk footprints, such as simulation output.

Packed Disk Sequence primitives

Packed Disk Sequence primitives are similar to Packed Disk primitives (see above), but the primitive references a sequence of geometry filenames and an index into the file sequence. When mantra loads the sequence primitive as part of the scene, it knows the full sequence (instead of just having the geometry for the current frame), so it can interpolate between frames for motion blur. So Packed Disk Sequence primitives are a simple way to instance animated geometry (in the form of per-frame geometry files) efficiently at render time with motion blur.

To import an animated sequence as a PDS, use a File SOP. Set the Load parameter to "Packed Disk Sequence". Click the file chooser icon next to the Geometry file parameter and choose the geometry sequence to load.

Note

Technically, when you're loading a PDS, the $F in the Geometry file pattern is interpolated between the values in the Frame range parameter (it doesn’t refer to the current frame as usual). We use $F here for consistency with the other modes, and so the file chooser works as you expect.

The Sequence index parameter on the Load node sets which (floating point) frame in the animated sequence to use. The default is $FF - 1. You can edit the index on an existing PDS primitive using the Packed Disk Edit SOP.

Packed disk primitives automatically cycle when the rendered frame is outside the animation’s frame range. You can change this by setting the primitive string attribute "wrap" on the packed disk sequence to one of "clamp", "cycle", "mirror" or "strict".

  • "cycle" automatically wraps the sample index to the valid range (the default behavior).

  • "clamp" clamps out of range index samples to the valid range (so, for example, if the valid frame range is 1-5, frame numbers greater than 5 will stick at frame 5).

  • "mirror" wraps by reversing in a zig-zag or ping-pong style.

  • "strict" gives empty geometry outside the valid frame range.

Packed fragments

When you pack geometry that includes a name attribute, each piece of geometry that shares the same name value becomes a packed fragment primitive, containing a reference to the original geometry. So each fragment shares the same geometry, but refers to a subset of it.

  • Packed fragment primitives are ideal for representing many pieces of a complete model. Especially where each fragment will receive some unique transformation, such as in a rigid body simulation.

  • If you "unpack" a fragment, only that part of the original model is copied into memory.

  • Because each fragment is just a reference to the original geometry, it’s very efficient to have a large number of them. However, if you're deleting many of the fragments, they can become more inefficient than just using real geometry. Even if you only have a few crumbs left, Houdini is still keeping the entire original model in memory, whereas if you used real geometry, memory usage would be high at first but go down as you deleted parts.

    You can try getting the best of both worlds by "unpacking" the remaining fragments at some point where you only have a few left.

How to

To...Do this

Convert SOP geometry into a packed primitive

Use the Pack SOP. The Pack node can create a new primitive with all the input geometry, or separate packed primitives based on the value of an attribute (such as name, as created by Shatter).

Extract a "sub-primitive" from inside a packed primitive

Use the Unpack SOP.

Import geometry from a dynamics network as a packed primitive

The DOP Import SOP has an option to import Geometry data from a dynamics network as a packed primitive.

Access attributes on packed geometry in a VEX shader

You can use the renderstate VEX function to get the value of attributes on packed geometry. For example, if the packed geometry has a Cd attribute, you can use renderstate("packed:Cd", PackedCd).

Note

You cannot set/use primitive attributes on the geometry other than the material attribute (which Houdini treats as a special case). Primitive-level attributes will not work in general since to Houdini the packed geometry looks like a single primitive with a single point.

Rendering

Packed primitives are extremely useful for rendering in Mantra. They let you generate IFD and render faster and use less memory and disk space. However, you should understand how Mantra works with packed primitives to take full advantage of them.

Material assignment

With standard geometry, you can assign materials at two levels:

  • At the object level, in the Geometry node's parameters.

  • At the geometry (SOPs) level, using the Material node to set the material attribute on certain primitives. This overrides the object material for the primitives that have it.

When Houdini generates the scene description (IFD) file for rendering, it checks objects and geometry attributes for material assignments, so it knows which shaders to include in the IFD.

When you use packed primitives, it adds a third possible level of material assignment:

  • Material attributes on the embedded geometry inside packed primitives. These override "higher" levels (primitive attributes and object materials).

However, when Houdini generates the IFD, it doesn’t look in the embedded geometry (which might be a very large file that would be slow to scan through). so it can’t know what material attributes the embedded geometry might have, so it won’t know to include the shaders in the IFD. Only when Mantra unpacks the primitives at render time will it find out it might not have the shaders it needs.

To work around this problem, you can tell Houdini to include all shaders in the scene in the IFD, regardless of whether they're assigned at the object or geometry levels. Turn on Save all SHOPs on the Mantra render node. (This will increase the on-disk size of your IFD by a small amount). As long as you load the shaders needed by packed primitives into the scene, they will be available at render time.

Tip

For how to assign shaders and override shading parameters "inside" packed geometry, see the help on material style sheets.

Displacement and subdivision surfaces

Houdini does displacement shading and subdivision surface rendering the same for packed geometry as for standard geometry. However, if you're primarily using packed geometry for instancing, and applying displacement shaders or using subdivision rendering, you need to think about dicing.

  • Before rendering a displaced or a subdivided surface, Mantra "dices" the geometry into smaller primitives until there is one primitive for every pixel (when shading quality is set to 1). This means it dices objects closer to the camera more than objects in the distance (which have less pixel coverage).

  • When instancing using packed geometry, this can cause a problem. The benefit of instancing is that geometry is shared across all instances. But if you add displacements or subdivision rendering, Mantra must load and dice each object individually, which means the geometry is no longer being shared.

  • To avoid this problem, you can add the Share Displacements Between Instances render property to the object containing your instances. Turning this parameter on will tell Mantra to:

    • Use the highest level of dicing necessary for the scene on one instance, and then

    • Share that diced geometry between all instances

  • This means objects far away will get "too much" detail. There is some potential for this to cause slowdowns, however the benefits of preserving instancing probably outweigh any downside.

    In the worst case, if "incorrect" dicing levels cause problems, you can split the instances between two objects based on distance from camera, so the "highest dicing level" is computed separately for "near" and "far".

    Alternatively, you could unpack instances close to the camera, removing them from the "highest necessary dicing level" calculation.

Attributes

Disk and in-memory packed primitive instances are simply pointers to the same file or memory, so each instance can’t have individual attribute values (except the material and vel (velocity) attributes which is specifically hacked to work, see below).

Packed fragment instances can have individual attributes because they are coalesced, but this also means they're less efficient than on-disk or in-memory packed primitives.

Alembic primitive instances cannot have individual attribute values. However, there is an option to "unshare" Alembic primitives in Mantra. This uses a lot more memory but allows you to have individual attributes on Alembic primitive instances at render time.

Mantra creates a tree of virtual Mantra objects for packed primitives, and copies the material attribute down to each virtual object in the tree (if they don’t have a material attribute of their own), so materials on the Geometry object are properly applied to packed primitives inside.

Similarly, the vel (velocity) attribute is added down through the virtual object tree, so motion blur will work properly.

Primitive attributes on packed primitives are copied to object properties on the virtual Mantra object. You can access them in shader code using the Render State VOP or renderstate VEX function.

If you have a primitive material attribute inside a packed primitive, Houdini won’t look inside the primitive to know that it needs to include that material in the file it sends to Mantra. You can fix this (at the expense of a larger render file) by turning on the Declare all SHOPs option, which tells Houdini to simply include all materials in the render file.

When Mantra renders packed primitive fragments, it copies attributes from the packed primitive onto the geometry, so velocity blur on fragments will work. Other packed primitive types will not work this way since they are rendered as instances.

Geometry

Understanding

Modeling

Next steps

Guru level