mixedreality/com.microsoft.mixedreality..../Core/Utilities/StandardShader/MeshOutline.cs

148 lines
5.5 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace Microsoft.MixedReality.Toolkit.Utilities
{
/// <summary>
/// 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.
/// </summary>
[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
/// <summary>
/// Gathers initial render state.
/// </summary>
private void Awake()
{
meshRenderer = GetComponent<MeshRenderer>();
propertyBlock = new MaterialPropertyBlock();
vertexExtrusionValueID = Shader.PropertyToID(vertexExtrusionValueName);
defaultMaterials = meshRenderer.sharedMaterials;
}
/// <summary>
/// Enables the outline.
/// </summary>
private void OnEnable()
{
ApplyOutlineMaterial();
}
/// <summary>
/// Resets the renderer materials to the default settings.
/// </summary>
private void OnDisable()
{
meshRenderer.materials = defaultMaterials;
}
/// <summary>
/// Removes any components this component has created.
/// </summary>
private void OnDestroy()
{
Destroy(createdMeshSmoother);
}
#endregion MonoBehaviour Implementation
#region BaseMeshOutline Implementation
/// <summary>
/// Prepares and applies the current outline material to the renderer.
/// </summary>
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<MeshSmoother>() : createdMeshSmoother;
if (meshSmoother == null)
{
createdMeshSmoother = gameObject.AddComponent<MeshSmoother>();
meshSmoother = createdMeshSmoother;
}
meshSmoother.SmoothNormals();
}
ApplyOutlineWidth();
// Add the outline material as another material pass.
var materials = new List<Material>(defaultMaterials);
materials.Add(outlineMaterial);
meshRenderer.materials = materials.ToArray();
}
}
/// <summary>
/// Updates the current vertex extrusion value used by the shader.
/// </summary>
protected override void ApplyOutlineWidth()
{
if (meshRenderer != null && propertyBlock != null)
{
meshRenderer.GetPropertyBlock(propertyBlock);
propertyBlock.SetFloat(vertexExtrusionValueName, outlineWidth);
meshRenderer.SetPropertyBlock(propertyBlock);
}
}
#endregion BaseMeshOutline Implementation
/// <summary>
/// Searches for the minimum render queue value in a list of materials.
/// </summary>
/// <param name="materials">The list of materials to search.</param>
/// <returns>The minimum render queue value.</returns>
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;
}
}
}