using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; /// /// Outil d'éditeur : reconvertit des matériaux tombés par erreur sur le shader /// "Universal Render Pipeline/2D/Mesh2D-Lit-Default" vers "Universal Render Pipeline/Lit". /// /// AVANT DE LANCER : commit Git ou copie du projet. L'annulation (Ctrl+Z) sur des /// assets n'est pas fiable — le contrôle de version est le vrai filet de sécurité. /// /// 1. Placer le fichier dans un dossier "Editor" (ex. Assets/Editor/). /// 2. Menu "Tools > URP > 1. Analyser..." pour voir la liste sans rien modifier. /// 3. Menu "Tools > URP > 2. Convertir (sélection)" ou "3. Convertir (tout le projet)". /// /// LIMITE IMPORTANTE : ce script rebranche le bon shader et rétablit texture/couleur/ /// tiling/keywords. Il ne peut PAS deviner les valeurs de smoothness, metallic, normal /// map ou emission perdues lors de la mauvaise conversion initiale. /// public static class Convert2DMaterialsToURPLit { private const string SourceShaderName = "Universal Render Pipeline/2D/Mesh2D-Lit-Default"; private const string TargetShaderName = "Universal Render Pipeline/Lit"; [MenuItem("Tools/URP/1. Analyser les matériaux 2D (aucune modification)", priority = 1)] private static void DryRunAll() => Run(dryRun: true, selectionOnly: false); [MenuItem("Tools/URP/2. Convertir vers URP Lit (sélection)", priority = 2)] private static void ConvertSelection() => Run(dryRun: false, selectionOnly: true); [MenuItem("Tools/URP/3. Convertir vers URP Lit (tout le projet)", priority = 3)] private static void ConvertAll() => Run(dryRun: false, selectionOnly: false); private static void Run(bool dryRun, bool selectionOnly) { Shader targetShader = Shader.Find(TargetShaderName); if (targetShader == null) { Debug.LogError($"Shader cible introuvable : {TargetShaderName}. Vérifier que URP est installé et actif."); return; } string[] guids = selectionOnly ? Selection.assetGUIDs : AssetDatabase.FindAssets("t:Material", new[] { "Assets" }); if (guids == null || guids.Length == 0) { Debug.LogWarning("Aucun matériau à examiner."); return; } if (!dryRun && !EditorUtility.DisplayDialog( "Conversion de matériaux", $"{guids.Length} asset(s) vont être examinés et les matériaux en shader 2D seront " + $"reconvertis vers {TargetShaderName}.\n\nLe projet est-il sauvegardé (Git / copie) ?", "Convertir", "Annuler")) { return; } var converted = new List(); var readOnly = new List(); int examined = 0; bool canceled = false; int undoGroup = Undo.GetCurrentGroup(); Undo.SetCurrentGroupName("Convert 2D materials to URP/Lit"); if (!dryRun) AssetDatabase.StartAssetEditing(); try { for (int i = 0; i < guids.Length; i++) { string path = AssetDatabase.GUIDToAssetPath(guids[i]); if (string.IsNullOrEmpty(path)) continue; if (EditorUtility.DisplayCancelableProgressBar( dryRun ? "Analyse des matériaux" : "Conversion des matériaux", $"{path} ({i + 1}/{guids.Length})", (i + 1) / (float)guids.Length)) { canceled = true; break; } // On ne traite que les .mat éditables : pas les matériaux intégrés // à un .fbx, pas ceux qui viennent d'un package immuable. if (!path.EndsWith(".mat", System.StringComparison.OrdinalIgnoreCase)) continue; Material mat = AssetDatabase.LoadAssetAtPath(path); if (mat == null || mat.shader == null) continue; if (mat.shader.name != SourceShaderName) continue; examined++; if ((mat.hideFlags & HideFlags.NotEditable) != 0 || !AssetDatabase.IsOpenForEdit(mat)) { readOnly.Add(path); continue; } if (dryRun) { converted.Add(path); continue; } // Sauvegarde des valeurs dont le NOM de propriété change entre les deux shaders. Texture mainTex = mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null; Vector2 texScale = mat.HasProperty("_MainTex") ? mat.GetTextureScale("_MainTex") : Vector2.one; Vector2 texOffset = mat.HasProperty("_MainTex") ? mat.GetTextureOffset("_MainTex") : Vector2.zero; Color? mainColor = mat.HasProperty("_Color") ? mat.GetColor("_Color") : (Color?)null; Undo.RegisterCompleteObjectUndo(mat, "Convert shader to URP/Lit"); mat.shader = targetShader; if (mainTex != null && mat.HasProperty("_BaseMap")) { mat.SetTexture("_BaseMap", mainTex); mat.SetTextureScale("_BaseMap", texScale); mat.SetTextureOffset("_BaseMap", texOffset); } if (mainColor.HasValue && mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", mainColor.Value); // Indispensable : réaligne keywords, render queue et passes. ResetKeywords(mat); EditorUtility.SetDirty(mat); converted.Add(path); } } finally { if (!dryRun) AssetDatabase.StopAssetEditing(); EditorUtility.ClearProgressBar(); } if (!dryRun) { AssetDatabase.SaveAssets(); Undo.CollapseUndoOperations(undoGroup); } string verbe = dryRun ? "détecté(s)" : "converti(s)"; Debug.Log($"{(canceled ? "INTERROMPU — " : "")}{converted.Count} matériau(x) {verbe} " + $"sur {examined} en shader 2D." + (dryRun ? " (aucune modification)" : "")); if (converted.Count > 0) Debug.Log("Matériaux :\n" + string.Join("\n", converted)); if (readOnly.Count > 0) Debug.LogWarning("Non modifiables (package / lecture seule / non extraits) :\n" + string.Join("\n", readOnly)); if (canceled) Debug.LogWarning("Conversion interrompue : le projet est dans un état partiellement converti."); } // ------------------------------------------------------------------ // Remise à niveau des keywords / render state du matériau. // // URP fournit UnityEditor.Rendering.Universal.ShaderUtils.ResetMaterialKeywords, // mais cette classe n'est publique qu'à partir de URP 12 (Unity 2021.2) et exige // une référence à l'assembly Unity.RenderPipelines.Universal.Editor. On y accède // par réflexion pour que le script compile sur toutes les versions, avec un repli // manuel (état opaque standard) si la méthode est absente. // ------------------------------------------------------------------ private static MethodInfo _resetMethod; private static bool _resetMethodResolved; private static void ResetKeywords(Material mat) { if (!_resetMethodResolved) { _resetMethodResolved = true; _resetMethod = FindResetMaterialKeywords(); if (_resetMethod == null) Debug.LogWarning("ShaderUtils.ResetMaterialKeywords introuvable (URP < 12 ?). " + "Repli sur une configuration opaque standard : vérifier les matériaux transparents à la main."); } if (_resetMethod != null) { var ps = _resetMethod.GetParameters(); var args = new object[ps.Length]; args[0] = mat; for (int i = 1; i < ps.Length; i++) { var t = ps[i].ParameterType; object def = ps[i].HasDefaultValue ? ps[i].DefaultValue : null; if (t.IsEnum) def = System.Enum.ToObject(t, def ?? 0); else if (def == null && t.IsValueType) def = System.Activator.CreateInstance(t); args[i] = def; } _resetMethod.Invoke(null, args); return; } ResetKeywordsFallback(mat); } private static MethodInfo FindResetMaterialKeywords() { foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies()) { var type = asm.GetType("UnityEditor.Rendering.Universal.ShaderUtils", false); if (type == null) continue; foreach (var m in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) { if (m.Name != "ResetMaterialKeywords") continue; var ps = m.GetParameters(); if (ps.Length >= 1 && ps[0].ParameterType == typeof(Material)) return m; } } return null; } /// Configuration opaque standard de URP/Lit, appliquée si l'API de URP est indisponible. private static void ResetKeywordsFallback(Material mat) { mat.SetFloat("_Surface", 0f); // Opaque mat.SetFloat("_Blend", 0f); mat.SetFloat("_AlphaClip", 0f); mat.SetFloat("_SrcBlend", (float)BlendMode.One); mat.SetFloat("_DstBlend", (float)BlendMode.Zero); mat.SetFloat("_ZWrite", 1f); mat.SetFloat("_WorkflowMode", 1f); // Metallic mat.SetOverrideTag("RenderType", "Opaque"); mat.renderQueue = (int)RenderQueue.Geometry; mat.SetShaderPassEnabled("ShadowCaster", true); mat.DisableKeyword("_SURFACE_TYPE_TRANSPARENT"); mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); mat.DisableKeyword("_ALPHATEST_ON"); SetKeyword(mat, "_NORMALMAP", HasTex(mat, "_BumpMap")); SetKeyword(mat, "_METALLICSPECGLOSSMAP", HasTex(mat, "_MetallicGlossMap")); SetKeyword(mat, "_OCCLUSIONMAP", HasTex(mat, "_OcclusionMap")); bool emission = HasTex(mat, "_EmissionMap") || (mat.HasProperty("_EmissionColor") && mat.GetColor("_EmissionColor").maxColorComponent > 0f); SetKeyword(mat, "_EMISSION", emission); mat.globalIlluminationFlags = emission ? MaterialGlobalIlluminationFlags.RealtimeEmissive : MaterialGlobalIlluminationFlags.EmissiveIsBlack; } private static bool HasTex(Material m, string prop) => m.HasProperty(prop) && m.GetTexture(prop) != null; private static void SetKeyword(Material m, string keyword, bool on) { if (on) m.EnableKeyword(keyword); else m.DisableKeyword(keyword); } }