123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- using System;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Profiling;
- using UnityEngine.Rendering;
- using UnityEngine.UI;
- using MaskIntr = UnityEngine.SpriteMaskInteraction;
- namespace Coffee.UISoftMask
- {
- /// <summary>
- /// Soft maskable.
- /// Add this component to Graphic under SoftMask for smooth masking.
- /// </summary>
- #if UNITY_2018_3_OR_NEWER
- [ExecuteAlways]
- #else
- [ExecuteInEditMode]
- # endif
- [RequireComponent(typeof(Graphic))]
- public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter
- #if UNITY_EDITOR
- , ISerializationCallbackReceiver
- # endif
- {
- private const int kVisibleInside = (1 << 0) + (1 << 2) + (1 << 4) + (1 << 6);
- private const int kVisibleOutside = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6);
- private static readonly Hash128 k_InvalidHash = new Hash128();
- private static int s_SoftMaskTexId;
- private static int s_StencilCompId;
- private static int s_MaskInteractionId;
- private static int s_GameVPId;
- private static int s_GameTVPId;
- private static List<SoftMaskable> s_ActiveSoftMaskables;
- private static int[] s_Interactions = new int[4];
- [SerializeField, HideInInspector, System.Obsolete]
- private bool m_Inverse;
- [SerializeField, Tooltip("The interaction for each masks."), HideInInspector]
- private int m_MaskInteraction = kVisibleInside;
- [SerializeField, Tooltip("Use stencil to mask.")]
- private bool m_UseStencil = true;
- [SerializeField, Tooltip("Use soft-masked raycast target.\n\nNote: This option is expensive.")]
- private bool m_RaycastFilter;
- private Graphic _graphic;
- public SoftMask RootMask;
- private SoftMask _softMask;
- private Hash128 _effectMaterialHash;
- /// <summary>
- /// The graphic will be visible only in areas where no mask is present.
- /// </summary>
- public bool inverse
- {
- get { return m_MaskInteraction == kVisibleOutside; }
- set
- {
- var intValue = value ? kVisibleOutside : kVisibleInside;
- if (m_MaskInteraction == intValue) return;
- m_MaskInteraction = intValue;
- graphic.SetMaterialDirtyEx();
- }
- }
- /// <summary>
- /// Use soft-masked raycast target. This option is expensive.
- /// </summary>
- public bool raycastFilter
- {
- get { return m_RaycastFilter; }
- set { m_RaycastFilter = value; }
- }
- /// <summary>
- /// Use stencil to mask.
- /// </summary>
- public bool useStencil
- {
- get { return m_UseStencil; }
- set
- {
- if (m_UseStencil == value) return;
- m_UseStencil = value;
- graphic.SetMaterialDirtyEx();
- }
- }
- /// <summary>
- /// The graphic associated with the soft mask.
- /// </summary>
- public Graphic graphic
- {
- get
- {
-
- return _graphic ? _graphic : _graphic = GetComponent<Graphic>();
- }
- }
- public SoftMask softMask
- {
- get
- {
- if (RootMask != null)
- {
- return RootMask;
- }
- return _softMask ? _softMask : _softMask = this.GetComponentInParentEx<SoftMask>();
- }
- }
- public Material modifiedMaterial { get; private set; }
- /// <summary>
- /// Perform material modification in this function.
- /// </summary>
- /// <returns>Modified material.</returns>
- /// <param name="baseMaterial">Configured Material.</param>
- Material IMaterialModifier.GetModifiedMaterial(Material baseMaterial)
- {
- _softMask = null;
- modifiedMaterial = null;
- // If this component is disabled, the material is returned as is.
- // If the parents do not have a soft mask component, the material is returned as is.
- if (!isActiveAndEnabled || !softMask)
- {
- MaterialCache.Unregister(_effectMaterialHash);
- _effectMaterialHash = k_InvalidHash;
- return baseMaterial;
- }
- // Generate soft maskable material.
- var previousHash = _effectMaterialHash;
- _effectMaterialHash = new Hash128(
- (uint) baseMaterial.GetInstanceID(),
- (uint) softMask.GetInstanceID(),
- (uint) m_MaskInteraction,
- (uint) (m_UseStencil ? 1 : 0)
- );
- // Generate soft maskable material.
- modifiedMaterial = MaterialCache.Register(baseMaterial, _effectMaterialHash, mat =>
- {
- mat.shader = Shader.Find(string.Format("Hidden/{0} (SoftMaskable)", mat.shader.name));
- mat.SetTexture(s_SoftMaskTexId, softMask.softMaskBuffer);
- mat.SetInt(s_StencilCompId, m_UseStencil ? (int) CompareFunction.Equal : (int) CompareFunction.Always);
- #if UNITY_EDITOR
- mat.EnableKeyword("SOFTMASK_EDITOR");
- UpdateMaterialForSceneView(mat);
- #endif
- var root = MaskUtilities.FindRootSortOverrideCanvas(transform);
- var stencil = MaskUtilities.GetStencilDepth(transform, root);
- mat.SetVector(s_MaskInteractionId, new Vector4(
- 1 <= stencil ? (m_MaskInteraction >> 0 & 0x3) : 0,
- 2 <= stencil ? (m_MaskInteraction >> 2 & 0x3) : 0,
- 3 <= stencil ? (m_MaskInteraction >> 4 & 0x3) : 0,
- 4 <= stencil ? (m_MaskInteraction >> 6 & 0x3) : 0
- ));
- });
- // Unregister the previous material.
- MaterialCache.Unregister(previousHash);
- return modifiedMaterial;
- }
- /// <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>
- bool ICanvasRaycastFilter.IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
- {
- if (!isActiveAndEnabled || !softMask)
- return true;
- if (!RectTransformUtility.RectangleContainsScreenPoint(transform as RectTransform, sp, eventCamera))
- return false;
- if (m_RaycastFilter)
- {
- var sm = _softMask;
- for (var i = 0; i < 4; i++)
- {
- s_Interactions[i] = sm ? ((m_MaskInteraction >> i * 2) & 0x3) : 0;
- sm = sm ? sm.parent : null;
- }
- return _softMask.IsRaycastLocationValid(sp, eventCamera, graphic, s_Interactions);
- }
- else
- {
- var sm = _softMask;
- for (var i = 0; i < 4; i++)
- {
- if (!sm) break;
- s_Interactions[i] = sm ? ((m_MaskInteraction >> i * 2) & 0x3) : 0;
- var interaction = s_Interactions[i] == 1;
- var rt = sm.transform as RectTransform;
- var inRect = RectTransformUtility.RectangleContainsScreenPoint(rt, sp, eventCamera);
- if (!sm.ignoreSelfGraphic && interaction != inRect) return false;
- foreach (var child in sm._children)
- {
- if (!child) continue;
- var childRt = child.transform as RectTransform;
- var inRectChild = RectTransformUtility.RectangleContainsScreenPoint(childRt, sp, eventCamera);
- if (!child.ignoreSelfGraphic && interaction != inRectChild) return false;
- }
- sm = sm ? sm.parent : null;
- }
- return true;
- }
- }
- /// <summary>
- /// Set the interaction for each mask.
- /// </summary>
- public void SetMaskInteraction(MaskIntr intr)
- {
- SetMaskInteraction(intr, intr, intr, intr);
- }
- /// <summary>
- /// Set the interaction for each mask.
- /// </summary>
- public void SetMaskInteraction(MaskIntr layer0, MaskIntr layer1, MaskIntr layer2, MaskIntr layer3)
- {
- m_MaskInteraction = (int) layer0 + ((int) layer1 << 2) + ((int) layer2 << 4) + ((int) layer3 << 6);
- graphic.SetMaterialDirtyEx();
- }
- /// <summary>
- /// This function is called when the object becomes enabled and active.
- /// </summary>
- private void OnEnable()
- {
- // Register.
- if (s_ActiveSoftMaskables == null)
- {
- s_ActiveSoftMaskables = new List<SoftMaskable>();
- s_SoftMaskTexId = Shader.PropertyToID("_SoftMaskTex");
- s_StencilCompId = Shader.PropertyToID("_StencilComp");
- s_MaskInteractionId = Shader.PropertyToID("_MaskInteraction");
- #if UNITY_EDITOR
- s_GameVPId = Shader.PropertyToID("_GameVP");
- s_GameTVPId = Shader.PropertyToID("_GameTVP");
- #endif
- }
- s_ActiveSoftMaskables.Add(this);
- graphic.SetMaterialDirtyEx();
- _softMask = null;
- }
- /// <summary>
- /// This function is called when the behaviour becomes disabled.
- /// </summary>
- private void OnDisable()
- {
- s_ActiveSoftMaskables.Remove(this);
- graphic.SetMaterialDirtyEx();
- _softMask = null;
- MaterialCache.Unregister(_effectMaterialHash);
- _effectMaterialHash = k_InvalidHash;
- }
- #if UNITY_EDITOR
- private void UpdateMaterialForSceneView(Material mat)
- {
- if(!mat || !graphic || !graphic.canvas || !mat.shader || !mat.shader.name.EndsWith(" (SoftMaskable)")) return;
- // Set view and projection matrices.
- Profiler.BeginSample("Set view and projection matrices");
- var c = graphic.canvas.rootCanvas;
- var cam = c.worldCamera ?? Camera.main;
- if (c && c.renderMode != RenderMode.ScreenSpaceOverlay && cam)
- {
- var p = GL.GetGPUProjectionMatrix(cam.projectionMatrix, false);
- var pv = p * cam.worldToCameraMatrix;
- mat.SetMatrix(s_GameVPId, pv);
- mat.SetMatrix(s_GameTVPId, pv);
- }
- else
- {
- var pos = c.transform.position;
- var scale = c.transform.localScale.x;
- var size = (c.transform as RectTransform).sizeDelta;
- var gameVp = Matrix4x4.TRS(new Vector3(0, 0, 0.5f), Quaternion.identity, new Vector3(2 / size.x, 2 / size.y, 0.0005f * scale));
- var gameTvp = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(1 / pos.x, 1 / pos.y, -2 / 2000f)) * Matrix4x4.Translate(-pos);
- mat.SetMatrix(s_GameVPId, gameVp);
- mat.SetMatrix(s_GameTVPId, gameTvp);
- }
- Profiler.EndSample();
- }
- private void LateUpdate()
- {
- UpdateMaterialForSceneView(modifiedMaterial);
- }
- /// <summary>
- /// This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).
- /// </summary>
- private void OnValidate()
- {
- graphic.SetMaterialDirtyEx();
- }
- void ISerializationCallbackReceiver.OnBeforeSerialize()
- {
- }
- void ISerializationCallbackReceiver.OnAfterDeserialize()
- {
- #pragma warning disable 0612
- if (m_Inverse)
- {
- m_Inverse = false;
- m_MaskInteraction = kVisibleOutside;
- }
- #pragma warning restore 0612
- var current = this;
- UnityEditor.EditorApplication.delayCall += () =>
- {
- if (!current) return;
- if (!graphic) return;
- if (graphic.name.Contains("TMP SubMeshUI")) return;
- if (!graphic.material) return;
- if (!graphic.material.shader) return;
- if (graphic.material.shader.name != "Hidden/UI/Default (SoftMaskable)") return;
- graphic.material = null;
- graphic.SetMaterialDirtyEx();
- };
- }
- #endif
- }
- }
|