Search Unity

Clear old texture references from materials

Discussion in 'Shaders' started by alexandre-fiset, Apr 14, 2015.

  1. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    715
    Hi there,

    Does anybody know if it is possible to "force clear" old references from materials?

    Ex: If I assign a texture to the Normal Map slot of a normal mapped material, and then switch to a Diffuse-only shader, the old normal map will still be loaded along with the scene.

    This takes space and when the project is big it becomes really difficult to maintain.
     
    reedreedchen likes this.
  2. Neyl

    Neyl

    Joined:
    Feb 3, 2011
    Posts:
    18
    Solution... Or you can switch inspector to debug mode and remove old properties manually.
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. namespace CrankshaftEditor.Toolset
    5. {
    6.     public class ReferencesCleaner : EditorWindow
    7.     {
    8.         private Material m_selectedMaterial;
    9.         private SerializedObject m_serializedObject;
    10.  
    11.         [MenuItem("Tools/ReferencesCleaner")]
    12.         private static void Init()
    13.         {
    14.             GetWindow<ReferencesCleaner>("Ref. Cleaner");
    15.         }
    16.  
    17.         protected virtual void OnEnable()
    18.         {
    19.             GetSelectedMaterial();
    20.         }
    21.  
    22.         protected virtual void OnSelectionChange()
    23.         {
    24.             GetSelectedMaterial();
    25.         }
    26.  
    27.         protected virtual void OnProjectChange()
    28.         {
    29.             GetSelectedMaterial();
    30.         }
    31.  
    32.         protected virtual void OnGUI()
    33.         {
    34.             EditorGUIUtility.labelWidth = 200f;
    35.  
    36.             if (m_selectedMaterial == null)
    37.             {
    38.                 EditorGUILayout.LabelField("No material selected");
    39.             }
    40.             else
    41.             {
    42.                 EditorGUILayout.Space();
    43.                 EditorGUILayout.LabelField("Selected material:",m_selectedMaterial.name);
    44.                 EditorGUILayout.LabelField("Shader:", m_selectedMaterial.shader.name);
    45.                 EditorGUILayout.Space();
    46.                 EditorGUILayout.LabelField("Properties");
    47.  
    48.                 m_serializedObject.Update();
    49.  
    50.                 EditorGUI.indentLevel++;
    51.  
    52.                 EditorGUILayout.LabelField("Textures");
    53.                 EditorGUI.indentLevel++;
    54.                 ProcessProperties("m_SavedProperties.m_TexEnvs");
    55.                 EditorGUI.indentLevel--;
    56.                
    57.                 EditorGUILayout.LabelField("Floats");
    58.                 EditorGUI.indentLevel++;
    59.                 ProcessProperties("m_SavedProperties.m_Floats");
    60.                 EditorGUI.indentLevel--;
    61.  
    62.                 EditorGUILayout.LabelField("Colors");
    63.                 EditorGUI.indentLevel++;
    64.                 ProcessProperties("m_SavedProperties.m_Colors");
    65.                 EditorGUI.indentLevel--;
    66.  
    67.                 EditorGUI.indentLevel--;
    68.             }
    69.  
    70.             EditorGUIUtility.labelWidth = 0;
    71.         }
    72.  
    73.         private void ProcessProperties(string path)
    74.         {
    75.             var properties = m_serializedObject.FindProperty(path);
    76.             if (properties != null && properties.isArray)
    77.             {
    78.                 for (int i = 0; i < properties.arraySize; i++)
    79.                 {
    80.                     string propName = properties.GetArrayElementAtIndex(i).FindPropertyRelative("first.name").stringValue;
    81.                     bool exist = m_selectedMaterial.HasProperty(propName);
    82.  
    83.                     if (exist)
    84.                     {
    85.                         EditorGUILayout.LabelField(propName, "Exist");
    86.                     }
    87.                     else
    88.                     {
    89.                         using (new EditorGUILayout.HorizontalScope())
    90.                         {
    91.                             EditorGUILayout.LabelField(propName, "Old reference", "CN StatusWarn");
    92.                             if (GUILayout.Button("Remove", GUILayout.Width(80f)))
    93.                             {
    94.                                 properties.DeleteArrayElementAtIndex(i);
    95.                                 m_serializedObject.ApplyModifiedProperties();
    96.                                 GUIUtility.ExitGUI();
    97.                             }
    98.                         }
    99.  
    100.                     }
    101.                 }
    102.             }
    103.         }
    104.  
    105.         private void GetSelectedMaterial()
    106.         {
    107.             m_selectedMaterial = Selection.activeObject as Material;
    108.             if (m_selectedMaterial != null)
    109.             {
    110.                 m_serializedObject = new SerializedObject(m_selectedMaterial);
    111.             }
    112.  
    113.             Repaint();
    114.         }
    115.     }
    116. }
     

    Attached Files:

  3. lrb

    lrb

    Joined:
    Jun 21, 2014
    Posts:
    28
    This makes no sense.
    I work with Unity in Standalone and Mobile project for a long time, change shader material all the time.
    Now this is the first time I have this problem. There is no reason to put textures in the Build just because in the past they were used.

    Sure this is a bug.
    Do anyone have any updates about that?
     
  4. Seanm07

    Seanm07

    Joined:
    Mar 18, 2014
    Posts:
    12
    It won't actually build the old textures into a build, they're only stored for editor usage so the reference is still there if you decide to switch back to that shader (or another shader which uses that property)

    The only time this will really be noticed is if you're taking a memory sample in the editor, you'll notice textures/cubemaps being listed which may not be in use anymore https://i.imgur.com/EJzbeFi.png

    And setting the inspector to debug mode shows that the texture is a saved property https://i.imgur.com/6i4M2pz.png

    It's still nice to cleanup the editor memory references though.
     
  5. Seanm07

    Seanm07

    Joined:
    Mar 18, 2014
    Posts:
    12
    Also .FindPropertyRelative("first.name") doesn't seem to be working in the latest version of Unity anymore (using 2017.4.0f1) it just returns null. But it can simply just be replaced with .displayName

    Here's the fixed version of Neyl's script working with Unity 2017.4.0f1:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. namespace CrankshaftEditor.Toolset
    5. {
    6.     public class ReferencesCleaner : EditorWindow
    7.     {
    8.         private Material m_selectedMaterial;
    9.         private SerializedObject m_serializedObject;
    10.  
    11.         [MenuItem("Tools/ReferencesCleaner")]
    12.         private static void Init()
    13.         {
    14.             GetWindow<ReferencesCleaner>("Ref. Cleaner");
    15.         }
    16.  
    17.         protected virtual void OnEnable()
    18.         {
    19.             GetSelectedMaterial();
    20.         }
    21.  
    22.         protected virtual void OnSelectionChange()
    23.         {
    24.             GetSelectedMaterial();
    25.         }
    26.  
    27.         protected virtual void OnProjectChange()
    28.         {
    29.             GetSelectedMaterial();
    30.         }
    31.  
    32.         protected virtual void OnGUI()
    33.         {
    34.             EditorGUIUtility.labelWidth = 200f;
    35.  
    36.             if (m_selectedMaterial == null)
    37.             {
    38.                 EditorGUILayout.LabelField("No material selected");
    39.             }
    40.             else
    41.             {
    42.                 EditorGUILayout.Space();
    43.                 EditorGUILayout.LabelField("Selected material:",m_selectedMaterial.name);
    44.                 EditorGUILayout.LabelField("Shader:", m_selectedMaterial.shader.name);
    45.                 EditorGUILayout.Space();
    46.                 EditorGUILayout.LabelField("Properties");
    47.  
    48.                 m_serializedObject.Update();
    49.  
    50.                 EditorGUI.indentLevel++;
    51.  
    52.                 EditorGUILayout.LabelField("Textures");
    53.                 EditorGUI.indentLevel++;
    54.                 ProcessProperties("m_SavedProperties.m_TexEnvs");
    55.                 EditorGUI.indentLevel--;
    56.              
    57.                 EditorGUILayout.LabelField("Floats");
    58.                 EditorGUI.indentLevel++;
    59.                 ProcessProperties("m_SavedProperties.m_Floats");
    60.                 EditorGUI.indentLevel--;
    61.  
    62.                 EditorGUILayout.LabelField("Colors");
    63.                 EditorGUI.indentLevel++;
    64.                 ProcessProperties("m_SavedProperties.m_Colors");
    65.                 EditorGUI.indentLevel--;
    66.  
    67.                 EditorGUI.indentLevel--;
    68.             }
    69.  
    70.             EditorGUIUtility.labelWidth = 0;
    71.         }
    72.  
    73.         private void ProcessProperties(string path)
    74.         {
    75.             var properties = m_serializedObject.FindProperty(path);
    76.             if (properties != null && properties.isArray)
    77.             {
    78.                 for (int i = 0; i < properties.arraySize; i++)
    79.                 {
    80.                     string propName = properties.GetArrayElementAtIndex(i).displayName;
    81.                     bool exist = m_selectedMaterial.HasProperty(propName);
    82.  
    83.                     if (exist)
    84.                     {
    85.                         EditorGUILayout.LabelField(propName, "Exist");
    86.                     }
    87.                     else
    88.                     {
    89.                         using (new EditorGUILayout.HorizontalScope())
    90.                         {
    91.                             EditorGUILayout.LabelField(propName, "Old reference", "CN StatusWarn");
    92.                             if (GUILayout.Button("Remove", GUILayout.Width(80f)))
    93.                             {
    94.                                 properties.DeleteArrayElementAtIndex(i);
    95.                                 m_serializedObject.ApplyModifiedProperties();
    96.                                 GUIUtility.ExitGUI();
    97.                             }
    98.                         }
    99.  
    100.                     }
    101.                 }
    102.             }
    103.         }
    104.  
    105.         private void GetSelectedMaterial()
    106.         {
    107.             m_selectedMaterial = Selection.activeObject as Material;
    108.             if (m_selectedMaterial != null)
    109.             {
    110.                 m_serializedObject = new SerializedObject(m_selectedMaterial);
    111.             }
    112.  
    113.             Repaint();
    114.         }
    115.     }
    116. }
     
    IlyaSakhatskiyWG and Kevin-VFX like this.
  6. Seanm07

    Seanm07

    Joined:
    Mar 18, 2014
    Posts:
    12
    This thread is top of google for clearing unused references from a material so here's an update I made to the script which adds support for selecting multiple materials at once and removing unused references from all selected.

    Tested and working in the Unity 2019.4 LTS

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. public class ReferencesCleaner : EditorWindow {
    6.     private List<Material> m_selectedMaterials = new List<Material>();
    7.     private SerializedObject[] m_serializedObjects;
    8.  
    9.     [MenuItem("Tools/ReferencesCleaner")]
    10.     private static void Init() {
    11.         GetWindow<ReferencesCleaner>("Ref. Cleaner");
    12.     }
    13.  
    14.     protected virtual void OnEnable() {
    15.         GetSelectedMaterials();
    16.     }
    17.  
    18.     protected virtual void OnSelectionChange() {
    19.         GetSelectedMaterials();
    20.     }
    21.  
    22.     protected virtual void OnProjectChange() {
    23.         GetSelectedMaterials();
    24.     }
    25.  
    26.     private Vector2 scrollPos;
    27.  
    28.     protected virtual void OnGUI() {
    29.         EditorGUIUtility.labelWidth = 200f;
    30.  
    31.         if (m_selectedMaterials == null || m_selectedMaterials.Count <= 0) {
    32.             EditorGUILayout.LabelField("No materials selected");
    33.         } else {
    34.             if (GUILayout.Button("Remove Old Texture References", GUILayout.Width(200f))) {
    35.                 for (int i = 0; i < m_selectedMaterials.Count; i++) {
    36.                     RemoveUnusedProperties("m_SavedProperties.m_TexEnvs", i);
    37.                 }
    38.  
    39.                 GUIUtility.ExitGUI();
    40.             }
    41.            
    42.             if (GUILayout.Button("Remove Old Float References", GUILayout.Width(200f))) {
    43.                 for (int i = 0; i < m_selectedMaterials.Count; i++) {
    44.                     RemoveUnusedProperties("m_SavedProperties.m_Floats", i);
    45.                 }
    46.  
    47.                 GUIUtility.ExitGUI();
    48.             }
    49.            
    50.             if (GUILayout.Button("Remove Old Color References", GUILayout.Width(200f))) {
    51.                 for (int i = 0; i < m_selectedMaterials.Count; i++) {
    52.                     RemoveUnusedProperties("m_SavedProperties.m_Colors", i);
    53.                 }
    54.  
    55.                 GUIUtility.ExitGUI();
    56.             }
    57.            
    58.             if (GUILayout.Button("Remove All Old References", GUILayout.Width(200f))) {
    59.                 for (int i = 0; i < m_selectedMaterials.Count; i++) {
    60.                     RemoveUnusedProperties("m_SavedProperties.m_TexEnvs", i);
    61.                     RemoveUnusedProperties("m_SavedProperties.m_Floats", i);
    62.                     RemoveUnusedProperties("m_SavedProperties.m_Colors", i);
    63.                 }
    64.  
    65.                 GUIUtility.ExitGUI();
    66.             }
    67.  
    68.             EditorGUILayout.BeginHorizontal();
    69.             scrollPos = EditorGUILayout.BeginScrollView(scrollPos, false, true);
    70.  
    71.             for (int i = 0; i < m_selectedMaterials.Count; i++) {
    72.                 Material m_selectedMaterial = m_selectedMaterials[i];
    73.  
    74.                 EditorGUILayout.Space();
    75.                 EditorGUILayout.LabelField("Selected material:", m_selectedMaterial.name);
    76.                 EditorGUILayout.LabelField("Shader:", m_selectedMaterial.shader.name);
    77.                 EditorGUILayout.Space();
    78.                 EditorGUILayout.LabelField("Properties");
    79.  
    80.                 m_serializedObjects[i].Update();
    81.  
    82.                 EditorGUI.indentLevel++;
    83.  
    84.                 EditorGUILayout.LabelField("Textures");
    85.                 EditorGUI.indentLevel++;
    86.                 ProcessProperties("m_SavedProperties.m_TexEnvs", i);
    87.                 EditorGUI.indentLevel--;
    88.  
    89.                 EditorGUILayout.LabelField("Floats");
    90.                 EditorGUI.indentLevel++;
    91.                 ProcessProperties("m_SavedProperties.m_Floats", i);
    92.                 EditorGUI.indentLevel--;
    93.  
    94.                 EditorGUILayout.LabelField("Colors");
    95.                 EditorGUI.indentLevel++;
    96.                 ProcessProperties("m_SavedProperties.m_Colors", i);
    97.                 EditorGUI.indentLevel--;
    98.  
    99.                 EditorGUI.indentLevel--;
    100.             }
    101.  
    102.             EditorGUILayout.EndScrollView();
    103.             EditorGUILayout.EndHorizontal();
    104.         }
    105.  
    106.         EditorGUIUtility.labelWidth = 0;
    107.     }
    108.  
    109.     private void RemoveUnusedProperties(string path, int i) {
    110.         var properties = m_serializedObjects[i].FindProperty(path);
    111.        
    112.         if (properties != null && properties.isArray) {
    113.             for (int j = properties.arraySize-1; j >= 0; j--) {
    114.                 string propName = properties.GetArrayElementAtIndex(j).displayName;
    115.                 bool exist = m_selectedMaterials[i].HasProperty(propName);
    116.  
    117.                 if (!exist) {
    118.                     properties.DeleteArrayElementAtIndex(j);
    119.                     m_serializedObjects[i].ApplyModifiedProperties();
    120.                 }
    121.             }
    122.         }
    123.     }
    124.  
    125.     private void ProcessProperties(string path, int i) {
    126.         var properties = m_serializedObjects[i].FindProperty(path);
    127.         if (properties != null && properties.isArray) {
    128.             for (int j = 0; j < properties.arraySize; j++) {
    129.                 string propName = properties.GetArrayElementAtIndex(j).displayName;
    130.                 bool exist = m_selectedMaterials[i].HasProperty(propName);
    131.  
    132.                 if (exist) {
    133.                     EditorGUILayout.LabelField(propName, "Exist");
    134.                 } else {
    135.                     using (new EditorGUILayout.HorizontalScope()) {
    136.                         EditorGUILayout.LabelField(propName, "Old reference", "CN StatusWarn");
    137.                         if (GUILayout.Button("Remove", GUILayout.Width(80f))) {
    138.                             properties.DeleteArrayElementAtIndex(j);
    139.                             m_serializedObjects[i].ApplyModifiedProperties();
    140.                             GUIUtility.ExitGUI();
    141.                         }
    142.                     }
    143.                 }
    144.             }
    145.         }
    146.     }
    147.  
    148.     private void GetSelectedMaterials() {
    149.         Object[] objects = Selection.objects;
    150.  
    151.         m_selectedMaterials = new List<Material>();
    152.  
    153.         for (int i = 0; i < objects.Length; i++) {
    154.             Material newMat = objects[i] as Material;
    155.  
    156.             if (newMat != null)
    157.                 m_selectedMaterials.Add(newMat);
    158.         }
    159.  
    160.         if (m_selectedMaterials != null) {
    161.             m_serializedObjects = new SerializedObject[m_selectedMaterials.Count];
    162.  
    163.             for (int i = 0; i < m_serializedObjects.Length; i++) {
    164.                 m_serializedObjects[i] = new SerializedObject(m_selectedMaterials[i]);
    165.             }
    166.         }
    167.  
    168.         Repaint();
    169.     }
    170. }
     
    AshwinMods likes this.
  7. PrecisionCats

    PrecisionCats

    Joined:
    Nov 19, 2017
    Posts:
    40
    I've improved the script
    - Added support for the new Integer properties
    - Checks property existence using the type of property (so that same name but different type properties will be cleaned up correctly)
    - Added button to transfer any matching float to integers, so that e.g. _StencilComp and _Stencil can easily be converted to Integer shader properties
    - Won't do anything if the shader is null/missing (instead of removing every property)
    - Repaints when undo/redo
    - Nicer GUI

    Code (CSharp):
    1. // https://forum.unity.com/threads/clear-old-texture-references-from-materials.318769/
    2.  
    3. #define UPDATING_FLOAT_TO_INT
    4.  
    5. using System.Collections.Generic;
    6. using UnityEngine;
    7. using UnityEditor;
    8.  
    9. public class MaterialPropertyCleaner : EditorWindow
    10. {
    11.     private const float REMOVE_BUTTON_WIDTH = 60;
    12.     private const float TYPE_SPACING = 4;
    13.     private const float SCROLLBAR_WIDTH = 15;
    14.     private const float MATERIAL_SPACING = 20;
    15.  
    16.     private List<Material> m_selectedMaterials = new List<Material>();
    17.     private SerializedObject[] m_serializedObjects;
    18.     private Vector2 scrollPos;
    19.     private GUIStyle warningStyle, errorStyle;
    20.  
    21.     [MenuItem("Tools/Material Property Cleaner")]
    22.     private static void Init()
    23.     {
    24.         GetWindow<MaterialPropertyCleaner>("Property Cleaner");
    25.     }
    26.  
    27.     protected virtual void OnEnable()
    28.     {
    29.         GetSelectedMaterials();
    30.  
    31.         Undo.undoRedoPerformed += OnUndoRedo;
    32.     }
    33.     private void OnDisable()
    34.     {
    35.         Undo.undoRedoPerformed -= OnUndoRedo;
    36.     }
    37.     private void OnUndoRedo()
    38.     {
    39.         Repaint();
    40.     }
    41.  
    42.     protected virtual void OnSelectionChange()
    43.     {
    44.         GetSelectedMaterials();
    45.     }
    46.  
    47.     protected virtual void OnProjectChange()
    48.     {
    49.         GetSelectedMaterials();
    50.     }
    51.  
    52.     protected virtual void OnGUI()
    53.     {
    54.         if (m_selectedMaterials == null || m_selectedMaterials.Count <= 0)
    55.         {
    56.             EditorGUILayout.LabelField("No Material Selected", new GUIStyle("LargeLabel"));
    57.         }
    58.         else
    59.         {
    60.             EditorGUIUtility.labelWidth = position.width * 0.5f - SCROLLBAR_WIDTH - 2;
    61.             GUIStyle typeLabelStyle = new GUIStyle("LargeLabel");
    62.             errorStyle = new GUIStyle("CN StatusError");
    63.             warningStyle = new GUIStyle("CN StatusWarn");
    64.  
    65. #if UPDATING_FLOAT_TO_INT
    66.             if (GUILayout.Button("Transfer Floats to Matching Ints"))
    67.             {
    68.                 for (int i = 0; i < m_selectedMaterials.Count; i++)
    69.                 {
    70.                     Material mat = m_selectedMaterials[i];
    71.  
    72.                     if (!HasShader(mat))
    73.                     {
    74.                         Debug.LogError("Material " + mat.name + " doesn't have a shader");
    75.                         continue;
    76.                     }
    77.  
    78.                     var floats = m_serializedObjects[i].FindProperty("m_SavedProperties.m_Floats");
    79.                     var ints = m_serializedObjects[i].FindProperty("m_SavedProperties.m_Ints");
    80.  
    81.                     if (floats != null && floats.isArray && ints != null && ints.isArray)
    82.                     {
    83.                         for (int intID = 0; intID < ints.arraySize; intID++)
    84.                         {
    85.                             var intProp = ints.GetArrayElementAtIndex(intID);
    86.                             string intPropName = GetName(intProp);
    87.                             if (!ShaderHasProperty(mat, intPropName, PropertyType.Float))
    88.                             {
    89.                                 for (int floatID = 0; floatID < floats.arraySize; floatID++)
    90.                                 {
    91.                                     var floatProp = floats.GetArrayElementAtIndex(floatID);
    92.                                     if (GetName(floatProp) == intPropName)
    93.                                     {
    94.                                         var intSecond = intProp.FindPropertyRelative("second");
    95.                                         var floatSecond = floatProp.FindPropertyRelative("second");
    96.                                         intSecond.intValue = Mathf.RoundToInt(floatSecond.floatValue);
    97.                                         floats.DeleteArrayElementAtIndex(floatID);
    98.                                         m_serializedObjects[i].ApplyModifiedProperties();
    99.  
    100.                                         Debug.Log("Transferred Float " + intPropName + " to Int of same name");
    101.                                     }
    102.                                 }
    103.                             }
    104.                             else
    105.                             {
    106.                                 Debug.LogError // This would happen if you revert from using an int to using a float again
    107.                                 (
    108.                                     "Material " + mat.name + " has an Int property " + intPropName +
    109.                                     " whereas the Shader " + mat.shader.name + " has it as a Float property.\n" +
    110.                                     "The Int material property should be cleaned away"
    111.                                 );
    112.                             }
    113.                         }
    114.                     }
    115.                 }
    116.                 GUIUtility.ExitGUI();
    117.             }
    118.  
    119.             EditorGUILayout.Space();
    120. #endif
    121.  
    122.             if (GUILayout.Button("Remove Old Texture References"))
    123.             {
    124.                 for (int i = 0; i < m_selectedMaterials.Count; i++)
    125.                     RemoveUnusedProperties("m_SavedProperties.m_TexEnvs", i, PropertyType.TexEnv);
    126.                 GUIUtility.ExitGUI();
    127.             }
    128.  
    129.             if (GUILayout.Button("Remove Old Int References"))
    130.             {
    131.                 for (int i = 0; i < m_selectedMaterials.Count; i++)
    132.                     RemoveUnusedProperties("m_SavedProperties.m_Ints", i, PropertyType.Int);
    133.                 GUIUtility.ExitGUI();
    134.             }
    135.  
    136.             if (GUILayout.Button("Remove Old Float References"))
    137.             {
    138.                 for (int i = 0; i < m_selectedMaterials.Count; i++)
    139.                     RemoveUnusedProperties("m_SavedProperties.m_Floats", i, PropertyType.Float);
    140.                 GUIUtility.ExitGUI();
    141.             }
    142.  
    143.             if (GUILayout.Button("Remove Old Color References"))
    144.             {
    145.                 for (int i = 0; i < m_selectedMaterials.Count; i++)
    146.                     RemoveUnusedProperties("m_SavedProperties.m_Colors", i, PropertyType.Color);
    147.                 GUIUtility.ExitGUI();
    148.             }
    149.  
    150.             if (GUILayout.Button("Remove All Old References"))
    151.             {
    152.                 for (int i = 0; i < m_selectedMaterials.Count; i++)
    153.                 {
    154.                     var mat = m_selectedMaterials[i];
    155.                     if (HasShader(mat))
    156.                     {
    157.                         RemoveUnusedProperties("m_SavedProperties.m_TexEnvs", i, PropertyType.TexEnv);
    158.                         RemoveUnusedProperties("m_SavedProperties.m_Ints", i, PropertyType.Int);
    159.                         RemoveUnusedProperties("m_SavedProperties.m_Floats", i, PropertyType.Float);
    160.                         RemoveUnusedProperties("m_SavedProperties.m_Colors", i, PropertyType.Color);
    161.                     }
    162.                     else
    163.                         Debug.LogError("Material " + mat.name + " doesn't have a shader");
    164.                 }
    165.  
    166.                 GUIUtility.ExitGUI();
    167.             }
    168.  
    169.             var scrollBarStyle = new GUIStyle(GUI.skin.verticalScrollbar);
    170.             scrollBarStyle.fixedWidth = SCROLLBAR_WIDTH;
    171.             scrollPos = EditorGUILayout.BeginScrollView(scrollPos, true, true, GUIStyle.none, scrollBarStyle, GUI.skin.box);
    172.             EditorGUILayout.BeginVertical();
    173.  
    174.             for (int i = 0; i < m_selectedMaterials.Count; i++)
    175.             {
    176.                 EditorGUILayout.Space(MATERIAL_SPACING);
    177.  
    178.                 Material m_selectedMaterial = m_selectedMaterials[i];
    179.  
    180.                 EditorGUILayout.BeginHorizontal();
    181.                 if (GUILayout.Button(m_selectedMaterial.name, GUILayout.Width(EditorGUIUtility.labelWidth)))
    182.                     EditorGUIUtility.PingObject(m_selectedMaterial);
    183.                 if (!HasShader(m_selectedMaterial))
    184.                     EditorGUILayout.LabelField("NULL Shader", errorStyle);
    185.                 else
    186.                 {
    187.                     if (GUILayout.Button(m_selectedMaterial.shader.name, GUILayout.Width(EditorGUIUtility.labelWidth))) //, new GUIStyle("miniButton")
    188.                         EditorGUIUtility.PingObject(m_selectedMaterial.shader);
    189.                 }
    190.                 EditorGUILayout.EndHorizontal();
    191.  
    192.                 m_serializedObjects[i].Update();
    193.  
    194.                 EditorGUI.indentLevel++;
    195.                 {
    196.                     EditorGUILayout.Space(TYPE_SPACING);
    197.  
    198.                     EditorGUILayout.LabelField("Textures", typeLabelStyle);
    199.                     EditorGUI.indentLevel++;
    200.                     ProcessProperties("m_SavedProperties.m_TexEnvs", i, PropertyType.TexEnv);
    201.                     EditorGUI.indentLevel--;
    202.  
    203.                     EditorGUILayout.Space(TYPE_SPACING);
    204.  
    205.                     EditorGUILayout.LabelField("Ints", typeLabelStyle);
    206.                     EditorGUI.indentLevel++;
    207.                     ProcessProperties("m_SavedProperties.m_Ints", i, PropertyType.Int);
    208.                     EditorGUI.indentLevel--;
    209.  
    210.                     EditorGUILayout.Space(TYPE_SPACING);
    211.  
    212.                     EditorGUILayout.LabelField("Floats", typeLabelStyle);
    213.                     EditorGUI.indentLevel++;
    214.                     ProcessProperties("m_SavedProperties.m_Floats", i, PropertyType.Float);
    215.                     EditorGUI.indentLevel--;
    216.  
    217.                     EditorGUILayout.Space(TYPE_SPACING);
    218.  
    219.                     EditorGUILayout.LabelField("Colors", typeLabelStyle);
    220.                     EditorGUI.indentLevel++;
    221.                     ProcessProperties("m_SavedProperties.m_Colors", i, PropertyType.Color);
    222.                     EditorGUI.indentLevel--;
    223.                 }
    224.                 EditorGUI.indentLevel--;
    225.  
    226.                 EditorGUILayout.Space(MATERIAL_SPACING);
    227.             }
    228.  
    229.             EditorGUILayout.EndVertical();
    230.             EditorGUILayout.EndScrollView();
    231.    
    232.             EditorGUIUtility.labelWidth = 0;
    233.         }
    234.     }
    235.  
    236.     private enum PropertyType { TexEnv, Int, Float, Color }
    237.     private static bool ShaderHasProperty(Material mat, string name, PropertyType type)
    238.     {
    239.         switch (type)
    240.         {
    241.             case PropertyType.TexEnv:
    242.                 return mat.HasTexture(name);
    243.             case PropertyType.Int:
    244.                 return mat.HasInteger(name);
    245.             case PropertyType.Float:
    246.                 return mat.HasFloat(name);
    247.             case PropertyType.Color:
    248.                 return mat.HasColor(name);
    249.         }
    250.         return false;
    251.     }
    252.  
    253.     private static string GetName(SerializedProperty property)
    254.     {
    255.         return property.FindPropertyRelative("first").stringValue; //return property.displayName;
    256.     }
    257.  
    258.     private static bool HasShader(Material mat)
    259.     {
    260.         return mat.shader.name != "Hidden/InternalErrorShader";
    261.     }
    262.  
    263.     private void RemoveUnusedProperties(string path, int i, PropertyType type)
    264.     {
    265.         if (!HasShader(m_selectedMaterials[i]))
    266.         {
    267.             Debug.LogError("Material " + m_selectedMaterials[i].name + " doesn't have a shader");
    268.             return;
    269.         }
    270.  
    271.         var properties = m_serializedObjects[i].FindProperty(path);
    272.         if (properties != null && properties.isArray)
    273.         {
    274.             for (int j = properties.arraySize - 1; j >= 0; j--)
    275.             {
    276.                 string propName = GetName(properties.GetArrayElementAtIndex(j));
    277.                 bool exists = ShaderHasProperty(m_selectedMaterials[i], propName, type);
    278.  
    279.                 if (!exists)
    280.                 {
    281.                     Debug.Log("Removed " + type + " Property: " + propName);
    282.                     properties.DeleteArrayElementAtIndex(j);
    283.                     m_serializedObjects[i].ApplyModifiedProperties();
    284.                 }
    285.             }
    286.         }
    287.     }
    288.  
    289.     private void ProcessProperties(string path, int i, PropertyType type)
    290.     {
    291.         var properties = m_serializedObjects[i].FindProperty(path);
    292.         if (properties != null && properties.isArray)
    293.         {
    294.             for (int j = 0; j < properties.arraySize; j++)
    295.             {
    296.                 string propName = GetName(properties.GetArrayElementAtIndex(j));
    297.                 bool exists = ShaderHasProperty(m_selectedMaterials[i], propName, type);
    298.  
    299.                 if (!HasShader(m_selectedMaterials[i]))
    300.                 {
    301.                     EditorGUILayout.LabelField(propName, "UNKNOWN", errorStyle);
    302.                 }
    303.                 else if (exists)
    304.                 {
    305.                     EditorGUILayout.LabelField(propName, "Exists"); // in Shader
    306.                 }
    307.                 else
    308.                 {
    309.                     EditorGUILayout.BeginHorizontal();
    310.                     float w = EditorGUIUtility.labelWidth * 2 - REMOVE_BUTTON_WIDTH;
    311.                     EditorGUILayout.LabelField(propName, "Old Reference", warningStyle, GUILayout.Width(w));
    312.                     if (GUILayout.Button("Remove", GUILayout.Width(REMOVE_BUTTON_WIDTH)))
    313.                     {
    314.                         properties.DeleteArrayElementAtIndex(j);
    315.                         m_serializedObjects[i].ApplyModifiedProperties();
    316.                         GUIUtility.ExitGUI();
    317.                     }
    318.                     EditorGUILayout.EndHorizontal();
    319.                 }
    320.             }
    321.         }
    322.     }
    323.  
    324.     private void GetSelectedMaterials()
    325.     {
    326.         Object[] objects = Selection.objects;
    327.  
    328.         m_selectedMaterials = new List<Material>();
    329.  
    330.         for (int i = 0; i < objects.Length; i++)
    331.         {
    332.             Material newMat = objects[i] as Material;
    333.             if (newMat != null)
    334.                 m_selectedMaterials.Add(newMat);
    335.         }
    336.  
    337.         if (m_selectedMaterials != null)
    338.         {
    339.             m_serializedObjects = new SerializedObject[m_selectedMaterials.Count];
    340.             for (int i = 0; i < m_serializedObjects.Length; i++)
    341.                 m_serializedObjects[i] = new SerializedObject(m_selectedMaterials[i]);
    342.         }
    343.  
    344.         Repaint();
    345.     }
    346. }
     
    Last edited: Jul 18, 2021
  8. FloBeber

    FloBeber

    Joined:
    Jun 9, 2015
    Posts:
    166
    Thanks for sharing. Though, your code don't compile. Maybe you use an extension for mat.HasTexture, mat.HasInteger.. functions? They're not recognized.

    EDIT: my bad, just realized these seem to be Unity 2021 only
     
    reedreedchen likes this.
  9. BigGameCompany

    BigGameCompany

    Joined:
    Sep 29, 2016
    Posts:
    112

    Great stuff - thanks. In Unity 2020 I had to update 1 function.

    Code (CSharp):
    1. private static bool ShaderHasProperty(Material mat, string name, PropertyType type)
    2.     {
    3.         return mat.HasProperty(name);
    4.     }
     
    OwlBoy-, JohnSu6616, lang_fox and 2 others like this.
  10. SampsaPlaysome

    SampsaPlaysome

    Joined:
    Oct 20, 2019
    Posts:
    34
    Thank you so much! This has allowed me to clean 81 materials that were referencing some old textures but it was impossible to know if the textures were really not being used. The utility made it a breeze!
     
    FlyingSquirrels likes this.