Animated instances in Arnold

To me there’s always been a bit of a mystery surrounding how to properly set up instances in an efficient manner for Arnold in Houdini. Having been involved in setting these things up across a few projects over the years, I’ll use this blog post to summarize my findings and what I consider to be best practices for ensuring good artistic control and acceptable computational overhead at render time.

As always I’m open to the fact that I don’t know everything about the subject matter, so if you read this and disagree with any of my theories and statements, please feel free to reach out to correct me!

I haven’t really entered the USD/Solaris game yet, so there is a chance this isn’t fully relevant anymore.

Let’s go through the steps by using an example; I need to render a flock of birds flying around. I’ve decided I can get away with one or more animation cycles with  random offsets and that I don’t need a sophisticated crowd setup with different animated states and all that jazz.

 

Preparing the instance objects

I’ll keep this part brief as this part is the most intuitive; I want to prepare my geo for the task.

In the case of the birds it would probably be something I would expect to pick up from the animation department as an alembic cache. 


It would depend on what it is you’re instancing but typical things you would consider at this point is:

  • If applicable; making sure the geometry is loopable ie. the last frame of the cache should seamlessly play back into its first frame
  • Consider the duration of the loop too, the less frames you can get away with the more optimized the final renders will be
  • This is also a good place to clean up the geometry if needed (delete unwanted attributes etc.), or possibly create different LOD’s of the cache.
  • If you can get away with animation that is relying on rigid transforms as opposed to deformation that is definitely something you should consider too, as that will result in a way more efficient Alembic cache. Examples of that could be things like bees and flies, that pending on how close you’ll see them, you might get away with rigidly transformed fast flapping wings instead of fully deforming geometry
240 frames of this beauty cached out with transforms results in a file that is roughly half the size of its deforming equivalent.

Caching out ass files

Once we have some alembics we’re happy with we can move on to caching them out as ass files. How  you would go about doing that and which parameters you want ticked will depend on what you’re doing, but in the case of my bird animation I’m only after the “Export Shapes” parameter



One thing to check though is the motion blur settings on the Arnold ROP you use to cache out the ass files. This will be “inherited” when you render the instances later, so make sure you enable and set those according to your needs before writing out the ass files to disk!

Setting up instance points

At this point you should have one or more file sequences of ass files that you want to instance onto some points.Seeing as we are relying on our caches looping we need to set up our “instancefile” attribute in a way that takes that into account. There’s probably more elegant ways to go about doing that, but using a bit of vex we can set up something that will add random offsets and pick the right frame number

Nothing too complicated going on there, but in short:

It takes the bath to where my ass files are on disk /path/to/ass/files/bird_v001.1001.ass, but I strip away the frame number and extension.

Next I create a variable called offset which gives a random value between 0-1, which I then multiply up by the length of my cache, 100 frames in this case. One thing to note here though, is that I also added a modulo (%) to limit the number of unique random values (highlighted in the screenshot above). Whilst it’s not necessary, this helps optimizing the render. The less fully unique frame offsets you can get away with the better.

Next I add the offset to the current frame(@Frame) and use the modulo again to loop it based on the duration. 

Lastly I combine these bits to set my “instancefile” attribute.

string basePath = chs("ass_base_path");
int offset = rand(@ptnum%ch("Unique_Offsets"))*chi("Duration");
int frame =((@Frame + offset)%chi("Duration")) + chi("Start_Frame");

s@instancefile = concat(basePath,itoa(frame),".ass");

Before rendering we need to add the instance parameters to the geo object that contains our instance points and enable fast point instancing.

 

Now I’m ready to render, and if everything has gone to plan you should have a pretty efficient scene. In my not so realistic test I tried to render 50k instances of my bird, and it reached the first pixel within a few seconds. The ass file on disk for the render was small, and the memory use of the render was less than 3GB. Visually it looks like this, which is horrendous, but now that I’ve done all the leg work of setting up my instances in an efficient way I can focus on making it look nice rather than waste my time waiting for ages for the render to show up every time I want to render it.

In any case I hope this info has been useful. It’s a versatile technique to be familiar with and can be used for a variety of different purposes, flocks of birds, vegetation, crowds and bats or whatever you can imagine

When working on Morbius my team and I relied on this technique heavily to render thousands and thousands of bats (with fur!), which goes to show that it’s pretty robust even with heavy geo.

Bonus tip

It’s not always convenient to deal with points when setting these things up, as you can’t really see what you’re doing until you hit render.. Seeing as we already did the work of setting the correct frame number for our “instancefile” attribute, we could use that again for previewing alembics (whihch matches our ass files).

Store the frame value as an attribute by adding this to the wrangle from before

@frame = frame;

Load your alembic cache in an Alembic SOP and use a Copy to Points SOP 

 

Then in a wrangle we can set an intrinsic attribute called “abcframe” to get the right animation and offsets. Despite the name this attribute actually deals with time in seconds rather than frames, so we’ll have to convert our frame value:

float frame = point(0,"frame",@ptnum);
float time = (frame+1)/$FPS;
setprimintrinsic(0,"abcframe",@primnum,time,"set");

I recommend going a bit gentle with this in terms of point count, as it’s not super fast, but still useful!