123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
-
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine;
- [DisallowMultipleComponent]
- public class Outline : MonoBehaviour {
- private static HashSet<Mesh> registeredMeshes = new HashSet<Mesh>();
- public enum Mode {
- OutlineAll,
- OutlineVisible,
- OutlineHidden,
- OutlineAndSilhouette,
- SilhouetteOnly
- }
- 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;
- }
- }
- [Serializable]
- private class ListVector3 {
- public List<Vector3> data;
- }
- [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<Mesh> bakeKeys = new List<Mesh>();
- [SerializeField, HideInInspector]
- private List<ListVector3> bakeValues = new List<ListVector3>();
- private Renderer[] renderers;
- private Material outlineMaskMaterial;
- private Material outlineFillMaterial;
- private bool needsUpdate;
- void Awake() {
-
- renderers = GetComponentsInChildren<Renderer>();
-
- outlineMaskMaterial = Instantiate(Resources.Load<Material>(@"Materials/OutlineMask"));
- outlineFillMaterial = Instantiate(Resources.Load<Material>(@"Materials/OutlineFill"));
- outlineMaskMaterial.name = "OutlineMask (Instance)";
- outlineFillMaterial.name = "OutlineFill (Instance)";
-
- LoadSmoothNormals();
-
- needsUpdate = true;
- }
- void OnEnable() {
- foreach (var renderer in renderers) {
-
- var materials = renderer.sharedMaterials.ToList();
- materials.Add(outlineMaskMaterial);
- materials.Add(outlineFillMaterial);
- renderer.materials = materials.ToArray();
- }
- }
- void OnValidate() {
-
- needsUpdate = true;
-
- if (!precomputeOutline && bakeKeys.Count != 0 || bakeKeys.Count != bakeValues.Count) {
- bakeKeys.Clear();
- bakeValues.Clear();
- }
-
- if (precomputeOutline && bakeKeys.Count == 0) {
- Bake();
- }
- }
- void Update() {
- if (needsUpdate) {
- needsUpdate = false;
- UpdateMaterialProperties();
- }
- }
- void OnDisable() {
- foreach (var renderer in renderers) {
-
- var materials = renderer.sharedMaterials.ToList();
- materials.Remove(outlineMaskMaterial);
- materials.Remove(outlineFillMaterial);
- renderer.materials = materials.ToArray();
- }
- }
- void OnDestroy() {
-
- Destroy(outlineMaskMaterial);
- Destroy(outlineFillMaterial);
- }
- void Bake() {
-
- var bakedMeshes = new HashSet<Mesh>();
- foreach (var meshFilter in GetComponentsInChildren<MeshFilter>()) {
-
- if (!bakedMeshes.Add(meshFilter.sharedMesh)) {
- continue;
- }
-
- var smoothNormals = SmoothNormals(meshFilter.sharedMesh);
- bakeKeys.Add(meshFilter.sharedMesh);
- bakeValues.Add(new ListVector3() { data = smoothNormals });
- }
- }
- void LoadSmoothNormals() {
-
- foreach (var meshFilter in GetComponentsInChildren<MeshFilter>()) {
-
- if (!registeredMeshes.Add(meshFilter.sharedMesh)) {
- continue;
- }
-
- var index = bakeKeys.IndexOf(meshFilter.sharedMesh);
- var smoothNormals = (index >= 0) ? bakeValues[index].data : SmoothNormals(meshFilter.sharedMesh);
-
- meshFilter.sharedMesh.SetUVs(3, smoothNormals);
- }
-
- foreach (var skinnedMeshRenderer in GetComponentsInChildren<SkinnedMeshRenderer>()) {
- if (registeredMeshes.Add(skinnedMeshRenderer.sharedMesh)) {
- skinnedMeshRenderer.sharedMesh.uv4 = new Vector2[skinnedMeshRenderer.sharedMesh.vertexCount];
- }
- }
- }
- List<Vector3> SmoothNormals(Mesh mesh) {
-
- var groups = mesh.vertices.Select((vertex, index) => new KeyValuePair<Vector3, int>(vertex, index)).GroupBy(pair => pair.Key);
-
- var smoothNormals = new List<Vector3>(mesh.normals);
-
- foreach (var group in groups) {
-
- if (group.Count() == 1) {
- continue;
- }
-
- var smoothNormal = Vector3.zero;
- foreach (var pair in group) {
- smoothNormal += mesh.normals[pair.Value];
- }
- smoothNormal.Normalize();
-
- foreach (var pair in group) {
- smoothNormals[pair.Value] = smoothNormal;
- }
- }
- return smoothNormals;
- }
- void UpdateMaterialProperties() {
-
- outlineFillMaterial.SetColor("_OutlineColor", outlineColor);
- switch (outlineMode) {
- case Mode.OutlineAll:
- outlineMaskMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Always);
- outlineFillMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Always);
- outlineFillMaterial.SetFloat("_OutlineWidth", outlineWidth);
- break;
- case Mode.OutlineVisible:
- outlineMaskMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Always);
- outlineFillMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.LessEqual);
- outlineFillMaterial.SetFloat("_OutlineWidth", outlineWidth);
- break;
- case Mode.OutlineHidden:
- outlineMaskMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Always);
- outlineFillMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Greater);
- outlineFillMaterial.SetFloat("_OutlineWidth", outlineWidth);
- break;
- case Mode.OutlineAndSilhouette:
- outlineMaskMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.LessEqual);
- outlineFillMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Always);
- outlineFillMaterial.SetFloat("_OutlineWidth", outlineWidth);
- break;
- case Mode.SilhouetteOnly:
- outlineMaskMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.LessEqual);
- outlineFillMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Greater);
- outlineFillMaterial.SetFloat("_OutlineWidth", 0);
- break;
- }
- }
- }
|