using System.Collections.Generic; using UnityEngine; using System; using UnityEngine.Rendering; namespace UnityEditor.Rendering { /// /// Material Upgrader dialog text. /// public static class DialogText { /// Material Upgrader title. public static readonly string title = "Material Upgrader"; /// Material Upgrader proceed. public static readonly string proceed = "Proceed"; /// Material Upgrader Ok. public static readonly string ok = "OK"; /// Material Upgrader cancel. public static readonly string cancel = "Cancel"; /// Material Upgrader no selection message. public static readonly string noSelectionMessage = "You must select at least one material."; /// Material Upgrader project backup message. public static readonly string projectBackMessage = "Make sure to have a project backup before proceeding."; } /// /// Material Upgrader class. /// public class MaterialUpgrader { /// /// Material Upgrader finalizer delegate. /// /// Material public delegate void MaterialFinalizer(Material mat); string m_OldShader; string m_NewShader; private static string[] s_PathsWhiteList = new[] { "Hidden/", "HDRP/", "Shader Graphs/" }; /// /// Retrieves path to new shader. /// public string NewShaderPath { get => m_NewShader; } MaterialFinalizer m_Finalizer; Dictionary m_TextureRename = new Dictionary(); Dictionary m_FloatRename = new Dictionary(); Dictionary m_ColorRename = new Dictionary(); Dictionary m_FloatPropertiesToSet = new Dictionary(); Dictionary m_ColorPropertiesToSet = new Dictionary(); List m_TexturesToRemove = new List(); Dictionary m_TexturesToSet = new Dictionary(); class KeywordFloatRename { public string keyword; public string property; public float setVal, unsetVal; } List m_KeywordFloatRename = new List(); /// /// Type of property to rename. /// public enum MaterialPropertyType { /// Texture reference property. Texture, /// Float property. Float, /// Color property. Color } /// /// Retrieves a collection of renamed parameters of a specific MaterialPropertyType. /// /// Material Property Type /// Dictionary of property names to their renamed values. /// type is not valid. public IReadOnlyDictionary GetPropertyRenameMap(MaterialPropertyType type) { switch (type) { case MaterialPropertyType.Texture: return m_TextureRename; case MaterialPropertyType.Float: return m_FloatRename; case MaterialPropertyType.Color: return m_ColorRename; default: throw new ArgumentException(nameof(type)); } } /// /// Upgrade Flags /// [Flags] public enum UpgradeFlags { /// None. None = 0, /// LogErrorOnNonExistingProperty. LogErrorOnNonExistingProperty = 1, /// CleanupNonUpgradedProperties. CleanupNonUpgradedProperties = 2, /// LogMessageWhenNoUpgraderFound. LogMessageWhenNoUpgraderFound = 4 } /// /// Upgrade method. /// /// Material to upgrade. /// Upgrade flag public void Upgrade(Material material, UpgradeFlags flags) { Material newMaterial; if ((flags & UpgradeFlags.CleanupNonUpgradedProperties) != 0) { newMaterial = new Material(Shader.Find(m_NewShader)); } else { newMaterial = UnityEngine.Object.Instantiate(material) as Material; newMaterial.shader = Shader.Find(m_NewShader); } Convert(material, newMaterial); material.shader = Shader.Find(m_NewShader); material.CopyPropertiesFromMaterial(newMaterial); UnityEngine.Object.DestroyImmediate(newMaterial); if (m_Finalizer != null) m_Finalizer(material); } // Overridable function to implement custom material upgrading functionality /// /// Custom material conversion method. /// /// Source material. /// Destination material. public virtual void Convert(Material srcMaterial, Material dstMaterial) { foreach (var t in m_TextureRename) { if (!srcMaterial.HasProperty(t.Key) || !dstMaterial.HasProperty(t.Value)) continue; dstMaterial.SetTextureScale(t.Value, srcMaterial.GetTextureScale(t.Key)); dstMaterial.SetTextureOffset(t.Value, srcMaterial.GetTextureOffset(t.Key)); dstMaterial.SetTexture(t.Value, srcMaterial.GetTexture(t.Key)); } foreach (var t in m_FloatRename) { if (!srcMaterial.HasProperty(t.Key) || !dstMaterial.HasProperty(t.Value)) continue; dstMaterial.SetFloat(t.Value, srcMaterial.GetFloat(t.Key)); } foreach (var t in m_ColorRename) { if (!srcMaterial.HasProperty(t.Key) || !dstMaterial.HasProperty(t.Value)) continue; dstMaterial.SetColor(t.Value, srcMaterial.GetColor(t.Key)); } foreach (var prop in m_TexturesToRemove) { if (!dstMaterial.HasProperty(prop)) continue; dstMaterial.SetTexture(prop, null); } foreach (var prop in m_TexturesToSet) { if (!dstMaterial.HasProperty(prop.Key)) continue; dstMaterial.SetTexture(prop.Key, prop.Value); } foreach (var prop in m_FloatPropertiesToSet) { if (!dstMaterial.HasProperty(prop.Key)) continue; dstMaterial.SetFloat(prop.Key, prop.Value); } foreach (var prop in m_ColorPropertiesToSet) { if (!dstMaterial.HasProperty(prop.Key)) continue; dstMaterial.SetColor(prop.Key, prop.Value); } foreach (var t in m_KeywordFloatRename) { if (!dstMaterial.HasProperty(t.property)) continue; dstMaterial.SetFloat(t.property, srcMaterial.IsKeywordEnabled(t.keyword) ? t.setVal : t.unsetVal); } } /// /// Rename shader. /// /// Old name. /// New name. /// Finalizer delegate. public void RenameShader(string oldName, string newName, MaterialFinalizer finalizer = null) { m_OldShader = oldName; m_NewShader = newName; m_Finalizer = finalizer; } /// /// Rename Texture Parameter. /// /// Old name. /// New name. public void RenameTexture(string oldName, string newName) { m_TextureRename[oldName] = newName; } /// /// Rename Float Parameter. /// /// Old name. /// New name. public void RenameFloat(string oldName, string newName) { m_FloatRename[oldName] = newName; } /// /// Rename Color Parameter. /// /// Old name. /// New name. public void RenameColor(string oldName, string newName) { m_ColorRename[oldName] = newName; } /// /// Remove Texture Parameter. /// /// Parameter name. public void RemoveTexture(string name) { m_TexturesToRemove.Add(name); } /// /// Set float property. /// /// Property name. /// Property value. public void SetFloat(string propertyName, float value) { m_FloatPropertiesToSet[propertyName] = value; } /// /// Set color property. /// /// Property name. /// Property value. public void SetColor(string propertyName, Color value) { m_ColorPropertiesToSet[propertyName] = value; } /// /// Set texture property. /// /// Property name. /// Property value. public void SetTexture(string propertyName, Texture value) { m_TexturesToSet[propertyName] = value; } /// /// Rename a keyword to float. /// /// Old name. /// New name. /// Value when set. /// Value when unset. public void RenameKeywordToFloat(string oldName, string newName, float setVal, float unsetVal) { m_KeywordFloatRename.Add(new KeywordFloatRename { keyword = oldName, property = newName, setVal = setVal, unsetVal = unsetVal }); } static MaterialUpgrader GetUpgrader(List upgraders, Material material) { if (material == null || material.shader == null) return null; string shaderName = material.shader.name; for (int i = 0; i != upgraders.Count; i++) { if (upgraders[i].m_OldShader == shaderName) return upgraders[i]; } return null; } //@TODO: Only do this when it exceeds memory consumption... static void SaveAssetsAndFreeMemory() { AssetDatabase.SaveAssets(); GC.Collect(); EditorUtility.UnloadUnusedAssetsImmediate(); AssetDatabase.Refresh(); } /// /// Checking if the passed in value is a path to a Material. /// /// Material to check. /// HashSet of strings to ignore. /// Returns true if the passed in material's shader is not in the passed in ignore list. static bool ShouldUpgradeShader(Material material, HashSet shaderNamesToIgnore) { if (material == null) return false; if (material.shader == null) return false; return !shaderNamesToIgnore.Contains(material.shader.name); } private static bool IsNotAutomaticallyUpgradable(List upgraders, Material material) { return GetUpgrader(upgraders, material) == null && !material.shader.name.ContainsAny(s_PathsWhiteList); } /// /// Checking if project folder contains any materials that are not using built-in shaders. /// /// List if MaterialUpgraders /// Returns true if at least one material uses a non-built-in shader (ignores Hidden, HDRP and Shader Graph Shaders) public static bool ProjectFolderContainsNonBuiltinMaterials(List upgraders) { foreach (var material in AssetDatabaseHelper.FindAssets(".mat")) { if(IsNotAutomaticallyUpgradable(upgraders, material)) return true; } return false; } /// /// Upgrade the project folder. /// /// List of upgraders. /// Name of the progress bar. /// Material Upgrader flags. public static void UpgradeProjectFolder(List upgraders, string progressBarName, UpgradeFlags flags = UpgradeFlags.None) { HashSet shaderNamesToIgnore = new HashSet(); UpgradeProjectFolder(upgraders, shaderNamesToIgnore, progressBarName, flags); } /// /// Upgrade the project folder. /// /// List of upgraders. /// Set of shader names to ignore. /// Name of the progress bar. /// Material Upgrader flags. public static void UpgradeProjectFolder(List upgraders, HashSet shaderNamesToIgnore, string progressBarName, UpgradeFlags flags = UpgradeFlags.None) { if ((!Application.isBatchMode) && (!EditorUtility.DisplayDialog(DialogText.title, "The upgrade will overwrite materials in your project. " + DialogText.projectBackMessage, DialogText.proceed, DialogText.cancel))) return; var materialAssets = AssetDatabase.FindAssets($"t:{nameof(Material)} glob:\"**/*.mat\""); int materialIndex = 0; foreach (var guid in materialAssets) { Material material = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); materialIndex++; if (UnityEditor.EditorUtility.DisplayCancelableProgressBar(progressBarName, string.Format("({0} of {1}) {2}", materialIndex, materialAssets.Length, material), (float)materialIndex / (float)materialAssets.Length)) break; if (!ShouldUpgradeShader(material, shaderNamesToIgnore)) continue; Upgrade(material, upgraders, flags); } // Upgrade terrain specifically since it is a builtin material if (Terrain.activeTerrains.Length > 0) { Material terrainMat = Terrain.activeTerrain.materialTemplate; Upgrade(terrainMat, upgraders, flags); } UnityEditor.EditorUtility.ClearProgressBar(); } /// /// Upgrade a material. /// /// Material to upgrade. /// Material upgrader. /// Material Upgrader flags. public static void Upgrade(Material material, MaterialUpgrader upgrader, UpgradeFlags flags) { using (ListPool.Get(out List upgraders)) { upgraders.Add(upgrader); Upgrade(material, upgraders, flags); } } /// /// Upgrade a material. /// /// Material to upgrade. /// List of Material upgraders. /// Material Upgrader flags. public static void Upgrade(Material material, List upgraders, UpgradeFlags flags) { string message = string.Empty; if (Upgrade(material, upgraders, flags, ref message)) return; if (!string.IsNullOrEmpty(message)) { Debug.Log(message); } } /// /// Upgrade a material. /// /// Material to upgrade. /// List of Material upgraders. /// Material upgrader flags. /// Error message to be outputted when no material upgraders are suitable for given material if the flags is used. /// Returns true if the upgrader was found for the passed in material. public static bool Upgrade(Material material, List upgraders, UpgradeFlags flags, ref string message) { if (material == null) return false; var upgrader = GetUpgrader(upgraders, material); if (upgrader != null) { upgrader.Upgrade(material, flags); return true; } if ((flags & UpgradeFlags.LogMessageWhenNoUpgraderFound) == UpgradeFlags.LogMessageWhenNoUpgraderFound) { message = $"{material.name} material was not upgraded. There's no upgrader to convert {material.shader.name} shader to selected pipeline"; return false; } return true; } /// /// Upgrade the selection. /// /// List of upgraders. /// Name of the progress bar. /// Material Upgrader flags. public static void UpgradeSelection(List upgraders, string progressBarName, UpgradeFlags flags = UpgradeFlags.None) { HashSet shaderNamesToIgnore = new HashSet(); UpgradeSelection(upgraders, shaderNamesToIgnore, progressBarName, flags); } /// /// Upgrade the selection. /// /// List of upgraders. /// Set of shader names to ignore. /// Name of the progress bar. /// Material Upgrader flags. public static void UpgradeSelection(List upgraders, HashSet shaderNamesToIgnore, string progressBarName, UpgradeFlags flags = UpgradeFlags.None) { var selection = Selection.objects; if (selection == null) { EditorUtility.DisplayDialog(DialogText.title, DialogText.noSelectionMessage, DialogText.ok); return; } List selectedMaterials = new List(selection.Length); for (int i = 0; i < selection.Length; ++i) { Material mat = selection[i] as Material; if (mat != null) selectedMaterials.Add(mat); } int selectedMaterialsCount = selectedMaterials.Count; if (selectedMaterialsCount == 0) { EditorUtility.DisplayDialog(DialogText.title, DialogText.noSelectionMessage, DialogText.ok); return; } if (!EditorUtility.DisplayDialog(DialogText.title, string.Format("The upgrade will overwrite {0} selected material{1}. ", selectedMaterialsCount, selectedMaterialsCount > 1 ? "s" : "") + DialogText.projectBackMessage, DialogText.proceed, DialogText.cancel)) return; string lastMaterialName = ""; for (int i = 0; i < selectedMaterialsCount; i++) { if (UnityEditor.EditorUtility.DisplayCancelableProgressBar(progressBarName, string.Format("({0} of {1}) {2}", i, selectedMaterialsCount, lastMaterialName), (float)i / (float)selectedMaterialsCount)) break; var material = selectedMaterials[i]; if (!ShouldUpgradeShader(material, shaderNamesToIgnore)) continue; Upgrade(material, upgraders, flags); if (material != null) lastMaterialName = material.name; } UnityEditor.EditorUtility.ClearProgressBar(); } } }