using System.Collections.Generic; using System.Linq; using System.Reflection; using FlatKit; using JetBrains.Annotations; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; public static class ObjectOutlineEditorUtils { private static readonly GUIStyle RichHelpBoxStyle = new(EditorStyles.helpBox) { richText = true }; public static void SetActive(Material material, bool active) { var renderer = GetRenderer(Camera.current ?? Camera.main); if (renderer == null) { const string m = "ScriptableRendererData is required to enable per-object outlines.\n" + "Please assign a URP Asset in the Graphics settings."; EditorGUILayout.LabelField(m, RichHelpBoxStyle); return; } var features = GetRendererFeatures(renderer); if (features == null) { const string m = "ScriptableRendererFeature is required to enable per-object outlines.\n" + "Please assign a URP Asset in the Graphics settings."; EditorGUILayout.LabelField(m, RichHelpBoxStyle); return; } var feature = features.FirstOrDefault(f => f.GetType() == typeof(ObjectOutlineRendererFeature)); if (feature == null) { if (active) { // Add the feature. var rendererData = GetRendererData(); if (rendererData == null) return; feature = ScriptableObject.CreateInstance(); feature.name = "Flat Kit Per Object Outline"; AddRendererFeature(rendererData, feature); var m = $"[Flat Kit] Added {feature.name} Renderer " + $"Feature to {rendererData.name}."; Debug.Log(m, rendererData); } else { // Feature not added and outline is disabled. return; } } // Register the material. This handles the feature activation. var outlineFeature = feature as ObjectOutlineRendererFeature; if (outlineFeature == null) { Debug.LogError("ObjectOutlineRendererFeature not found"); return; } var featureIsUsed = outlineFeature.RegisterMaterial(material, active); // Remove the feature if no materials are using it. if (!featureIsUsed) { var rendererData = GetRendererData(); if (rendererData == null) return; RemoveRendererFeature(rendererData, feature); var m = $"[Flat Kit] Removed {feature.name} Renderer " + $"Feature from {rendererData.name} because no materials are using it."; Debug.Log(m, rendererData); } } private static void AddRendererFeature(ScriptableRendererData rendererData, ScriptableRendererFeature feature) { // Save the asset as a sub-asset. AssetDatabase.AddObjectToAsset(feature, rendererData); rendererData.rendererFeatures.Add(feature); rendererData.SetDirty(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } private static void RemoveRendererFeature(ScriptableRendererData rendererData, ScriptableRendererFeature feature) { rendererData.rendererFeatures.Remove(feature); rendererData.SetDirty(); // Remove the asset. AssetDatabase.RemoveObjectFromAsset(feature); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } [CanBeNull] private static ScriptableRenderer GetRenderer(Camera camera) { if (!camera) { return null; } var additionalCameraData = camera.GetComponent(); if (!additionalCameraData) { return null; } var renderer = additionalCameraData.scriptableRenderer; return renderer; } private static List GetRendererFeatures(ScriptableRenderer renderer) { var property = typeof(ScriptableRenderer).GetProperty("rendererFeatures", BindingFlags.NonPublic | BindingFlags.Instance); if (property == null) return null; var features = property.GetValue(renderer) as List; return features; } internal static ScriptableRendererData GetRendererData() { #if UNITY_6000_0_OR_NEWER var srpAsset = GraphicsSettings.defaultRenderPipeline; #else var srpAsset = GraphicsSettings.renderPipelineAsset; #endif if (srpAsset == null) { const string m = "Flat Kit No SRP asset found. Please assign a URP Asset in the Graphics settings " + "to enable per-object outlines."; Debug.LogError(m); return null; } var field = typeof(UniversalRenderPipelineAsset).GetField("m_RendererDataList", BindingFlags.NonPublic | BindingFlags.Instance); var rendererDataList = (ScriptableRendererData[])field!.GetValue(srpAsset); var rendererData = rendererDataList.FirstOrDefault(); if (rendererData == null) { Debug.LogError("No ScriptableRendererData found"); return null; } return rendererData; } }