Mograph Multishader Setup in Houdini

So if you run in the Mograph circles you most likely have come across the infamous random image to plane deal. You know the one. The one where you take a folder full of images and map them to individual planes. I know I’ve had my share of it. Sure enough, I was asked to do it again recently. Normally, I would reach for Cinema 4D and quickly set it up. However, I wanted to try it in Houdini since I figured this would be a piece of cake. There are probably multiple way of dealing with this setup in Houdini but I wanted to go with a quick and dirty way. In this #QuickTip I’ll show you how I approached a mograph multishader setup in Houdini. Or at least attempted to…

Image Frame Setup

The overall setup is really easy. I first want to create a frame to house the images. Nothing fancy, just something else besides a grid. Drop down a Geometry object which I call frame, dive in and model a simple frame border from a box object. I also need a primitive for the picture mapping so I add this face to a Group. I use a couple of Blast nodes to separate the face and the border and add UVs to each geometry piece. You could have also easily done it with a couple of Group nodes. I add a Null node at the end of the chain for easy picking later on and I’m done with this network. Now we need some points!

Image frame
Image frame

 

Scattering Points

Now with the frame geometry ready, I want to create another network for the points the planes are going to be copied on to. Think Cloner set to Object in Cinema 4D. I create another Geometry SOP at object level, name it image_planes and dive in. You can create this setup multiple ways. You can use a particle system, you can paint or spray points, the list goes on and on. It will all the depend on the type of functionality and flexibility you want to have. I just throw down a box, transform it a bit and use a Points From Volume SOP to create some scattered points. Throw in a Sort SOP set to random to mix the points up a bit. I also want to randomize the scale of the frames so they vary in size. For this, I need to create a pscale attribute. I can use a Point Wrangle but an Attribute Randomize will also do the job. I set the Attribute name parameter to pscale and this part of my network is done.

Scattered points
Scattered points

 

Our networks so far
Our networks so far

Merging Our Geometry

The next thing I need to do is merge both networks together. This is a common approach you will see in Houdini scenes. You create objects in various networks and then just merge them in where you need them. Essentially, you are compartmentalizing your objects. If you are familiar with some programming concepts, you can think of this as an Object Oriented approach of sorts. Working this way allows you to work on separate sections of your scene without other objects or setups getting in the way. Some folks complain about jumping in and out of networks but it will depend on how you are accustomed to working.

In order to accomplish the merging of objects, you use the Object Merge SOP. Since I want to keep my frame geometry in its own network, I merge the frame into the image_planes network. All you have to really change in this node is select the object you want to import. The Object parameter is rather versatile. You can use wildcards for example to merge multiple pieces of geometry. You can say something like frame*. This will select all the geometry objects that start with frame. Here I select my single frame geometry.

Houdini Object Merge SOP
Houdini Object Merge SOP

 

Now for the mograph-like part. I drop down a Copy to Points SOP and wire the frame to the left input and the scattered points to the right input. This will copy my frames onto the points created by the volume. Instant Cloner Object! Notice the Object Merge also brings in the attributes and groups. You can see we have the face group which we will use for the images.

Copying geometry to points
Copying geometry to points

 

Copy to Points
Copy to Points

Image Setup

So here comes the tricky part. Well, not really tricky, just not as straight forward as using a Multi-Shader in Cinema 4D. All the images I’m using for the geometry are the same size and same format. They also follow a specific naming convention. This greatly simplifies things. If this wasn’t the case, it would take a bit more work. Definitely doable, but just more work. Originally, all my images were various sizes, formats, and names. However, a couple of lines of Bash and I was ready to rock! Setting up your images shouldn’t be a hard endeavor. You can use Adobe Bridge (ugh) to resize, format, and rename your images.

Images for planes
Images for texturing

Next thing to do is add a Material node right after the Copy to Points SOP. Also, create a Principled Shader and make sure Use Texture is checked on. If you leave it off, you will not see the textures. Leave the Texture parameter blank. This will be filled in programatically.

Use texture on Principled Shader
Use texture on Principled Shader

 

A Simple Random Texture Setup

The first pass of getting the different images on to the geometry will be quick and easy. It’s simple but less flexible. It just takes a simple expression. In the Material node, select your face group and set the shader. Then click on Overrides use local variables and create a Local Override by clicking on the plus sign. Choose the parameter that is going to be overridden. In this case, it’s the basecolor_texture. Remember if you are not sure of the name, just hover your mouse over the parameter to get the internal name. Hold down CTRL for a quicker pop up.

We need to write an expression in the String parameter to grab our images. I know I want an image for each primitive. I also know I have images named pic_1.jpg, pic_2.jpg, etc. We can use the primitive number, A.K.A. @primnum attribute to exploit this. Here is the expression we need and take note of the back ticks surrounding the main expression.

$HIP/tex/pic_`@primnum % 191`.jpg

That’s it! All this is doing is reaching into our folder where all the images are located and filling in the number in the image name. It’s using Modulo division to divide the @primnum by the number of images in the folder. If you have 191 primitives and 191 images, you have a 1:1 mapping. If you have more primitives than images, you’ll get images that repeat. The images should now populate the faces on the frames. Yay!

Setting up the Material for the multi-textures
Setting up the Material for the multi-textures

 

Multi-texture. Yay!
Multi-texture. Yay!

 

Again, this works for something quick and dirty but not too flexible. As far as the material for the border, I just add another Material node with its own shader and invert the group by using !face in the Group parameter.

Texturing the border
Texturing the border

 

A More Advanced Random Texture Setup

Let’s say you want to add some more randomness and the ability to shuffle the images around, change the number of images used, or number of points. We can do this with our trusted friend the Primitive Wrangle. We write a bit of VEX and create an attribute that we can then use in the material and create a few sliders to randomize the images and amount of points used. The reason I added the points option is because I wanted the ability to set the number of frames that appear in the scene.

