Introduction
Instancing is a very useful tool, not least because you can have one material setup that gives each instance an unique look (based on Instance ID). The Array Modifier is another useful tool, and it is similar to instancing (duplicating meshes), but it doesn’t give you the same options to control the material setup per copy in the same way1.
You could use an Array Modifier to distribute faces (or vertices) that you then use for instancing objects (which material you then can control with Instance ID), but it might not be that intuitive to setup, you have to have two objects (instead of one), etc. So, wouldn’t it be great if you could get something like Instance IDs for a normal Array Modifier setup, and in fact: You can!
Here I’ll go through how to get an (sequential) “Instance ID” with the Array Modifier (in several different ways), as well as a (pseudo) random ID.
This is aimed towards Octane (and Blender), but a similar setup will work with any render engine.
Please note that these instructions are written with the aim to help a beginner, if you’re a more confident Blender user you could probably just skim through the text (and/or look at the provided examples/screenshots) to get the gist of it.
Setting up the object and modifier
In this example we will assume that we are setting up a simple grid (two Array Modifiers, one for X, one for Y), but this method can be used in more complex setups.
The first step is to set up the Array Modifier(s)2, where we need to change the U and V offsets under the UVs drop down. For one of the modifiers (eg. the “X-axis modifier”) we are going change the U offset, and for the other the V offset (leaving the other offset at 0).
(It is recommended to have positive offset values)
But, change it to what? The easiest is just to set it to 1.0. This will have the least impact on the UV map (and any consideration that you have to take in the material setup).
You could also set it to a fraction based on how many instances you have in each direction (1 / [number of instances]). So for 10 instance the offset would be 0.1, for 20 it would be 0.05 etc.
But you also then have to have a UV map that is scaled down accordingly (eg. scaled by 0.1) and positioned in the lower left corner in the UV map. (Example setup).
The benefit of this is that you keep the UV coordinates within the 0.0 – 1.0 range, the negative being that the offsets affects all UV maps for the object.
Setting up the shader
Now when we have the object set up, we need to set up the shader. It’s pretty straightforward, but there’s some slight differences depending on whether you want to use an image texture, or an OSL script, and whether you want a random or a sequential value.
OSL vs image texture
An OSL (Open Shading Language) script is a way to have custom procedural textures (and more). Using OSL have similar benefits to procedural textures (no resolution limit, not locked down to a certain set texture), but it also have some disadvantages (like not working in Eevee or Cycles CUDA rendering).
And, while an image texture will work with any render engine, they are limiting: You can only have 255 different values3 (while the there isn’t really a limit when using OSL4), and the grid shape/size is set5.
Setting up with OSL
First we need to add an (OSL) script node (in the Shader Editor):
- With Octane: [Shift+A] > Octane Texture > OSL Texture
- With Cycles: [Shift+A] > Script > Script (NB: OSL doesn’t work in Cycles when using CUDA)
For both Octane and Cycles you can use either an internal (“text block”) or external script file, where external file is the easier option. If you’re setting this up for Cycles the script should load as soon as you’ve selected the file (if it doesn’t try clicking on the Update button), with Octane you need to click the Compile OSL Node before you can use the script.
You can find the OSL script file(s) below.
You should now have a node that looks like the example to the right6. Change the Columns and Row to match your Array Modifier setup. If you have chosen to use the Full UV (and set the U/V offsets to 1.0) you should also enable “Use Full UV” on the OSL node (likewise, make sure it’s disabled if you have chosen to scale down the UV map).
If you have set up a different UV map for this you should add a Mesh UV projection node (for Octane, and a UV map [input] node for Cycles) and connect it to the Projection input of the OSL node, and change UV set to the appropriate index. (You don’t have to add a UV projection node if you’re using the default UV map).
Now you’re ready to use(/connect) the Instance ID as you see fit (in the examples we’ve simply connected it to a Gradient map/Color Ramp, which gives each “instance” a unique color)
Random values
Now that we have the OSL setup, it’s pretty easy to switch to get random values (or “instance IDs” instead of sequential. Here we have two different scripts that we can use: instance_grid_random.osl or noise_grid.osl. The first one is a variant of the instance grid script, and thus keeps the same options (and ads a Time option), the second is a simpler script that generates patches with random values. With the noise_grid script you need to set the X/Y Scale to match the number of columns and rows of your array modifiers, if you have chosen to have scaled/”fractional” UV (otherwise leave it at 1.0 for the best result)
The Time option is basically a seed value, letting you select the randomized pattern that looks the best to you, or you can animate it, having the random values change over time.
Setting up with image textures
To set up “instance IDs” with an image texture we first need the image texture itself. I have provided some pre-made image textures, but there might not be a texture that works with your setup7. So what do you do then?
You could make the texture by hand in a photo editor, but that is a tedious task, instead you could generate the textures automatically.
Generating textures with Python
If you’re comfortable with using Python scripts, you can use the simple scripts that I’ve written to generate the image textures needed. (You need the PIL/Pillow library). There is one script for sequential “instance IDs”, and one for random IDs.
The scripts are quite simple, and takes no command line arguments, so you need to edit the scripts if you want to change the resolution of the generated image, or how many columns/rows it should contain. (NB: For sequential values you shouldn’t go beyond 256 patches, eg. 16×16, 8×32, 64×4).
The node setup
This isn’t really any different from using any other image texture. If you are using Octane, it is recommended to use the Grayscale Texture (aka Float Image Tex) node.
You should also set the Gamma to 1.0 (for Octane), and the Color Space to Linear or Non-Color (for Cycles).
The caveat here is the scaling of the texture:
If you have setup your mesh UV and Array modifier to use “fractional UV”, you should leave the scaling as 1.0 (and translation [or location] as 0.0, or just don’t add a transform node at all).
If you’re using the full UV, you need to scale, and translate, the texture according to your settings:
Octane
Set the X scaling to match the number of columns, and the Y scaling to match the number of rows. Then you need to set the translation to half the number of columns and rows (X and Y translation respectively) minus 0.5 (so for 16 columns set X to 7.5 [16/2 – 0.5], for 10 rows set Y to 4.5 [10/2 – 0.5]).
Cycles
Set the X scaling to 1/[number of columns], and the Y scaling to 1/[number of rows] (with 16 columns or rows set X or Y to 1/16, i.e. 0.0625).
Random values
The only thing you need to change to get random values/”IDs” is to use an image with randomized texture. This is of course not truly random, but neither is the procedural/OSL generated values.
Using an image texture that doesn’t match the array modifier setup
It’s best to have an image texture where the patches in the texture matches the columns and rows in your setup. Even so, you can have a texture with a different number of rows or columns than your Array modifier setup, BUT you should then set the UV offset in the Array modifier (if you use “fractional UV”), or the scale and translation (if you’re using the full UV), to match the columns and rows in the texture, not the array modifiers.
If the image texture has more columns or rows than your setup you will get gaps in the sequence of instance IDs (eg. last on one row could have “ID” 8, and the first one on the next row will have ID 17). For random values this doesn’t really matter.
If the image has less columns or rows you will get repeats (which could be noticeable with both sequential and random values).
Files
OSL script files
instance_grid.osl (Google drive / Gist) – OSL-script that generate the “instance IDs”.
instance_grid_random.osl (Google Drive / Gist) – OSL-script that generates a random (greyscale) value per “instance” (based on instance_grid.osl).
noise_grid.osl (Google drive / Gist) – OSL-script that generates a random (greyscale) value per “instance”.
Image texture files
Image generation Python scripts
imggen_sequence.py (Google Drive) – Python script to generate an image texture (PNG) with a grid of sequential values.
imggen_randompatches.py (Google Drive) – Python script to generate an image texture (PNG) with a grid of random values.
Example .blend files
Footnotes
- Neither does the Array Modifier give you the memory savings instancing does, but we’re going to ignore that for this discussion. ↩︎
- This method works best with 1 or 2 Array Modifiers, mostly because it uses UV coordinates. It can be used with more, but it might be as straight forward ↩︎
- It needs to be greyscale, and with 8 bit images that means 255 values. I’m looking in to generating 16 bit images. ↩︎
- The maximal number of values between 0.0 and 1.0 is more than 16 million ↩︎
- This isn’t necessarily a deal breaker, you could generate a new image, or use one that’s bigger than you need (you’ll just get some gaps in the sequence). ↩︎
- The example is from an Octane setup, it will look similar with Cycles. NB: The order of the options might differ. ↩︎
- You can use a texture with more columns/rows than you need, but you might have gaps in the sequential “IDs”. ↩︎