////////////////////////////////////////////////////// // MK Install Wizard Configuration // // // // Created by Michael Kremmel // // www.michaelkremmel.de // // Copyright © 2020 All rights reserved. // ////////////////////////////////////////////////////// using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.IO; using System.Linq; #if UNITY_EDITOR #if MK_URP using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; #endif using Configuration = MK.Toon.Editor.InstallWizard.Configuration; namespace MK.Toon.Editor.InstallWizard { //[CreateAssetMenu(fileName = "Configuration", menuName = "MK/Install Wizard/Create Configuration Asset")] public sealed class Configuration : ScriptableObject { #pragma warning disable CS0414 internal static bool isReady { get { if(_instance == null) TryGetInstance(); return _instance != null; } } [SerializeField] private RenderPipeline _renderPipeline = RenderPipeline.Built_in; [SerializeField] internal bool showInstallerOnReload = true; [SerializeField] internal bool disablePerObjectOutlinesWarning = false; [SerializeField][Space] private Texture2D _titleImage = null; [SerializeField][Space] private Object _readMe = null; [SerializeField][Space] private Object _basePackageBuiltin = null; [SerializeField] private Object _basePackageURP = null; [SerializeField][Space] private Object _examplesPackageInc = null; [SerializeField] private Object _examplesPackageBuiltin = null; [SerializeField] private Object _examplesPackageURP = null; [SerializeField] private Object _examplesPackageURP_2023_2_Or_Newer = null; [SerializeField] private Object _examplesPackageBuiltin_2023_2_Or_Newer = null; [SerializeField][Space] private ExampleContainer[] _examples = null; [Space][Header("Global Shader Features")] [SerializeField] private UnityEngine.Object _globalShaderFeaturesFile = null; public static void ConfigureGlobalShaderFeatures() { if(isReady) { List content = new List(); string projectPath = Application.dataPath; projectPath = projectPath.Substring(0, projectPath.Length - 6); string filePath = projectPath + AssetDatabase.GetAssetPath(_instance._globalShaderFeaturesFile); //Read using(MemoryStream dataStream = new MemoryStream(File.ReadAllBytes(filePath))) { using (StreamReader dataReader = new StreamReader(dataStream, System.Text.Encoding.UTF8)) { string line; while((line = dataReader.ReadLine()) != null) { content.Add(line); } } } const string detectFeature = "//%%"; bool updated = false; //Modify for(int i = 0; i < content.Count; i++) { for(int f = 0; f < _instance._globalShaderFeatures.Count; f++) { for(int id = 0; id < _instance._globalShaderFeatures[f].identifiers.Count; id++) { if(content[i].Contains(detectFeature + _instance._globalShaderFeatures[f].identifiers[id])) { if(ParseFeature(content, i, _instance._globalShaderFeatures[f].mode == id)) updated = true; } } } } if(!updated) return; //Write using(MemoryStream dataStream = new MemoryStream()) { using (StreamWriter streamWriter = new StreamWriter(dataStream, System.Text.Encoding.UTF8)) { while(content.Count > 0) { streamWriter.WriteLine(content[0]); content.RemoveAt(0); } } File.WriteAllBytes(filePath, dataStream.ToArray()); } AssetDatabase.Refresh(); Configuration.SetCompileDirectives(); } } private static bool ParseFeature(List content, int startIndex, bool enable) { int featureEnd = startIndex + 5; bool updated = false; if(enable) { for(int i = startIndex; i <= featureEnd; i++) { if(content[i] == null) break; string s = content[i]; content[i] = content[i].Replace("/*!!", "//!!"); content[i] = content[i].Replace("$$*/", "//$$"); updated = s != content[i]; } } else { for(int i = startIndex; i <= featureEnd; i++) { if(content[i] == null) break; string s = content[i]; content[i] = content[i].Replace("//!!", "/*!!"); content[i] = content[i].Replace("//$$", "$$*/"); updated = s != content[i]; } } return updated; } private readonly static List _globalShaderFeaturesTemplate = new List() { //keep features in sync with the shader files new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_COMBINE_VERTEX_COLOR_WITH_ALBEDO_MAP_OFF", "MK_COMBINE_VERTEX_COLOR_WITH_ALBEDO_MAP" }, new List() {}, "Multiply vertex color with albedo", "Multiplies vertex color into the albedo Color."), new GlobalShaderFeatureOutlineFading(MGlobalShaderFeatureOutlineFadingMode.Off, new List() { "MK_OUTLINE_FADING_OFF", "MK_OUTLINE_FADING_LINEAR", "MK_OUTLINE_FADING_EXPONENTIAL", "MK_OUTLINE_FADING_INVERSE_EXPONENTIAL" }, new List() { "MK_NULL", "MK_TOON_OUTLINE_FADING_LINEAR", "MK_TOON_OUTLINE_FADING_EXPONENTIAL", "MK_TOON_OUTLINE_FADING_INVERSE_EXPONENTIAL" }, "Outline Distance Fading", "Enable outline distance based fading."), new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_POINT_FILTERING_OFF", "MK_POINT_FILTERING" }, new List() {}, "Force Point Filtering", "Forces point filtering on textures."), new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_DISSOLVE_PROJECTION_SCREEN_SPACE_OFF", "MK_DISSOLVE_PROJECTION_SCREEN_SPACE" }, new List() {}, "Dissolve Projection Screen Space", "Forces dissolve projection into screen space."), new GlobalShaderFeature(GlobalShaderFeatureMode.On, new List() { "MK_LOCAL_ANTIALIASING_OFF", "MK_LOCAL_ANTIALIASING" }, new List() {}, "Enable Local Antialiasing", "Enables local antialiasing except for mobile devices."), new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_STYLIZE_SYSTEM_SHADOWS_OFF", "MK_STYLIZE_SYSTEM_SHADOWS" }, new List() { "MK_NULL", "MK_TOON_STYLIZE_SYSTEM_SHADOWS" }, "Stylize System Shadows", "Stylizes the system shadows like the lighting. Be careful with the thresholds!"), new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_LEGACY_NOISE_OFF", "MK_LEGACY_NOISE" }, new List() {}, "Legacy Noise", "Enables the legacy noise calculation for animation and outline noise."), new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_REGULAR_SCREEN_SPACE_OFF", "MK_REGULAR_SCREEN_SPACE" }, new List() {}, "Regular Screen Space UV", "Enables the regular screen space uv for screen spaced effects instead MK Toons customized screen space uv."), //Conditional features new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_LEGACY_SCREEN_SCALING_OFF", "MK_LEGACY_SCREEN_SCALING" }, new List() {}, "Legacy Screen Scaling", "Enables legacy screen spaced scaling for artistic textures and outlines in clip space."), new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_MULTI_PASS_STEREO_SCALING_OFF", "MK_MULTI_PASS_STEREO_SCALING" }, new List() {}, "Multi Pass Scaling", "Enables correct scaling for artistic textures and outlines in clip space. \n\n This should only be enabled if your XR Render Mode is set to \"Multi Pass\". \n\n This is required because there is no workaround to detect the \"Multi Pass\" rendering mode, when using XR."), new GlobalShaderFeature(GlobalShaderFeatureMode.Off, new List() { "MK_LINEAR_lIGHT_DISTANCE_ATTENUATION_OFF", "MK_LINEAR_lIGHT_DISTANCE_ATTENUATION" }, new List() {}, "Linear Light Attenuation", "Changes the light attenuation for non-directional lights more linear (Approximately like builtin renderpipeline).") }; [UnityEngine.SerializeReference] private List _globalShaderFeatures = new List(); [UnityEngine.SerializeReference][HideInInspector] private List _globalShaderFeaturesTemp = new List(); public static void BeginRegisterChangesOnGlobalShaderFeatures() { if(isReady) { _instance._globalShaderFeaturesTemp = new List(); for(int i = 0; i < _instance._globalShaderFeatures.Count; i++) { List identifiers = new List(); for(int id = 0; id < _instance._globalShaderFeatures[i].identifiers.Count; id++) { identifiers.Add(_instance._globalShaderFeatures[i].identifiers[id]); } List compileDirectives = new List(); for(int id = 0; id < _instance._globalShaderFeatures[i].compileDirectives.Count; id++) { compileDirectives.Add(_instance._globalShaderFeatures[i].compileDirectives[id]); } _instance._globalShaderFeaturesTemp.Add(new GlobalShaderFeature(_instance._globalShaderFeatures[i].modeEnum, identifiers, compileDirectives, _instance._globalShaderFeatures[i].name, _instance._globalShaderFeatures[i].tooltip)); } SaveInstance(); } } public static bool CheckGlobalShaderFeaturesChanges() { if(isReady) { bool hasChanged = false; for(int i = 0; i < _instance._globalShaderFeatures.Count; i++) { if(_instance._globalShaderFeaturesTemp[i].mode != _instance._globalShaderFeatures[i].mode) hasChanged = true; } return hasChanged; } else { return false; } } //List has to be updated once a feature is added //[MenuItem("MK/Install Wizard/Update Global Shader Features List")] private static void UpdateGlobalShaderFeatures() { if(isReady) { _instance._globalShaderFeatures = new List(_globalShaderFeaturesTemplate); SaveInstance(); BeginRegisterChangesOnGlobalShaderFeatures(); Debug.Log("Global Shader Features List Updated"); } } public static void DrawGlobalShaderFeaturesInspector() { if(isReady) { //linear atten is only for URP for(int i = 0; i < _instance._globalShaderFeatures.Count - 2; i++) { _instance._globalShaderFeatures[i].DrawInspector(); } if((int) (System.Object) _instance._globalShaderFeatures[_instance._globalShaderFeatures.Count - 3].modeEnum < 1) _instance._globalShaderFeatures[_instance._globalShaderFeatures.Count - 2].DrawInspector(); if(_instance._renderPipeline == RenderPipeline.Universal) _instance._globalShaderFeatures[_instance._globalShaderFeatures.Count - 1].DrawInspector(); } } private static void LogAssetNotFoundError() { Debug.LogError("Could not find Install Wizard Configuration Asset, please try to import the package again, if this issue persists."); } private static Configuration _instance = null; internal static Configuration TryGetInstance() { if(_instance == null) { string[] _guids = AssetDatabase.FindAssets("t:" + typeof(Configuration).Namespace + ".Configuration", null); if(_guids.Length > 0) { _instance = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(_guids[0]), typeof(Configuration)) as Configuration; if(_instance != null) return _instance; else { LogAssetNotFoundError(); return null; } } else { LogAssetNotFoundError(); return null; } } else return _instance; } internal static string TryGetPath() { if(isReady) { return AssetDatabase.GetAssetPath(_instance); } else { return string.Empty; } } internal static Texture2D TryGetTitleImage() { if(isReady) { return _instance._titleImage; } else { return null; } } internal static ExampleContainer[] TryGetExamples() { if(isReady) { return _instance._examples; } else { return null; } } internal static bool TryGetDisablePerOutlinesWarning() { if(isReady) { return _instance.disablePerObjectOutlinesWarning; } else { return false; } } internal static void TrySetDisablePerOutlinesWarning(bool v) { if(isReady) { _instance.disablePerObjectOutlinesWarning = v; SaveInstance(); } } internal static bool TryGetShowInstallerOnReload() { if(isReady) { return _instance.showInstallerOnReload; } else { return false; } } internal static void TrySetShowInstallerOnReload(bool v) { if(isReady) { _instance.showInstallerOnReload = v; SaveInstance(); } } internal static RenderPipeline TryGetRenderPipeline() { if(isReady) { return _instance._renderPipeline; } else { return RenderPipeline.Built_in; } } internal static void TrySetRenderPipeline(RenderPipeline v) { if(isReady) { _instance._renderPipeline = v; SaveInstance(); } } internal static void SaveInstance() { if(isReady) { EditorUtility.SetDirty(_instance); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } } internal static void ImportShaders(RenderPipeline renderPipeline) { if(isReady) { switch(renderPipeline) { case RenderPipeline.Built_in: AssetDatabase.ImportPackage(AssetDatabase.GetAssetPath(_instance._basePackageBuiltin), false); break; //case RenderPipeline.Lightweight: // AssetDatabase.ImportPackage(AssetDatabase.GetAssetPath(_instance._basePackageLWRP), false); //break; case RenderPipeline.Universal: AssetDatabase.ImportPackage(AssetDatabase.GetAssetPath(_instance._basePackageURP), false); break; default: //All cases should be handled break; } TrySetShowInstallerOnReload(false); } } internal static void ImportExamples(RenderPipeline renderPipeline) { if(isReady) { AssetDatabase.ImportPackage(AssetDatabase.GetAssetPath(_instance._examplesPackageInc), false); switch(renderPipeline) { case RenderPipeline.Built_in: #if UNITY_2023_2_OR_NEWER AssetDatabase.ImportPackage(AssetDatabase.GetAssetPath(_instance._examplesPackageBuiltin_2023_2_Or_Newer), false); #else AssetDatabase.ImportPackage(AssetDatabase.GetAssetPath(_instance._examplesPackageBuiltin), false); #endif break; case RenderPipeline.Universal: #if UNITY_2023_2_OR_NEWER AssetDatabase.ImportPackage(AssetDatabase.GetAssetPath(_instance._examplesPackageURP_2023_2_Or_Newer), false); #else AssetDatabase.ImportPackage(AssetDatabase.GetAssetPath(_instance._examplesPackageURP), false); #endif break; default: //All cases should be handled break; } } } internal static void OpenReadMe() { AssetDatabase.OpenAsset(_instance._readMe); } #if UNITY_2021_2_OR_NEWER public static void SetCompileDirectives() { if(isReady) { IEnumerable buildTargetsGroups = System.Enum.GetValues(typeof(BuildTargetGroup)).Cast().Where(b => b != BuildTargetGroup.Unknown).Where(b => !CheckIfObsolete(b)); foreach (BuildTargetGroup buildTargetGroup in buildTargetsGroups) { //string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup).Trim(); string[] defines; UnityEditor.Build.NamedBuildTarget namedBuildTarget = UnityEditor.Build.NamedBuildTarget.FromBuildTargetGroup(buildTargetGroup); PlayerSettings.GetScriptingDefineSymbols(namedBuildTarget, out defines); List definesList = defines.Where(b => !string.IsNullOrEmpty(b)).ToList(); for(int i = 0; i < _instance._globalShaderFeatures.Count; i++) { GlobalShaderFeatureBase gsfb = _instance._globalShaderFeatures[i]; for(int cd = 0; cd < gsfb.compileDirectives.Count; cd++) { if(gsfb.compileDirectives.Count > 0) { if (gsfb.compileDirectives[gsfb.mode] == null) continue; if (definesList.Contains(gsfb.compileDirectives[cd])) definesList.Remove(gsfb.compileDirectives[cd]); if(gsfb.mode > 0 && gsfb.mode == cd) { definesList.Add(gsfb.compileDirectives[gsfb.mode]); } } } } PlayerSettings.SetScriptingDefineSymbols(namedBuildTarget, definesList.ToArray()); } } } private static bool CheckIfObsolete(BuildTargetGroup buildTargetGroup) { System.Object[] attribute = typeof(BuildTargetGroup).GetField(buildTargetGroup.ToString()).GetCustomAttributes(typeof(System.ObsoleteAttribute), false); return attribute != null && attribute.Length > 0; } #else public static void SetCompileDirectives() { if(isReady) { IEnumerable buildTargetsGroups = System.Enum.GetValues(typeof(BuildTargetGroup)).Cast().Where(b => b != BuildTargetGroup.Unknown).Where(b => !CheckIfObsolete(b)); foreach (BuildTargetGroup buildTargetGroup in buildTargetsGroups) { string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup).Trim(); List definesList = defines.Split(';', ' ').Where(b => !string.IsNullOrEmpty(b)).ToList(); for(int i = 0; i < _instance._globalShaderFeatures.Count; i++) { GlobalShaderFeatureBase gsfb = _instance._globalShaderFeatures[i]; for(int cd = 0; cd < gsfb.compileDirectives.Count; cd++) { if(gsfb.compileDirectives.Count > 0) { if (gsfb.compileDirectives[gsfb.mode] == null) continue; if (definesList.Contains(gsfb.compileDirectives[cd])) definesList.Remove(gsfb.compileDirectives[cd]); if(gsfb.mode > 0 && gsfb.mode == cd) { definesList.Add(gsfb.compileDirectives[gsfb.mode]); } } } } if(definesList.Count > 0) defines = definesList.Aggregate((a, b) => a + ";" + b); else defines = string.Empty; PlayerSettings.SetScriptingDefineSymbolsForGroup(buildTargetGroup, defines); } } } private static bool CheckIfObsolete(BuildTargetGroup buildTargetGroup) { System.Object[] attribute = typeof(BuildTargetGroup).GetField(buildTargetGroup.ToString()).GetCustomAttributes(typeof(System.ObsoleteAttribute), false); return attribute != null && attribute.Length > 0; } #endif #if MK_URP public static void ShowURPOutlineWarning() { if(Configuration.TryGetDisablePerOutlinesWarning() || GraphicsSettings.currentRenderPipeline == null) return; bool MKToonPerObjectOutlinesRendererFeatureFound = false; try { RenderPipelineAsset renderPipelineAsset = GraphicsSettings.currentRenderPipeline; ScriptableRenderer defaultRenderer = (renderPipelineAsset as UniversalRenderPipelineAsset).scriptableRenderer; var scriptableRendererFeaturesProperty = typeof(ScriptableRenderer).GetProperty("rendererFeatures", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); System.Collections.Generic.List defaultScriptableRendererFeatures = scriptableRendererFeaturesProperty.GetValue(defaultRenderer) as System.Collections.Generic.List; foreach(ScriptableRendererFeature srf in defaultScriptableRendererFeatures) { if(srf.GetType() == typeof(MK.Toon.URP.MKToonPerObjectOutlines)) { MKToonPerObjectOutlinesRendererFeatureFound = true; break; } } } catch {} if(!MKToonPerObjectOutlinesRendererFeatureFound) { System.Text.StringBuilder warning = new System.Text.StringBuilder("Could not find the MK Toon Per Object Outlines Renderer Feature on your default renderer asset."); warning.Append(System.Environment.NewLine); warning.Append("The MK Toon Per Object Outline Renderer Feature is required in order to make the outlines rendered in a proper way"); warning.Append(System.Environment.NewLine); warning.Append(System.Environment.NewLine); warning.Append("It's recommend to attach the MK Toon Per Object Outlines Renderer Feature to every of your renderer assets that should render the per object outlines of MK Toon. Otherwise outlines can't be rendered."); EditorGUILayout.HelpBox(warning.ToString(), MessageType.Warning); if(GUILayout.Button("Find all Renderer Assets in Project Window")) { FindAllURPRendererAssetsInProjectWindow(); } if(GUILayout.Button("Select active default Renderer Asset")) { SelectDefaultRendererAsset(); } } } [MenuItem("Window/MK/Toon/Select Default URP Renderer Asset")] public static void SelectDefaultRendererAsset() { try { RenderPipelineAsset renderPipelineAsset = GraphicsSettings.currentRenderPipeline; UniversalRenderPipelineAsset universalRenderPipelineAsset = renderPipelineAsset as UniversalRenderPipelineAsset; var defaultScriptableRendererIndexField = typeof(UniversalRenderPipelineAsset).GetField("m_DefaultRendererIndex", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); int defaultRendererIndex = (int) defaultScriptableRendererIndexField.GetValue(universalRenderPipelineAsset); var scriptableRendererDataList = typeof(UniversalRenderPipelineAsset).GetField("m_RendererDataList", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); ScriptableRendererData[] scriptableRenderers = scriptableRendererDataList.GetValue(universalRenderPipelineAsset) as ScriptableRendererData[]; Selection.objects = new UnityEngine.Object[] { scriptableRenderers[defaultRendererIndex] }; EditorGUIUtility.PingObject(scriptableRenderers[defaultRendererIndex]); } catch { Debug.LogWarning("Could not find your default URP Renderer Asset"); } } [MenuItem("Window/MK/Toon/Find All URP Renderer Assets in Project Window")] public static void FindAllURPRendererAssetsInProjectWindow() { try { System.Type projectBrowserType = System.Type.GetType("UnityEditor.ProjectBrowser, UnityEditor"); EditorWindow window = EditorWindow.GetWindow(projectBrowserType); System.Reflection.MethodInfo searchMethod = projectBrowserType.GetMethod("SetSearch", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance, null, new System.Type[] { typeof(string) }, null); searchMethod.Invoke(window, new object[] { "t:UnityEngine.Rendering.Universal.ScriptableRendererData" }); } catch { Debug.LogWarning("Unable to find your URP Renderer Assets"); } } #endif #pragma warning restore CS0414 } } #endif