In my first setup I used a Points from Volume to quickly create some points. In this setup I use a Scatter node which gives me the ability to set the number of points. I wanted to be able to switch between the two setups. I added a Switch node and my points parameter just references the value of the total number of points in the Scatter. Also got a bit fancy and disabled the points when using the Points from Volume setup since there is no total number of points parameter and enabled it when using the Scatter. Ok, on to the code!

Right after the Copy to Points node, I throw in a Wrangle. Make sure it is set to run over Primitives. Here is the VEX code to access the images in the folder:

int numPoints = chi("Points");
int numimgs = chi("Images");
float imgSeed = chf("Seed");

string dir = "$HIP/tex/pic_";

[email protected] = int(fit01(rand(@primnum * imgSeed), 0, numimgs) % numimgs) + numPoints;
[email protected] = sprintf("%s%i.jpg", dir, @base);

The first variable of type Int will be the number of points used. The second variable of type Int is for the number of images. The third variable of type Float is going to be the seed value. These will be used for our interface. If you are not sure how to create the parameters, have a look at this #QuickTip

int numPoints = chi("Points");
int numimgs = chi("Images");
float imgSeed = chf("Seed");

The fourth variable is the path to the location of our images folder and partial name of the images. Notice it’s of type String and I’m using the $HIP environmental variable. Houdini will automatically expand the variable for us. We will fill in the image number and extension in a bit.

string dir = "$HIP/tex/pic_";

I then create two attributes. The attribute @base of type Int will be the number returned from our expression. Before you start screaming at me, the reason I made this an attribute was to debug the code. This way, I am able to see the value in the Spreadsheet. Once everything is working, I can change it back to a regular variable. The second attribute @textureImg needs to be an attribute as opposed to a regular variable because I will use it in my Material node to access the images.

The next line of code takes the primitive number (@primnum) and multiplies it by the seed. We pass this in as an argument to the rand function.

rand(@primnum * imgSeed)

This will return a value from 0 – 1. However, we need values from 0 to the number of images in the folder. There are a couple of ways of doing this. One is using the fit01(float value, float nmin, float nmax) function. The other is just mathematically working it out. Either way will work. There are other fancy ways of doing this but I’m not a math genius so I’ll stick to the easy stuff.

Using the fit01() function we can say take the value of rand() and map it from 0 to numimgs.

fit01(rand(@primnum * imgSeed), 0, numimgs);

You can also take the value of rand(), multiply it by the max value minus the min value and then adding back the min value. I know, I know… I just go by what smarter people figured out before me.

rand(@primnum * imgSeed) * (numimgs - 0) + 0;

Next thing we need to do is use Modulo division just like we used in our quick and dirty expression and then add the number of points being used. Again, the reason for using Modulo is because we want our images to repeat if we end up with more frames than there are images.

fit01(rand(@primnum * imgSeed), 0, numimgs) % numimgs;

Before we add our in our numPoints and assign the final value to our @base attribute, we have to do one more thing to this line of code. Right now we are getting back Float values. This value needs to be an Int since we are dealing with the number of the image. Remember we are using the value to fill in the number in pic_1.jpg, pic_2.jpg, pic_3.jpg, etc. We need to cast this value to an Int by using the Int(number) function. This will drop out the fractional part of our value.

int(fit01(rand(@primnum * imgSeed), 0, numimgs) % numimgs);

We can now add the number of points and assign the results to our attribute.

[email protected] = int(fit01(rand(@primnum * imgSeed), 0, numimgs) % numimgs) + numPoints;

The only thing left to do is create the path to the image. So how in the world is this actually done? Some of you are thinking the concatenation function and that will work fine. However, VEX provides a nice way to do formatting. If you have done any C or command-line scripting, you will be familiar with sprintf(string format, ...). This function formats a string like the printf(const char *format, ...) function found in C or C++ but returns the result as a string instead of printing it.

[email protected] = sprintf("%s%i.jpg", dir, @base);

This is a good time to check the Spreadsheet and make sure you are getting the right values, especially for the @textureImg attribute.

Final Image Mapping

Now with the code out of the way, we attach a Material node after the Wrangle. We need two materials. A nice thing about this node is you can create multiple materials within one Material node. Just click the plus sign. The first material will be for the border of the frame. Set the shader in the Material parameter. Switch over to the second tab and follow the same setup used in the quick and easy version. Select the face group, make sure Overrides uses local variables is checked on and set basecolor_texture as the parameter that you will override.

Now, instead of writing out an expression, we just use the @textureImg attribute we created in our Wrangle. Cool! Just make sure you surround the attribute within back ticks.

Material Node with overrides
Material Node with overrides

 

You should now have a sea of frames with all your images mapped on and the ability to randomize the images, set the amount of images used and the ability to set the number of points/frames used. I really should have added the switch in the Wrangle but I got lazy.

VEX Code setup
VEX Code setup

 

Final setup
Final setup

 

This was a long one. A #longTip. Anyways, this is not the fanciest of things to do. I’m sure you rather be doing some curl noise but believe it or not, it is requested often. As you can see, you have lots of flexibility and I’m sure there are other more sophisticated ways and easier ways to do the same setup. Until next time my friends.

By the way, I did a few thing wrong here. I’m not going to correct them because I’m going to leave them for another post but maybe you can figure out my mistakes. It’s good troubleshooting. When you revisit projects, especially code, you see immediately your errors and wonder what the heck you were thinking.

Share:
990adjustments

990adjustments

I am a motion designer & developer based out of South Florida. When not designing or animating pixels, I wrangle some code. If all else fails, I watch Twilight Zone, I Love Lucy, or Three's Company reruns.