So I have a package that has prefabs that I drag into my scene for mapping. I’m wondering what is the best way to go about changing colors and/or the material of the prefab? Will I need to create a scene folder and every time I add an object create new materials and attach them to the prefab?
If that’s the case, I’m thinking of creating an editor script which saves materials from individual objects into a corresponding scene folder for editing…
You can use a MaterialPropertyBlock to alter material properties on a speicific renderer, so you don’t have to create a copy of the material. Unity - Scripting API: MaterialPropertyBlock
But I don’t think the editor provides tools to make use of them. So you will probably still have to make an editor script.
I wrote an editor script that’s rough around the edges at the moment but works. One can just save this as GenerateUniqueMaterials.cs and attach it to their parent game object. Right now it creates unique materials for parent, child and subchildren objects of a game object. With this script I can now just add in prefabs quickly, hit the generate unique materials button and tweak each object individually:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
public class GenerateUniqueMaterials : MonoBehaviour {
}
[CustomEditor(typeof(GenerateUniqueMaterials))]
public class Editor_Inspectors_GenerateUniqueMaterials : Editor {
GenerateUniqueMaterials generateUniqueMaterials;
void OnEnable() {
generateUniqueMaterials = (GenerateUniqueMaterials)target;
}
public override void OnInspectorGUI() {
EditorGUILayout.BeginHorizontal ("box");
GUILayout.FlexibleSpace ();
if (GUILayout.Button ("Generate Unique Materials", GUILayout.Width (350f), GUILayout.MinWidth (0f))) {
if (EditorUtility.DisplayDialog ("Generate Unique Materials From Prefab", "Going to Attempt to Generate Unique Materials for this Prefab Object. Are you sure this is okay?", "Yes", "No")) {
// --------------------- BASE OBJECT -------------------------------
GameObject baseGameObject = generateUniqueMaterials.gameObject;
MeshRenderer meshRenderer = baseGameObject.GetComponent<MeshRenderer> ();
Material newMaterial = new Material (Shader.Find ("Standard"));
if (meshRenderer != null) {
newMaterial.CopyPropertiesFromMaterial (meshRenderer.sharedMaterial);
newMaterial.name = "Mat_" + baseGameObject.name + baseGameObject.GetInstanceID().ToString() + "_" + meshRenderer.sharedMaterial.name;
}
Debug.Log(newMaterial.name + " created........ : " + newMaterial);
AssetDatabase.CreateAsset (newMaterial, "Assets/" + newMaterial.name + ".mat");
//Assign our new material instead of the shared one.
meshRenderer.sharedMaterial = newMaterial;
// --------------------- BASE OBJECTS CHILDREN -------------------------------
foreach (Transform child in baseGameObject.transform) {
GameObject childGameObject = child.gameObject;
MeshRenderer childsMeshRenderer = child.gameObject.GetComponent<MeshRenderer> ();
Material childsNewMaterial = new Material (Shader.Find ("Standard"));
if (childsMeshRenderer != null) {
childsNewMaterial.CopyPropertiesFromMaterial (childsMeshRenderer.sharedMaterial);
childsNewMaterial.name = "Mat_" + childGameObject.name + childGameObject.GetInstanceID().ToString() + "_" + childsMeshRenderer.sharedMaterial.name;
}
Debug.Log ("childs new material created for " + childGameObject.name);
AssetDatabase.CreateAsset (childsNewMaterial, "Assets/" + childsNewMaterial.name + ".mat");
childsMeshRenderer.sharedMaterial = childsNewMaterial;
// -------------------- SUB CHILDREN --------------------------------
if (child.childCount > 0) {
foreach (Transform subChild in child) {
GameObject subChildGameObject = subChild.gameObject;
MeshRenderer subChildsMeshRenderer = subChild.gameObject.GetComponent<MeshRenderer> ();
Material subChildsNewMaterial = new Material (Shader.Find ("Standard"));
if (subChildsMeshRenderer != null) {
subChildsNewMaterial.CopyPropertiesFromMaterial (subChildsMeshRenderer.sharedMaterial);
subChildsNewMaterial.name = "Mat_" + subChildGameObject.name + subChildGameObject.GetInstanceID().ToString() + "_" + subChildsMeshRenderer.sharedMaterial.name;
}
Debug.Log ("childs new material created for " + childGameObject.name);
AssetDatabase.CreateAsset (subChildsNewMaterial, "Assets/" + subChildsNewMaterial.name + ".mat");
subChildsMeshRenderer.sharedMaterial = subChildsNewMaterial;
}
}
// ---------------------------------------------------------------------
}
// ----------------------------------------------------------------------------
}
}
GUILayout.FlexibleSpace ();
EditorGUILayout.EndHorizontal ();
}
}
This is a handy script. Though I would still advise you to use property blocks - having a unique material on every single object in the scene is dangerous for your fps in the long run (extra draw call for every object and all).
Rendering objects that use the same material but different material property blocks is much faster. I can see why you might like the idea of instancing better - that way you don’t have to make a script to actually edit the properties, but you might want to do some benchmark testing and make sure that your scene wont be too heavy to render.
I had trouble with draw calls though even though I would use prefab trees that all had the same shared materials…but I’m using a script from the asset store called ‘mesh combine’ that reduces the draw calls by combining the objects…I’m confused about the matter.
One question. Is it possible for me to check my objects as static if I use this method of cloning materials to have unique materials for objects that I need to be able to freely edit the material with?
So say I have two cubes that use the same material and I use my script to clone one of their materials into a unique one as I want that to have a unique meshrenderer mode, color, texture etc… Can I then just take those two cubes, check static, and have them on the same draw call to save on performance, even though they have different textures?
You can have two objects with different textures on the same draw call (with material property blocks assigned to their renderers), but it’s not the case for two objects with different materials.
Each material is an extra draw call, even if two materials are using the same shader (which again, can be solved with property blocks). If two materials are using different shaders, then there is nothing you can do about them getting split into 2 draw calls.
I must ask, are you sure you want unique shader properties on every object?.
I mean, unique color - do you actually want different colors on every object instead of setting up a palette of, say, 4 colors per material or smth? Because the first option sounds like bad art direction. Unique texture - exactly how many textures do you have, is there even enough to have a different one on every object, even if you multiply them by colors count? Unique mesh renderer mode - actually, I think with this one the extra draw call is inevitable, may be I’m wrong…
May be you could get by with just manually cloning some materials to create enough variations? Probably even save them as extra prefabs for easier workflow, that sounds more reasonable to me. (I may be totally wrong here since I have no idea what are you working on).
After all, there is nothing wrong with having multiple draw calls per frame, it only becomes an issue when every object has it’s own one.
And about marking stuff static - it wont batch two different materials into a single draw call, but you should probably still check everything, that remains static, static, for other reasons.