SoftMaskForUGUI/Scripts/SoftMaskable.cs

262 lines
6.1 KiB
C#
Raw Normal View History

2018-11-20 07:49:14 +08:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
namespace Coffee.UIExtensions
{
/// <summary>
/// Soft maskable.
/// Add this component to Graphic under SoftMask for smooth masking.
/// </summary>
[ExecuteInEditMode]
public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter
{
//################################
// Constant or Static Members.
//################################
static List<SoftMaskable> s_ActiveSoftMaskables;
static Material defaultMaterial = null;
//################################
// Serialize Members.
//################################
[Tooltip("The graphic will be visible only in areas where no mask is present.")]
[SerializeField] bool m_Inverse = false;
//################################
// Public Members.
//################################
/// <summary>
/// Perform material modification in this function.
/// </summary>
/// <returns>Modified material.</returns>
/// <param name="baseMaterial">Configured Material.</param>
public Material GetModifiedMaterial(Material baseMaterial)
{
_softMask = null;
if (!isActiveAndEnabled)
{
return baseMaterial;
}
// Find the nearest parent softmask.
var parentTransform = transform;
while (parentTransform)
{
var sm = parentTransform.GetComponent<SoftMask>();
if (sm && sm.enabled)
{
_softMask = sm;
break;
}
parentTransform = parentTransform.parent;
}
Material result = baseMaterial;
if (_softMask)
{
result = new Material(baseMaterial);
result.hideFlags = HideFlags.HideAndDontSave;
result.SetTexture(s_SoftMaskTexId, _softMask.softMaskBuffer);
if (m_Inverse)
{
result.SetFloat(s_SoftMaskInverseId, 1);
result.SetInt(s_StencilCompId, (int)CompareFunction.Always);
}
else
{
result.SetFloat(s_SoftMaskInverseId, 0);
result.SetInt(s_StencilCompId, (int)CompareFunction.Equal);
}
StencilMaterial.Remove(baseMaterial);
ReleaseMaterial(ref _maskMaterial);
_maskMaterial = result;
#if UNITY_EDITOR
result.EnableKeyword("SOFTMASK_EDITOR");
UpdateSceneViewMatrixForShader();
#endif
}
else
{
baseMaterial.SetTexture(s_SoftMaskTexId, Texture2D.whiteTexture);
}
return result;
}
/// <summary>
/// Given a point and a camera is the raycast valid.
/// </summary>
/// <returns>Valid.</returns>
/// <param name="sp">Screen position.</param>
/// <param name="eventCamera">Raycast camera.</param>
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
if (!isActiveAndEnabled || !_softMask)
return true;
if (!RectTransformUtility.RectangleContainsScreenPoint(transform as RectTransform, sp, eventCamera))
return false;
return _softMask.IsRaycastLocationValid(sp, eventCamera, graphic) != m_Inverse;
}
/// <summary>
/// The graphic will be visible only in areas where no mask is present.
/// </summary>
public bool inverse
{
get { return m_Inverse; }
set
{
if (m_Inverse != value)
{
m_Inverse = value;
graphic.SetMaterialDirty();
}
}
}
/// <summary>
/// The graphic associated with the soft mask.
/// </summary>
public Graphic graphic{ get { return _graphic ? _graphic : _graphic = GetComponent<Graphic>(); } }
//################################
// Private Members.
//################################
Graphic _graphic = null;
SoftMask _softMask = null;
Material _maskMaterial = null;
static int s_SoftMaskTexId;
static int s_StencilCompId;
static int s_SoftMaskInverseId;
#if UNITY_EDITOR
/// <summary>
/// Update the scene view matrix for shader.
/// </summary>
static void UpdateSceneViewMatrixForShader()
{
UnityEditor.SceneView sceneView = UnityEditor.SceneView.lastActiveSceneView;
if (!sceneView || !sceneView.camera)
{
return;
}
Camera cam = sceneView.camera;
Matrix4x4 w2c = cam.worldToCameraMatrix;
Matrix4x4 prj = cam.projectionMatrix;
foreach (var sm in s_ActiveSoftMaskables)
{
2018-12-15 19:20:14 +08:00
if(sm)
2018-11-20 07:49:14 +08:00
{
2018-12-15 19:20:14 +08:00
Material mat = sm._maskMaterial;
if (mat)
{
mat.SetMatrix ("_SceneView", w2c);
mat.SetMatrix ("_SceneProj", prj);
}
2018-11-20 07:49:14 +08:00
}
}
}
/// <summary>
/// This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).
/// </summary>
void OnValidate()
{
if (graphic)
{
graphic.SetMaterialDirty();
}
}
#endif
/// <summary>
/// This function is called when the object becomes enabled and active.
/// </summary>
void OnEnable()
{
// Register.
if (s_ActiveSoftMaskables == null)
{
s_ActiveSoftMaskables = new List<SoftMaskable>();
2018-11-20 10:30:07 +08:00
#if UNITY_EDITOR
2018-11-20 07:49:14 +08:00
UnityEditor.EditorApplication.update += UpdateSceneViewMatrixForShader;
2018-11-20 10:30:07 +08:00
#endif
2018-11-20 07:49:14 +08:00
s_SoftMaskTexId = Shader.PropertyToID("_SoftMaskTex");
s_StencilCompId = Shader.PropertyToID("_StencilComp");
s_SoftMaskInverseId = Shader.PropertyToID("_SoftMaskInverse");
}
s_ActiveSoftMaskables.Add(this);
var g = graphic;
if (g)
{
if (!g.material || g.material == Graphic.defaultGraphicMaterial)
{
g.material = defaultMaterial ?? (defaultMaterial = new Material (Resources.Load<Shader> ("UI-Default-SoftMask")) { hideFlags = HideFlags.HideAndDontSave, });
2018-11-20 07:49:14 +08:00
}
g.SetMaterialDirty();
}
_softMask = null;
}
/// <summary>
/// This function is called when the behaviour becomes disabled.
/// </summary>
void OnDisable()
{
s_ActiveSoftMaskables.Remove(this);
var g = graphic;
if (g)
{
if (g.material == defaultMaterial)
{
g.material = null;
}
g.SetMaterialDirty();
}
ReleaseMaterial(ref _maskMaterial);
_softMask = null;
}
/// <summary>
/// Release the material.
/// </summary>
void ReleaseMaterial(ref Material mat)
{
if (mat)
{
StencilMaterial.Remove(mat);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
DestroyImmediate(mat);
}
else
#endif
{
Destroy(mat);
}
mat = null;
}
}
}
}