I was browsing the subreddit /r/Unity3D yesterday and came across this post from /u/HyperParticles about batching objects with different colors which inspired me to share a trick I have used on many projects including Acorn Assault: Rodent Revolution for just this issue.
Unity is pretty good at batching like objects so that you reduce your draw calls and improve rendering performance. If you are unfamiliar with draw calls, read this article from Unity for more info.
So here is the scenario… You have a scene with nine identical objects with identical scale and all share the same material. Each of these objects colors are modified in script by calling something like “GetComponent<MeshRenderer>.material.color = myUniqueColor;”. All nine of these objects should be able to be batched and rendered at the same time, right? Well, not so fast. You see, when you modify the MeshRenderer’s material object, internally it will create a separate material for that object so the properties can be unique to it. Since this object is now using its own material it must now be rendered separately and now you have nine draw calls instead of one.
So what is the solution? Well, there is more than one way to skin this proverbial cat (don’t skin real cats), but what I prefer to use is vertex colors. You may be asking what a vertex color is. Well, for starters, vertices (plural form of vertex) are the points in space on a 3D model that define its polygons. Vertices can carry more information than just position and this is exactly what we are going to exploit to achieve draw call nirvana!
First, lets illustrate our problem. I have created a scene with nine cubes spread out in an array each with the script MaterialColorRandomizer attached to them. When I press play in the editor, each object will be given a unique color by setting the color property of the material that belongs to the MeshRenderer component. The script looks like this:
using UnityEngine;
public class MaterialColorRandomizer : MonoBehaviour {
private void Awake()
{
// Generate a random color
Color newColor =
new Color(Random.Range(0f, 1f),
Random.Range(0f, 1f),
Random.Range(0f, 1f));
// Apply the color to the material
GetComponent< MeshRenderer >().material.color = newColor;
}
}
Next we need to press play on the editor to run the script on each of the cubes. You will see each one with a unique color. Next we need to open the frame debugger window to see what is being sent to the GPU.
You can clearly see in the Frame Debug window that each mesh cube is being rendered separately. Each line labeled “Draw Mesh Cube” is one of our cubes being sent up to the GPU.
Here is where the magic happens. Lets remove MaterialColorRandomizer from all of the cubes and lets get crackin’ on a new script. This one will be called VertexColorRandomizer and it does just as it says.
using UnityEngine;
public class VertexColorRandomizer : MonoBehaviour {
private void Awake()
{
// Create a random color
Color32 newColor =
new Color32(
(byte)Random.Range(0, 256),
(byte)Random.Range(0, 256),
(byte)Random.Range(0, 256),
255);
// Get the mesh filter
Mesh mesh = GetComponent< MeshFilter >().mesh;
// Create an array of colors matching the same length as the mesh's color array
Color32[] newColors = new Color32[mesh.vertices.Length];
for (int vertexIndex = 0; vertexIndex < newColors.Length; vertexIndex++)
{
newColors[vertexIndex] = newColor;
}
// Apply the color
mesh.colors32 = newColors;
}
}
Now switch back to Unity and apply the script to each of the cubes. You will notice that if you click play now, the cubes will keep their Unity standard stark white color. The reason for this is because the standard shader does not take into account vertex color. Now, it would be nice if Unity included a standard shader with vertex color support in the future, but for now we have to get our hands just a little dirty. I also could go into detail into how to modify the default shader to include support for vertex colors, but instead I will leverage a shader uploaded by Jeff-Rossenberg on this thread. Go ahead and create a new shader by right-clicking the project panel and selecting Create -> Shader -> Standard Surface Shader and give it the name of StandardVertex. Open up the file and replace all the text with the shader code provided by Jeff-Rossenberg.
Now you will need to create a material that uses your new shader. Right click the project panel again and select Create -> Material and give that a name of VertexColorMaterial. Then, with the material selected in the project panel, change the shader to “Custom/StandardVertex”. Then you will need to apply the material to all nine cubes in the scene.
Once you are done with that, go ahead and click play and open up the Frame Debug window like before.
Look at that! One single draw call for all nine of our uniquely colored cubes! Now, in our example, you will never see any real world performance benefit since we took nine draw calls down to one. However, if you apply this method in places where there can be hundreds or thousands of objects that share a material but need to have different color variations, this will save you big time. In fact, this is one of the tricks that the particle system uses in order to give each particle a unique color without killing your frame-rates.
Now you know my secret draw call minimizing technique. Well… it’s not so much a secret but more of an intermediate game developer tool. So go forth and reduce your draw calls! How do you plan on using this technique?
Download the Unity 2017 sample project here: Vertex Shader Example Project