// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; namespace Microsoft.MixedReality.Toolkit.Utilities { /// /// Component which can be used to render an outline around a mesh renderer. Enabling this component introduces an additional render pass /// of the object being outlined, but is designed to run performantly on mobile Mixed Reality devices and does not utilize any post processes. /// This behavior is designed to be used in conjunction with the MRTK/Standard shader. Limitations of this effect include it not working well /// on objects which are not watertight (or required to be two sided) and depth sorting issues can occur on overlapping objects. /// [RequireComponent(typeof(MeshRenderer))] [AddComponentMenu("Scripts/MRTK/Core/MeshOutline")] public class MeshOutline : BaseMeshOutline { private const string vertexExtrusionKeyword = "_VERTEX_EXTRUSION"; private const string vertexExtrusionSmoothNormalsKeyword = "_VERTEX_EXTRUSION_SMOOTH_NORMALS"; private const string vertexExtrusionValueName = "_VertexExtrusionValue"; private MeshRenderer meshRenderer = null; private MaterialPropertyBlock propertyBlock = null; private int vertexExtrusionValueID = 0; private Material[] defaultMaterials = null; private MeshSmoother createdMeshSmoother = null; #region MonoBehaviour Implementation /// /// Gathers initial render state. /// private void Awake() { meshRenderer = GetComponent(); propertyBlock = new MaterialPropertyBlock(); vertexExtrusionValueID = Shader.PropertyToID(vertexExtrusionValueName); defaultMaterials = meshRenderer.sharedMaterials; } /// /// Enables the outline. /// private void OnEnable() { ApplyOutlineMaterial(); } /// /// Resets the renderer materials to the default settings. /// private void OnDisable() { meshRenderer.materials = defaultMaterials; } /// /// Removes any components this component has created. /// private void OnDestroy() { Destroy(createdMeshSmoother); } #endregion MonoBehaviour Implementation #region BaseMeshOutline Implementation /// /// Prepares and applies the current outline material to the renderer. /// protected override void ApplyOutlineMaterial() { if (outlineMaterial != null && meshRenderer != null) { Debug.AssertFormat(outlineMaterial.IsKeywordEnabled(vertexExtrusionKeyword), "The material \"{0}\" does not have vertex extrusion enabled, no outline will be rendered.", outlineMaterial.name); // Ensure that the outline material always renders before the default materials. outlineMaterial.renderQueue = GetMinRenderQueue(defaultMaterials) - 1; // If smooth normals are requested, make sure the mesh has smooth normals. if (outlineMaterial.IsKeywordEnabled(vertexExtrusionSmoothNormalsKeyword)) { var meshSmoother = (createdMeshSmoother == null) ? gameObject.GetComponent() : createdMeshSmoother; if (meshSmoother == null) { createdMeshSmoother = gameObject.AddComponent(); meshSmoother = createdMeshSmoother; } meshSmoother.SmoothNormals(); } ApplyOutlineWidth(); // Add the outline material as another material pass. var materials = new List(defaultMaterials); materials.Add(outlineMaterial); meshRenderer.materials = materials.ToArray(); } } /// /// Updates the current vertex extrusion value used by the shader. /// protected override void ApplyOutlineWidth() { if (meshRenderer != null && propertyBlock != null) { meshRenderer.GetPropertyBlock(propertyBlock); propertyBlock.SetFloat(vertexExtrusionValueName, outlineWidth); meshRenderer.SetPropertyBlock(propertyBlock); } } #endregion BaseMeshOutline Implementation /// /// Searches for the minimum render queue value in a list of materials. /// /// The list of materials to search. /// The minimum render queue value. private static int GetMinRenderQueue(Material[] materials) { var min = int.MaxValue; foreach (var material in materials) { if (material != null) { min = Mathf.Min(min, material.renderQueue); } } if (min == int.MaxValue) { min = (int)RenderQueue.Background; } return min; } } }