using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Callbacks; using UnityEditor.Rendering.Analytics; using UnityEditor.Rendering.Universal.Analytics; using UnityEditor.Rendering.Universal.ShaderGUI; using UnityEditor.ShaderGraph; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using static Unity.Rendering.Universal.ShaderUtils; using BlendMode = UnityEngine.Rendering.BlendMode; namespace UnityEditor.Rendering.Universal { class MaterialModificationProcessor : AssetModificationProcessor { const string k_MaterialExtension = ".mat"; static void OnWillCreateAsset(string asset) { if (!asset.HasExtension(k_MaterialExtension)) { return; } MaterialPostprocessor.s_CreatedAssets.Add(asset); } } class MaterialReimporter : Editor { static bool s_NeedToCheckProjSettingExistence = true; static void ReimportAllMaterials() { AssetReimportUtils.ReimportAll(out var duration, out var numberOfAssetsReimported); AssetReimporterAnalytic.Send(duration, numberOfAssetsReimported); MaterialPostprocessor.s_NeedsSavingAssets = true; } [InitializeOnLoadMethod] static void RegisterUpgraderReimport() { EditorApplication.update += () => { if (GraphicsSettings.currentRenderPipeline is not UniversalRenderPipelineAsset universalRenderPipeline) return; if (Time.renderedFrameCount > 0) { bool fileExist = true; // We check the file existence only once to avoid IO operations every frame. if (s_NeedToCheckProjSettingExistence) { fileExist = System.IO.File.Exists(UniversalProjectSettings.filePath); s_NeedToCheckProjSettingExistence = false; } //This method is called at opening and when URP package change (update of manifest.json) var curUpgradeVersion = UniversalProjectSettings.materialVersionForUpgrade; if (curUpgradeVersion != MaterialPostprocessor.k_Upgraders.Length) { string commandLineOptions = Environment.CommandLine; bool inTestSuite = commandLineOptions.Contains("-testResults"); bool isTemplate = EditorApplication.isCreateFromTemplate; if (!inTestSuite && fileExist && !isTemplate) { EditorUtility.DisplayDialog("URP Material upgrade", "The Materials in your Project were created using an older version of the Universal Render Pipeline (URP)." + " Unity must upgrade them to be compatible with your current version of URP. \n" + " Unity will re-import all of the Materials in your project, save the upgraded Materials to disk, and check them out in source control if needed.\n" + " Please see the Material upgrade guide in the URP documentation for more information.", "OK"); } ReimportAllMaterials(); } if (MaterialPostprocessor.s_NeedsSavingAssets) MaterialPostprocessor.SaveAssetsToDisk(); } }; } } class MaterialPostprocessor : AssetPostprocessor { public static List s_CreatedAssets = new List(); internal static List s_ImportedAssetThatNeedSaving = new List(); internal static bool s_NeedsSavingAssets = false; internal static readonly Action[] k_Upgraders = { UpgradeV1, UpgradeV2, UpgradeV3, UpgradeV4, UpgradeV5, UpgradeV6, UpgradeV7, UpgradeV8, UpgradeV9 }; static internal void SaveAssetsToDisk() { string commandLineOptions = System.Environment.CommandLine; bool inTestSuite = commandLineOptions.Contains("-testResults"); if (inTestSuite) { // Need to update material version to prevent infinite loop in the upgrader // when running tests. UniversalProjectSettings.materialVersionForUpgrade = k_Upgraders.Length; return; } foreach (var asset in s_ImportedAssetThatNeedSaving) { AssetDatabase.MakeEditable(asset); } AssetDatabase.SaveAssets(); //to prevent data loss, only update the saved version if user applied change and assets are written to UniversalProjectSettings.materialVersionForUpgrade = k_Upgraders.Length; UniversalProjectSettings.Save(); s_ImportedAssetThatNeedSaving.Clear(); s_NeedsSavingAssets = false; } [RunAfterClass(typeof(UniversalRenderPipelineGlobalSettingsPostprocessor))] static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { string upgradeLog = ""; var upgradeCount = 0; foreach (var asset in importedAssets) { // we only care about materials if (!asset.EndsWith(".mat", StringComparison.InvariantCultureIgnoreCase)) continue; // load the material and look for it's Universal ShaderID // we only care about versioning materials using a known Universal ShaderID // this skips any materials that only target other render pipelines, are user shaders, // or are shaders we don't care to version var material = (Material)AssetDatabase.LoadAssetAtPath(asset, typeof(Material)); var shaderID = GetShaderID(material.shader); if (shaderID == ShaderID.Unknown) continue; var wasUpgraded = false; var debug = "\n" + material.name + "(" + shaderID + ")"; // look for the Universal AssetVersion AssetVersion assetVersion = null; var allAssets = AssetDatabase.LoadAllAssetsAtPath(asset); foreach (var subAsset in allAssets) { if (subAsset is AssetVersion sub) { assetVersion = sub; } } if (!assetVersion) { wasUpgraded = true; assetVersion = ScriptableObject.CreateInstance(); if (s_CreatedAssets.Contains(asset)) { assetVersion.version = k_Upgraders.Length; s_CreatedAssets.Remove(asset); InitializeLatest(material, shaderID); debug += " initialized."; } else { if (shaderID.IsShaderGraph()) { // ShaderGraph materials NEVER had asset versioning applied prior to version 5. // so if we see a ShaderGraph material with no assetVersion, set it to 5 to ensure we apply all necessary versions. assetVersion.version = 5; debug += $" shadergraph material assumed to be version 5 due to missing version."; } else { assetVersion.version = UniversalProjectSettings.materialVersionForUpgrade; debug += $" assumed to be version {UniversalProjectSettings.materialVersionForUpgrade} due to missing version."; } } assetVersion.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector | HideFlags.NotEditable; AssetDatabase.AddObjectToAsset(assetVersion, asset); } while (assetVersion.version >= 0 && assetVersion.version < k_Upgraders.Length) { k_Upgraders[assetVersion.version](material, shaderID); debug += $" upgrading:v{assetVersion.version} to v{assetVersion.version + 1}"; assetVersion.version++; wasUpgraded = true; } if (wasUpgraded) { upgradeLog += debug; upgradeCount++; EditorUtility.SetDirty(assetVersion); s_ImportedAssetThatNeedSaving.Add(asset); s_NeedsSavingAssets = true; } } // Uncomment to show upgrade debug logs //if (!string.IsNullOrEmpty(upgradeLog)) // Debug.Log("UniversalRP Material log: " + upgradeLog); } static void InitializeLatest(Material material, ShaderID id) { // newly created materials should reset their keywords immediately (in case inspector doesn't get invoked) Unity.Rendering.Universal.ShaderUtils.UpdateMaterial(material, MaterialUpdateType.CreatedNewMaterial, id); } static void UpgradeV1(Material material, ShaderID shaderID) { if (shaderID.IsShaderGraph()) return; var shaderPath = ShaderUtils.GetShaderPath((ShaderPathID)shaderID); var upgradeFlag = MaterialUpgrader.UpgradeFlags.LogMessageWhenNoUpgraderFound; switch (shaderID) { case ShaderID.Unlit: MaterialUpgrader.Upgrade(material, new UnlitUpdaterV1(shaderPath), upgradeFlag); UnlitShader.SetMaterialKeywords(material); break; case ShaderID.SimpleLit: MaterialUpgrader.Upgrade(material, new SimpleLitUpdaterV1(shaderPath), upgradeFlag); SimpleLitShader.SetMaterialKeywords(material, SimpleLitGUI.SetMaterialKeywords); break; case ShaderID.Lit: MaterialUpgrader.Upgrade(material, new LitUpdaterV1(shaderPath), upgradeFlag); LitShader.SetMaterialKeywords(material, LitGUI.SetMaterialKeywords); break; case ShaderID.ParticlesLit: MaterialUpgrader.Upgrade(material, new ParticleUpdaterV1(shaderPath), upgradeFlag); ParticlesLitShader.SetMaterialKeywords(material, LitGUI.SetMaterialKeywords, ParticleGUI.SetMaterialKeywords); break; case ShaderID.ParticlesSimpleLit: MaterialUpgrader.Upgrade(material, new ParticleUpdaterV1(shaderPath), upgradeFlag); ParticlesSimpleLitShader.SetMaterialKeywords(material, SimpleLitGUI.SetMaterialKeywords, ParticleGUI.SetMaterialKeywords); break; case ShaderID.ParticlesUnlit: MaterialUpgrader.Upgrade(material, new ParticleUpdaterV1(shaderPath), upgradeFlag); ParticlesUnlitShader.SetMaterialKeywords(material, null, ParticleGUI.SetMaterialKeywords); break; } } static void UpgradeV2(Material material, ShaderID shaderID) { if (shaderID.IsShaderGraph()) return; // fix 50 offset on shaders if (material.HasProperty("_QueueOffset")) BaseShaderGUI.SetupMaterialBlendMode(material); } static void UpgradeV3(Material material, ShaderID shaderID) { if (shaderID.IsShaderGraph()) return; switch (shaderID) { case ShaderID.Lit: case ShaderID.SimpleLit: case ShaderID.ParticlesLit: case ShaderID.ParticlesSimpleLit: case ShaderID.ParticlesUnlit: var propertyID = Shader.PropertyToID("_EmissionColor"); if (material.HasProperty(propertyID)) { // In older version there was a bug that these shaders did not had HDR attribute on emission property. // This caused emission color to be converted from gamma to linear space. // In order to avoid visual regression on older projects we will do gamma to linear conversion here. var emissionGamma = material.GetColor(propertyID); var emissionLinear = emissionGamma.linear; material.SetColor(propertyID, emissionLinear); } break; } } static void UpgradeV4(Material material, ShaderID shaderID) { } static void UpgradeV5(Material material, ShaderID shaderID) { if (shaderID.IsShaderGraph()) return; var propertyID = Shader.PropertyToID("_Surface"); if (material.HasProperty(propertyID)) { float surfaceType = material.GetFloat(propertyID); if (surfaceType >= 1.0f) { material.EnableKeyword("_SURFACE_TYPE_TRANSPARENT"); } else { material.DisableKeyword("_SURFACE_TYPE_TRANSPARENT"); } } } // Separate Preserve Specular Lighting from Premultiplied blend mode. // Update materials params for backwards compatibility. (Keep the same end result). // - Previous (incorrect) premultiplied blend mode --> Alpha blend mode + Preserve Specular Lighting // - Otherwise keep the blend mode and disable Preserve Specular Lighting // - Correct premultiply mode is not possible in V5. // // This is run both hand-written and shadergraph materials. // // Hand-written and overridable shadergraphs always have blendModePreserveSpecular property, which // is assumed to be new since we only run this for V5 -> V6 upgrade. // // Fixed shadergraphs do not have this keyword and are filtered out. // The blend mode is baked in the generated shader, so there's no material properties to be upgraded. // The shadergraph upgrade on re-import will handle the fixed shadergraphs. static void UpgradeV6(Material material, ShaderID shaderID) { var surfaceTypePID = Shader.PropertyToID(Property.SurfaceType); bool isTransparent = material.HasProperty(surfaceTypePID) && material.GetFloat(surfaceTypePID) >= 1.0f; if (isTransparent) { if (shaderID == ShaderID.Unlit) { var blendModePID = Shader.PropertyToID(Property.BlendMode); var blendMode = (BaseShaderGUI.BlendMode)material.GetFloat(blendModePID); // Premultiply used to be "Premultiply (* alpha in shader)" aka Alpha blend if (blendMode == BaseShaderGUI.BlendMode.Premultiply) material.SetFloat(blendModePID, (float)BaseShaderGUI.BlendMode.Alpha); } else { var blendModePreserveSpecularPID = Shader.PropertyToID(Property.BlendModePreserveSpecular); if (material.HasProperty(blendModePreserveSpecularPID)) { // TL;DR; As Complex Lit was not being versioned before we want to only upgrade it in certain // cases to not alter visuals // // To elaborate, this is needed for the following reasons: // 1) Premultiplied used to mean something different before V6 // 2) If the update is applied twice it will change visuals // 3) As Complex Lit was missing form the ShaderID enum it was never being upgraded, so its // version is not a reliable indicator of which version of Premultiplied alpha the user // had intended (e.g. the URP Foundation test project is currently at V7 but some // Complex Lit materials are at V3) // 5) To determine the intended version we can check which version the project is being upgraded // from (as we can then know which version it was being actually used as). If the project is // at version 6 or higher then we know that if the user had selected Premultiplied it is // already working based on the new interpretation and should not be changed bool skipChangingBlendMode = shaderID == ShaderID.ComplexLit && UniversalProjectSettings.materialVersionForUpgrade >= 6; var blendModePID = Shader.PropertyToID(Property.BlendMode); var blendMode = (BaseShaderGUI.BlendMode)material.GetFloat(blendModePID); if (blendMode == BaseShaderGUI.BlendMode.Premultiply && !skipChangingBlendMode) { material.SetFloat(blendModePID, (float)BaseShaderGUI.BlendMode.Alpha); material.SetFloat(blendModePreserveSpecularPID, 1.0f); } else { material.SetFloat(blendModePreserveSpecularPID, 0.0f); } BaseShaderGUI.SetMaterialKeywords(material); } } } } // Upgrades alpha-clipped materials to include logic for automatic alpha-to-coverage support static void UpgradeV7(Material material, ShaderID shaderID) { var surfacePropertyID = Shader.PropertyToID(Property.SurfaceType); var alphaClipPropertyID = Shader.PropertyToID(Property.AlphaClip); var alphaToMaskPropertyID = Shader.PropertyToID(Property.AlphaToMask); if (material.HasProperty(surfacePropertyID) && material.HasProperty(alphaClipPropertyID) && material.HasProperty(alphaToMaskPropertyID)) { bool isOpaque = material.GetFloat(surfacePropertyID) < 1.0f; bool isAlphaClipEnabled = material.GetFloat(alphaClipPropertyID) > 0.0f; float alphaToMask = (isOpaque && isAlphaClipEnabled) ? 1.0f : 0.0f; material.SetFloat(alphaToMaskPropertyID, alphaToMask); } } // We need to disable the passes with a { "LightMode" = "MotionVectors" } tag for URP shaders that have them, // otherwise they'll be rendered for static objects (transform not moving and no skeletal animation) regressing MV perf. // // This is now needed as most URP material types have their own dedicated MV pass (this is so they work with // Alpha-Clipping which needs per material data and not just generic vertex data like for the override shader). // // In Unity (both Built-in and SRP), the MV pass will be used even if disabled on frames where the object's // transform changes or there's skeletal animation. But for URP disabling wasn't necessary before as the single // object MV override shader was only used when needed (and there wasn't even anything to disable per material) // // N.B. the SetShaderPassEnabled API takes a tag value corresponding to the "LightMode" key and not a pass name static void UpgradeV8(Material material, ShaderID shaderID) { if (HasMotionVectorLightModeTag(shaderID)) material.SetShaderPassEnabled(MotionVectorRenderPass.k_MotionVectorsLightModeTag, false); } // We want to disable the custom motion vector pass for SpeedTrees which won't have any // vertex animation due to no wind. This is done to prevent performance regression from // rendering trees with no motion vector output. static void UpgradeV9(Material material, ShaderID shaderID) { if(shaderID != ShaderID.SpeedTree8) return; // Check if the material is a SpeedTree material and whether it has wind turned on or off. if(SpeedTree8MaterialUpgrader.DoesMaterialHaveSpeedTreeWindKeyword(material)) { bool motionVectorPassEnabled = SpeedTree8MaterialUpgrader.IsWindEnabled(material); material.SetShaderPassEnabled(MotionVectorRenderPass.k_MotionVectorsLightModeTag, motionVectorPassEnabled); } } } // Upgraders v1 #region UpgradersV1 internal class LitUpdaterV1 : MaterialUpgrader { public static void UpdateLitDetails(Material material) { if (material == null) throw new ArgumentNullException("material"); if (material.GetTexture("_MetallicGlossMap") || material.GetTexture("_SpecGlossMap") || material.GetFloat("_SmoothnessTextureChannel") >= 0.5f) material.SetFloat("_Smoothness", material.GetFloat("_GlossMapScale")); else material.SetFloat("_Smoothness", material.GetFloat("_Glossiness")); } public LitUpdaterV1(string oldShaderName) { if (oldShaderName == null) throw new ArgumentNullException("oldShaderName"); string standardShaderPath = ShaderUtils.GetShaderPath(ShaderPathID.Lit); RenameShader(oldShaderName, standardShaderPath, UpdateLitDetails); RenameTexture("_MainTex", "_BaseMap"); RenameColor("_Color", "_BaseColor"); RenameFloat("_GlossyReflections", "_EnvironmentReflections"); } } internal class UnlitUpdaterV1 : MaterialUpgrader { static Shader bakedLit = Shader.Find(ShaderUtils.GetShaderPath(ShaderPathID.BakedLit)); public static void UpgradeToUnlit(Material material) { if (material == null) throw new ArgumentNullException("material"); if (material.GetFloat("_SampleGI") != 0) { material.shader = bakedLit; material.EnableKeyword("_NORMALMAP"); } } public UnlitUpdaterV1(string oldShaderName) { if (oldShaderName == null) throw new ArgumentNullException("oldShaderName"); RenameShader(oldShaderName, ShaderUtils.GetShaderPath(ShaderPathID.Unlit), UpgradeToUnlit); RenameTexture("_MainTex", "_BaseMap"); RenameColor("_Color", "_BaseColor"); } } internal class SimpleLitUpdaterV1 : MaterialUpgrader { public SimpleLitUpdaterV1(string oldShaderName) { if (oldShaderName == null) throw new ArgumentNullException("oldShaderName"); RenameShader(oldShaderName, ShaderUtils.GetShaderPath(ShaderPathID.SimpleLit), UpgradeToSimpleLit); RenameTexture("_MainTex", "_BaseMap"); RenameColor("_Color", "_BaseColor"); RenameFloat("_SpecSource", "_SpecularHighlights"); RenameFloat("_Shininess", "_Smoothness"); } public static void UpgradeToSimpleLit(Material material) { if (material == null) throw new ArgumentNullException("material"); var smoothnessSource = 1 - (int)material.GetFloat("_GlossinessSource"); material.SetFloat("_SmoothnessSource", smoothnessSource); if (material.GetTexture("_SpecGlossMap") == null) { var col = material.GetColor("_SpecColor"); var colBase = material.GetColor("_Color"); var smoothness = material.GetFloat("_Shininess"); if (material.GetFloat("_Surface") == 0) { if (smoothnessSource == 1) colBase.a = smoothness; else col.a = smoothness; material.SetColor("_BaseColor", colBase); } material.SetColor("_BaseColor", colBase); material.SetColor("_SpecColor", col); } } } internal class ParticleUpdaterV1 : MaterialUpgrader { public ParticleUpdaterV1(string shaderName) { if (shaderName == null) throw new ArgumentNullException("oldShaderName"); RenameShader(shaderName, shaderName, ParticleUpgrader.UpdateSurfaceBlendModes); RenameTexture("_MainTex", "_BaseMap"); RenameColor("_Color", "_BaseColor"); RenameFloat("_FlipbookMode", "_FlipbookBlending"); switch (ShaderUtils.GetEnumFromPath(shaderName)) { case ShaderPathID.ParticlesLit: RenameFloat("_Glossiness", "_Smoothness"); break; case ShaderPathID.ParticlesSimpleLit: RenameFloat("_Glossiness", "_Smoothness"); break; case ShaderPathID.ParticlesUnlit: break; } } } #endregion }