Automated randomly generated number decals. Script + procedural material.

0 votes
Date Updated: 

This post details how I set up an automated decal numbering system. A random number is assigned to a dummy object that matches a name convention and any decal objects attached to that dummy assume the same ID. The ID can be used for material assignment purposes, in this case it is to drive a sprite sheet that renders that number to a material.

At a minimum the script should be a good jump point for automating parts of procedural artwork.

As used in this image:

The vehicles were all instanced from an original with no other steps needed on the decals.

The script


  • Execute the script
  • Make a dummy object
  • Preface the name of the dummy object with Signage_ID, it now has a random ID between 1 and 999 (Object Properties > G-Buffer - Object ID)
  • Make some other objects, ie: decal planes
  • Link objects to the signage_id dummy, they automatically assume the same ID
  • Clone everything, new dummy object gets a new random ID


callbacks.removeScripts id:#autosignage
callbacks.addScript #nodeNameSet "onNodeNameSet()" id:#autosignage
callbacks.addScript #nodeCreated "onNodeCreated()" id:#autosignage
callbacks.addScript #nodeLinked "onNodeLinked()" id:#autosignage
function testName val =
	if matchPattern val pattern:"Signage_ID*" then
		return true
	return false
function assignID n id =
	n.gbufferChannel = id
	print ("node: " + + ", assigned id: " + id as string)
function onNodeNameSet = 
	n = callbacks.notificationParam();
	_node = n[3]
	_newName = n[2]
	if (classOf _node == Dummy AND testName _newName) then
		if (_node.gbufferChannel == 0) then
			assignID _node (random 1 999)
		for i in _node.children do
			assignID i _node.gbufferChannel
function onNodeCreated = 
	n = callbacks.notificationParam();
	if (classOf n == Dummy AND testName then
		assignID n (random 1 999)
function onNodeLinked = 
	n = callbacks.notificationParam();
	if (classOf n.parent == Dummy AND testName then
		assignID n n.parent.gbufferChannel

I'm using the gbufferChannel for ID because I am already using 'wire_color' as selection masks on this procedural material setup and later on I might use it as a group selector for randomising vehicle paint colours too.


Tut on how the decal plane material was set up:

I decided to draw the ID number straight onto the vehicles. To do that I built a simple sprite map system:

The number images are provided by one bitmap:

3DSMax has an OSL node called "Object ID" used to collect the ID numbers we have assigned to decal planes.

Next I needed to convert the ID into three separate digits so I made a new OSL script to do that:

shader IntSplit
[[ string help = "Splits input integer into 3 digits",
   string label= "IntSplit",
   string category = "Values" ]]
	int Input = 0 [[ string label = "Input" ]],
	output int I0 = 0,
	output int I1 = 0,
	output int I2 = 0,
	int i = Input;
	int d[3] = {0,0,0};
	int a = 0;
	while (i > 0) 
		int val = i % 10;
		d[a] = val;
		i = i / 10;
	I0 = d[0];
	I1 = d[1];
	I2 = d[2];

We are outputting whole numbers, but UVs operate in a 0-1 range, since our bitmap has ten columns I divide each input by ten.

The bitmap is piped through three UV transforms which each:
Set up the scaling: my decal planes are 3:1 ratio so I adjusted here to match
Set up the offsets for where each digit places

The UV translate number created earlier is then used to slide the bitmap horizontally to arrive at the given number.

Each transform then plugs into a layer map. I used checkerbox maps as a quick masking tool.


Hopefully this info dump helps someone in need.

Gallery of future images that will use this:


Application Version: 
3DSMax 2021. Maxscript should work in 2017
autosignage.ms1.13 KB
splitint.osl_.txt448 bytes


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
jjm1's picture

Sorry for not helping

Sorry for not helping sooner.

I don't currently have max installed so cant check the file.

Maybe the next person can be helped with a deeper description of the sprite setup.


This is using Arnold nodes and I cant remember their specifics but to recreate it in a generic sense with anything that has basic nodes:

Make a UV transform node for the number tiles texture. Manipulate the scale and position so the first tile (the number 0) is where you want the first number on your decal plane.

Clone the UV transform node twice and modify them to position the first tile where the second and third number on your decal plane should be.

Now I'm not sure exactly what I did, but the goal is to combine and trim using masks.

I configured simple 1 row, 2 column checker nodes as masks using size/offset etc. The mask in this setup is applied in the Layer node (the 'mix' input). There's different ways you can do this but the goal is to just mask out everything except the position of each number.

Make sure your layering setup isn't overwriting the first layer, I probably used a multiply blend mode.

You should now have a texture with 000 on it.

Next step is to offset each UV Transform with the (id number * 0.1) inputs. You may need a second UV Transform node but the Arnold UVTransform has an extra 'offset' input along with 'Transform' inputs we already used up.

The RGB conversion is so it converts to a vector and only changes the U coordinate, otherwise this 'Offset' input adjusts both U and V from memory.

brstarship's picture

About how to use it

Ok, I figured some answers.

1- OSL will works only in 3dsmax 2019 and newer versions

2- I had put the files and splintint.osl_.txt in wrong folders. The right ones are:

C:\Program Files\Autodesk\3ds Max 2019\scripts\Startup for

C:\Program Files\Autodesk\3ds Max 2019\Plugins\OSL for splintint.osl_.txt (which needs first to be renamed to splintint.osl)

3- I´m having a hard time trying to learn how to use the slate material editor to recreate the configurations indicated by JJM1, cause I can´t find the right settings on my side. Attached, what I have successfully replicated in the slate material editor (3dsmax 2019) Also attached the 3dsmax2019 file, case somebody is interested. I still don´t know why I can´t find the other configurations... :eyeroll:

My slate config:

3dsmax2019 file:

brstarship's picture

About how to use it

I´m a 3dsmax2016 user, and I was looking for a way to automatically generate the numbering of escape pods, when found your script. Looks it can do exactly what I need, but I´m having a hard time trying to use it. Maybe I´m a bit dumb, but I need some help here. I have some questions.
I put the and splintint.osl_.txt in the 3dsmax root directory (i.e C:\Program Files\Autodesk\3ds Max 2016\scripts). Is it correct?
I created the dummy, linked to a plane and applyed (as a test) the bitmap you provided. After the rendering I got the number 0123456789 printed on the plane, but it don´t changes when I clone the plane. I think I´m missing something here...
Why the number isn´t changing and how to put just 3 digits over the plane?
In advance, thank you!!


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.