using System; using System.Collections.Generic; using System.Linq; using UnityEngine; [DisallowMultipleComponent] public class Outline : MonoBehaviour { public enum Mode { OutlineAll = 0, OutlineVisible = 1, OutlineHidden = 2, OutlineAndSilhouette = 3, SilhouetteOnly = 4 } [Serializable] private class ListVector3 { public List data; } private static HashSet registeredMeshes = new HashSet(); [SerializeField] private Mode outlineMode; [SerializeField] private Color outlineColor = Color.white; [SerializeField] [Range(0f, 10f)] private float outlineWidth = 2f; [Header("Optional")] [SerializeField] [Tooltip("Precompute enabled: Per-vertex calculations are performed in the editor and serialized with the object. Precompute disabled: Per-vertex calculations are performed at runtime in Awake(). This may cause a pause for large meshes.")] private bool precomputeOutline; [SerializeField] [HideInInspector] private List bakeKeys = new List(); [SerializeField] [HideInInspector] private List bakeValues = new List(); private Renderer[] renderers; private Material outlineMaskMaterial; private Material outlineFillMaterial; private bool needsUpdate; public Mode OutlineMode { get { return outlineMode; } set { outlineMode = value; needsUpdate = true; } } public Color OutlineColor { get { return outlineColor; } set { outlineColor = value; needsUpdate = true; } } public float OutlineWidth { get { return outlineWidth; } set { outlineWidth = value; needsUpdate = true; } } private void Awake() { renderers = GetComponentsInChildren(); outlineMaskMaterial = UnityEngine.Object.Instantiate(Resources.Load("Materials/OutlineMask")); outlineFillMaterial = UnityEngine.Object.Instantiate(Resources.Load("Materials/OutlineFill")); outlineMaskMaterial.name = "OutlineMask (Instance)"; outlineFillMaterial.name = "OutlineFill (Instance)"; LoadSmoothNormals(); needsUpdate = true; } private void OnEnable() { Renderer[] array = renderers; foreach (Renderer obj in array) { List list = obj.sharedMaterials.ToList(); list.Add(outlineMaskMaterial); list.Add(outlineFillMaterial); obj.materials = list.ToArray(); } } private void OnValidate() { needsUpdate = true; if ((!precomputeOutline && bakeKeys.Count != 0) || bakeKeys.Count != bakeValues.Count) { bakeKeys.Clear(); bakeValues.Clear(); } if (precomputeOutline && bakeKeys.Count == 0) { Bake(); } } private void Update() { if (needsUpdate) { needsUpdate = false; UpdateMaterialProperties(); } } private void OnDisable() { Renderer[] array = renderers; foreach (Renderer obj in array) { List list = obj.sharedMaterials.ToList(); list.Remove(outlineMaskMaterial); list.Remove(outlineFillMaterial); obj.materials = list.ToArray(); } } private void OnDestroy() { UnityEngine.Object.Destroy(outlineMaskMaterial); UnityEngine.Object.Destroy(outlineFillMaterial); } private void Bake() { HashSet hashSet = new HashSet(); MeshFilter[] componentsInChildren = GetComponentsInChildren(); foreach (MeshFilter meshFilter in componentsInChildren) { if (hashSet.Add(meshFilter.sharedMesh)) { List data = SmoothNormals(meshFilter.sharedMesh); bakeKeys.Add(meshFilter.sharedMesh); bakeValues.Add(new ListVector3 { data = data }); } } } private void LoadSmoothNormals() { MeshFilter[] componentsInChildren = GetComponentsInChildren(); foreach (MeshFilter meshFilter in componentsInChildren) { if (registeredMeshes.Add(meshFilter.sharedMesh)) { int num = bakeKeys.IndexOf(meshFilter.sharedMesh); List uvs = ((num >= 0) ? bakeValues[num].data : SmoothNormals(meshFilter.sharedMesh)); meshFilter.sharedMesh.SetUVs(3, uvs); } } SkinnedMeshRenderer[] componentsInChildren2 = GetComponentsInChildren(); foreach (SkinnedMeshRenderer skinnedMeshRenderer in componentsInChildren2) { if (registeredMeshes.Add(skinnedMeshRenderer.sharedMesh)) { skinnedMeshRenderer.sharedMesh.uv4 = new Vector2[skinnedMeshRenderer.sharedMesh.vertexCount]; } } } private List SmoothNormals(Mesh mesh) { IEnumerable>> enumerable = from pair in mesh.vertices.Select((Vector3 vertex, int index) => new KeyValuePair(vertex, index)) group pair by pair.Key; List list = new List(mesh.normals); foreach (IGrouping> item in enumerable) { if (item.Count() == 1) { continue; } Vector3 zero = Vector3.zero; foreach (KeyValuePair item2 in item) { zero += mesh.normals[item2.Value]; } zero.Normalize(); foreach (KeyValuePair item3 in item) { list[item3.Value] = zero; } } return list; } private void UpdateMaterialProperties() { outlineFillMaterial.SetColor("_OutlineColor", outlineColor); switch (outlineMode) { case Mode.OutlineAll: outlineMaskMaterial.SetFloat("_ZTest", 8f); outlineFillMaterial.SetFloat("_ZTest", 8f); outlineFillMaterial.SetFloat("_OutlineWidth", outlineWidth); break; case Mode.OutlineVisible: outlineMaskMaterial.SetFloat("_ZTest", 8f); outlineFillMaterial.SetFloat("_ZTest", 4f); outlineFillMaterial.SetFloat("_OutlineWidth", outlineWidth); break; case Mode.OutlineHidden: outlineMaskMaterial.SetFloat("_ZTest", 8f); outlineFillMaterial.SetFloat("_ZTest", 5f); outlineFillMaterial.SetFloat("_OutlineWidth", outlineWidth); break; case Mode.OutlineAndSilhouette: outlineMaskMaterial.SetFloat("_ZTest", 4f); outlineFillMaterial.SetFloat("_ZTest", 8f); outlineFillMaterial.SetFloat("_OutlineWidth", outlineWidth); break; case Mode.SilhouetteOnly: outlineMaskMaterial.SetFloat("_ZTest", 4f); outlineFillMaterial.SetFloat("_ZTest", 5f); outlineFillMaterial.SetFloat("_OutlineWidth", 0f); break; } } }