How to Change Individual Object Colors with One Material in Unity Using MaterialPropertyBlock
Invalid Date Invalid Date Invalid Date (Today)
When building games in Unity, you often want multiple objects to share the same base material while displaying distinct base colors—such as visual hit reactions, tint variations, or team indicators.
Directly modifying Renderer.material.color forces Unity to instantiate an in-memory clone of the material asset, quickly inflating RAM usage and breaking GPU batching. Conversely, using Renderer.sharedMaterial modifies the core asset project-wide, turning every linked object the same color.
The optimal, performant solution is to use MaterialPropertyBlock.
Why Avoid renderer.material?
renderer.sharedMaterial: Directly alters the source material in your project assets. Every object sharing the material updates simultaneously, and changes can persist into the editor.renderer.material: Instantiates a duplicate copy of the material in RAM for that specific object. While it isolates the color change, having dozens or hundreds of clones increases draw calls and degrades GPU performance.MaterialPropertyBlock: Passes lightweight, per-renderer parameter overrides directly to the GPU while retaining a single shared material instance in memory.
Method Comparison
| Method | Memory Footprint | Draw Call / Batching Impact | Best Use Case |
|---|---|---|---|
sharedMaterial | Lowest (1 Asset) | Fast | Uniform, global material adjustments. |
.material | High (1 Clone per Object) | Slower (Breaks Batching) | Quick prototyping only; avoid in production loops. |
MaterialPropertyBlock | Low (1 Asset + GPU Overrides) | Fast (Supports GPU Instancing) | Individual tinting, runtime hit flashes, varied object colors. |
Implementation
Attach this script to any GameObject with a Renderer component to adjust its color upon collision without duplicating the material asset.
using UnityEngine;
public class ObjectHit : MonoBehaviour
{
private Renderer _renderer;
private MaterialPropertyBlock _propBlock;
void Awake()
{
_renderer = GetComponent<Renderer>();
_propBlock = new MaterialPropertyBlock();
}
private void OnCollisionEnter(Collision collision)
{
Debug.Log("Ouch! I crashed into: " + collision.gameObject.name);
if (collision.gameObject.CompareTag("Player"))
{
// Scale and reposition effect
transform.localScale *= 0.9f;
float nudgeAmount = transform.localScale.y * 0.1f;
transform.position += Vector3.up * nudgeAmount;
// Apply unique color override
ChangeColor(Color.red);
}
}
public void ChangeColor(Color newColor)
{
// 1. Fetch the current property block overrides from the renderer
_renderer.GetPropertyBlock(_propBlock);
// 2. Assign the color property
// Note: Use "_BaseColor" for URP/HDRP or "_Color" for the Built-in Render Pipeline
_propBlock.SetColor("_BaseColor", newColor);
// 3. Apply the updated property block back to the renderer
_renderer.SetPropertyBlock(_propBlock);
}
}
Key Shader Property Names
Ensure you target the correct shader variable for your project's active render pipeline:
- Universal Render Pipeline (URP) & HDRP:
_BaseColor - Built-in Render Pipeline (Standard Shader):
_Color