Material Property Blocks

It’s been a while since my last blog post. Hopefully, I will post more frequently from now on (no promises though).

If you already know what a material property is, great! You can ignore this post, and download the latest version of ExtraTools which has a handy shortcut. If not then read this article: https://thomasmountainborn.com/2016/05/25/materialpropertyblocks. It explains it very well.

I’m not gonna reiterate what Thomas says in that article, but the TL;DR version is as follows:

Using renderer.material.color is bad. It creates a new instance for each renderer this method is called on. This means more memory, more garbage, and more draw calls. The solution is to use something called MaterialPropertyBlock. Here is how it looks in code:

using UnityEngine;

public class PropertyBlockExample : MonoBehaviour
{
    [SerializeField] private Renderer rend;

    private MaterialPropertyBlock _propertyBlock;

    private void Awake()
    {
        // Make sure to initialize it.
        // Usually it needs to be initialized only once.
        _propertyBlock = new MaterialPropertyBlock(); 
    }

    private void Start()
    {
        SetColor("_MainColor", Color.black);
    }

    private void SetColor(string colorPropertyName, Color color)
    {
        rend.GetPropertyBlock(_propertyBlock); // Get previously set values. They will reset otherwise
        _propertyBlock.SetColor(colorPropertyName, color);
        rend.SetPropertyBlock(_propertyBlock);
    }
}

The property block can be reused and doesn’t need to be created each time you want to change a property.

It is easy enough, but wouldn’t it be great if we had a method like a renderer.Set(“PropertyName”, propertyValue)? Yes, of course, it would! So here is the extension method I’ve added to the ExtraTools.Utils class:

private static MaterialPropertyBlock _materialProperty;
// Create a new property block and assign to _materialProperty or return if one already exists
private static MaterialPropertyBlock MaterialPropertyBlock => _materialProperty ??= new MaterialPropertyBlock();

public static void SetProperty(this Renderer renderer, int propertyHash, Color value, int materialIndex = 0)
{
  	// Check if we are trying to acces an existing material
    if (materialIndex >= renderer.materials.Length)
    {
        Debug.LogWarning(
            $"Renderer {renderer.name} has {renderer.materials.Length.ToString()} materials. You are trying to access {materialIndex}. material.");
        return;
    }

    renderer.GetPropertyBlock(MaterialPropertyBlock, materialIndex);

    MaterialPropertyBlock.SetColor(propertyHash, value);

    renderer.SetPropertyBlock(MaterialPropertyBlock);
}

Fantastic! There is only one small thing that is not really about property blocks themselves. Perhaps you already noticed it. If you look closely at the method signature you will notice that I’m not using a property name, but rather a property hash. If you don’t know what a hash is definitely check it out. In short, you can write a method that would convert a string to an int. And there are a lot of reasons you would want to do it but I’m not gonna talk about them now. For shaders, you can use the one in the Shader class. In fact, I would suggest you do that because Unity does that every time you call SetColor (or a similar method) with a string. Combine that with the Constants class and your code will be perfect!

As usual, check out the ExtraTools repo (or get the ExtraTools as a package) and let me know if I made a mistake or could’ve done something better. See you in the next one!

2 thoughts to “Material Property Blocks”

Leave a Reply

Your email address will not be published. Required fields are marked *