SoftMaskable.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.Profiling;
  5. using UnityEngine.Rendering;
  6. using UnityEngine.UI;
  7. using MaskIntr = UnityEngine.SpriteMaskInteraction;
  8. namespace Coffee.UISoftMask
  9. {
  10. /// <summary>
  11. /// Soft maskable.
  12. /// Add this component to Graphic under SoftMask for smooth masking.
  13. /// </summary>
  14. #if UNITY_2018_3_OR_NEWER
  15. [ExecuteAlways]
  16. #else
  17. [ExecuteInEditMode]
  18. # endif
  19. [RequireComponent(typeof(Graphic))]
  20. public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter
  21. #if UNITY_EDITOR
  22. , ISerializationCallbackReceiver
  23. # endif
  24. {
  25. private const int kVisibleInside = (1 << 0) + (1 << 2) + (1 << 4) + (1 << 6);
  26. private const int kVisibleOutside = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6);
  27. private static readonly Hash128 k_InvalidHash = new Hash128();
  28. private static int s_SoftMaskTexId;
  29. private static int s_StencilCompId;
  30. private static int s_MaskInteractionId;
  31. private static int s_GameVPId;
  32. private static int s_GameTVPId;
  33. private static List<SoftMaskable> s_ActiveSoftMaskables;
  34. private static int[] s_Interactions = new int[4];
  35. [SerializeField, HideInInspector, System.Obsolete]
  36. private bool m_Inverse;
  37. [SerializeField, Tooltip("The interaction for each masks."), HideInInspector]
  38. private int m_MaskInteraction = kVisibleInside;
  39. [SerializeField, Tooltip("Use stencil to mask.")]
  40. private bool m_UseStencil = true;
  41. [SerializeField, Tooltip("Use soft-masked raycast target.\n\nNote: This option is expensive.")]
  42. private bool m_RaycastFilter;
  43. private Graphic _graphic;
  44. public SoftMask RootMask;
  45. private SoftMask _softMask;
  46. private Hash128 _effectMaterialHash;
  47. /// <summary>
  48. /// The graphic will be visible only in areas where no mask is present.
  49. /// </summary>
  50. public bool inverse
  51. {
  52. get { return m_MaskInteraction == kVisibleOutside; }
  53. set
  54. {
  55. var intValue = value ? kVisibleOutside : kVisibleInside;
  56. if (m_MaskInteraction == intValue) return;
  57. m_MaskInteraction = intValue;
  58. graphic.SetMaterialDirtyEx();
  59. }
  60. }
  61. /// <summary>
  62. /// Use soft-masked raycast target. This option is expensive.
  63. /// </summary>
  64. public bool raycastFilter
  65. {
  66. get { return m_RaycastFilter; }
  67. set { m_RaycastFilter = value; }
  68. }
  69. /// <summary>
  70. /// Use stencil to mask.
  71. /// </summary>
  72. public bool useStencil
  73. {
  74. get { return m_UseStencil; }
  75. set
  76. {
  77. if (m_UseStencil == value) return;
  78. m_UseStencil = value;
  79. graphic.SetMaterialDirtyEx();
  80. }
  81. }
  82. /// <summary>
  83. /// The graphic associated with the soft mask.
  84. /// </summary>
  85. public Graphic graphic
  86. {
  87. get
  88. {
  89. return _graphic ? _graphic : _graphic = GetComponent<Graphic>();
  90. }
  91. }
  92. public SoftMask softMask
  93. {
  94. get
  95. {
  96. if (RootMask != null)
  97. {
  98. return RootMask;
  99. }
  100. return _softMask ? _softMask : _softMask = this.GetComponentInParentEx<SoftMask>();
  101. }
  102. }
  103. public Material modifiedMaterial { get; private set; }
  104. /// <summary>
  105. /// Perform material modification in this function.
  106. /// </summary>
  107. /// <returns>Modified material.</returns>
  108. /// <param name="baseMaterial">Configured Material.</param>
  109. Material IMaterialModifier.GetModifiedMaterial(Material baseMaterial)
  110. {
  111. _softMask = null;
  112. modifiedMaterial = null;
  113. // If this component is disabled, the material is returned as is.
  114. // If the parents do not have a soft mask component, the material is returned as is.
  115. if (!isActiveAndEnabled || !softMask)
  116. {
  117. MaterialCache.Unregister(_effectMaterialHash);
  118. _effectMaterialHash = k_InvalidHash;
  119. return baseMaterial;
  120. }
  121. // Generate soft maskable material.
  122. var previousHash = _effectMaterialHash;
  123. _effectMaterialHash = new Hash128(
  124. (uint) baseMaterial.GetInstanceID(),
  125. (uint) softMask.GetInstanceID(),
  126. (uint) m_MaskInteraction,
  127. (uint) (m_UseStencil ? 1 : 0)
  128. );
  129. // Generate soft maskable material.
  130. modifiedMaterial = MaterialCache.Register(baseMaterial, _effectMaterialHash, mat =>
  131. {
  132. mat.shader = Shader.Find(string.Format("Hidden/{0} (SoftMaskable)", mat.shader.name));
  133. mat.SetTexture(s_SoftMaskTexId, softMask.softMaskBuffer);
  134. mat.SetInt(s_StencilCompId, m_UseStencil ? (int) CompareFunction.Equal : (int) CompareFunction.Always);
  135. #if UNITY_EDITOR
  136. mat.EnableKeyword("SOFTMASK_EDITOR");
  137. UpdateMaterialForSceneView(mat);
  138. #endif
  139. var root = MaskUtilities.FindRootSortOverrideCanvas(transform);
  140. var stencil = MaskUtilities.GetStencilDepth(transform, root);
  141. mat.SetVector(s_MaskInteractionId, new Vector4(
  142. 1 <= stencil ? (m_MaskInteraction >> 0 & 0x3) : 0,
  143. 2 <= stencil ? (m_MaskInteraction >> 2 & 0x3) : 0,
  144. 3 <= stencil ? (m_MaskInteraction >> 4 & 0x3) : 0,
  145. 4 <= stencil ? (m_MaskInteraction >> 6 & 0x3) : 0
  146. ));
  147. });
  148. // Unregister the previous material.
  149. MaterialCache.Unregister(previousHash);
  150. return modifiedMaterial;
  151. }
  152. /// <summary>
  153. /// Given a point and a camera is the raycast valid.
  154. /// </summary>
  155. /// <returns>Valid.</returns>
  156. /// <param name="sp">Screen position.</param>
  157. /// <param name="eventCamera">Raycast camera.</param>
  158. bool ICanvasRaycastFilter.IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
  159. {
  160. if (!isActiveAndEnabled || !softMask)
  161. return true;
  162. if (!RectTransformUtility.RectangleContainsScreenPoint(transform as RectTransform, sp, eventCamera))
  163. return false;
  164. if (m_RaycastFilter)
  165. {
  166. var sm = _softMask;
  167. for (var i = 0; i < 4; i++)
  168. {
  169. s_Interactions[i] = sm ? ((m_MaskInteraction >> i * 2) & 0x3) : 0;
  170. sm = sm ? sm.parent : null;
  171. }
  172. return _softMask.IsRaycastLocationValid(sp, eventCamera, graphic, s_Interactions);
  173. }
  174. else
  175. {
  176. var sm = _softMask;
  177. for (var i = 0; i < 4; i++)
  178. {
  179. if (!sm) break;
  180. s_Interactions[i] = sm ? ((m_MaskInteraction >> i * 2) & 0x3) : 0;
  181. var interaction = s_Interactions[i] == 1;
  182. var rt = sm.transform as RectTransform;
  183. var inRect = RectTransformUtility.RectangleContainsScreenPoint(rt, sp, eventCamera);
  184. if (!sm.ignoreSelfGraphic && interaction != inRect) return false;
  185. foreach (var child in sm._children)
  186. {
  187. if (!child) continue;
  188. var childRt = child.transform as RectTransform;
  189. var inRectChild = RectTransformUtility.RectangleContainsScreenPoint(childRt, sp, eventCamera);
  190. if (!child.ignoreSelfGraphic && interaction != inRectChild) return false;
  191. }
  192. sm = sm ? sm.parent : null;
  193. }
  194. return true;
  195. }
  196. }
  197. /// <summary>
  198. /// Set the interaction for each mask.
  199. /// </summary>
  200. public void SetMaskInteraction(MaskIntr intr)
  201. {
  202. SetMaskInteraction(intr, intr, intr, intr);
  203. }
  204. /// <summary>
  205. /// Set the interaction for each mask.
  206. /// </summary>
  207. public void SetMaskInteraction(MaskIntr layer0, MaskIntr layer1, MaskIntr layer2, MaskIntr layer3)
  208. {
  209. m_MaskInteraction = (int) layer0 + ((int) layer1 << 2) + ((int) layer2 << 4) + ((int) layer3 << 6);
  210. graphic.SetMaterialDirtyEx();
  211. }
  212. /// <summary>
  213. /// This function is called when the object becomes enabled and active.
  214. /// </summary>
  215. private void OnEnable()
  216. {
  217. // Register.
  218. if (s_ActiveSoftMaskables == null)
  219. {
  220. s_ActiveSoftMaskables = new List<SoftMaskable>();
  221. s_SoftMaskTexId = Shader.PropertyToID("_SoftMaskTex");
  222. s_StencilCompId = Shader.PropertyToID("_StencilComp");
  223. s_MaskInteractionId = Shader.PropertyToID("_MaskInteraction");
  224. #if UNITY_EDITOR
  225. s_GameVPId = Shader.PropertyToID("_GameVP");
  226. s_GameTVPId = Shader.PropertyToID("_GameTVP");
  227. #endif
  228. }
  229. s_ActiveSoftMaskables.Add(this);
  230. graphic.SetMaterialDirtyEx();
  231. _softMask = null;
  232. }
  233. /// <summary>
  234. /// This function is called when the behaviour becomes disabled.
  235. /// </summary>
  236. private void OnDisable()
  237. {
  238. s_ActiveSoftMaskables.Remove(this);
  239. graphic.SetMaterialDirtyEx();
  240. _softMask = null;
  241. MaterialCache.Unregister(_effectMaterialHash);
  242. _effectMaterialHash = k_InvalidHash;
  243. }
  244. #if UNITY_EDITOR
  245. private void UpdateMaterialForSceneView(Material mat)
  246. {
  247. if(!mat || !graphic || !graphic.canvas || !mat.shader || !mat.shader.name.EndsWith(" (SoftMaskable)")) return;
  248. // Set view and projection matrices.
  249. Profiler.BeginSample("Set view and projection matrices");
  250. var c = graphic.canvas.rootCanvas;
  251. var cam = c.worldCamera ?? Camera.main;
  252. if (c && c.renderMode != RenderMode.ScreenSpaceOverlay && cam)
  253. {
  254. var p = GL.GetGPUProjectionMatrix(cam.projectionMatrix, false);
  255. var pv = p * cam.worldToCameraMatrix;
  256. mat.SetMatrix(s_GameVPId, pv);
  257. mat.SetMatrix(s_GameTVPId, pv);
  258. }
  259. else
  260. {
  261. var pos = c.transform.position;
  262. var scale = c.transform.localScale.x;
  263. var size = (c.transform as RectTransform).sizeDelta;
  264. var gameVp = Matrix4x4.TRS(new Vector3(0, 0, 0.5f), Quaternion.identity, new Vector3(2 / size.x, 2 / size.y, 0.0005f * scale));
  265. var gameTvp = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(1 / pos.x, 1 / pos.y, -2 / 2000f)) * Matrix4x4.Translate(-pos);
  266. mat.SetMatrix(s_GameVPId, gameVp);
  267. mat.SetMatrix(s_GameTVPId, gameTvp);
  268. }
  269. Profiler.EndSample();
  270. }
  271. private void LateUpdate()
  272. {
  273. UpdateMaterialForSceneView(modifiedMaterial);
  274. }
  275. /// <summary>
  276. /// This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).
  277. /// </summary>
  278. private void OnValidate()
  279. {
  280. graphic.SetMaterialDirtyEx();
  281. }
  282. void ISerializationCallbackReceiver.OnBeforeSerialize()
  283. {
  284. }
  285. void ISerializationCallbackReceiver.OnAfterDeserialize()
  286. {
  287. #pragma warning disable 0612
  288. if (m_Inverse)
  289. {
  290. m_Inverse = false;
  291. m_MaskInteraction = kVisibleOutside;
  292. }
  293. #pragma warning restore 0612
  294. var current = this;
  295. UnityEditor.EditorApplication.delayCall += () =>
  296. {
  297. if (!current) return;
  298. if (!graphic) return;
  299. if (graphic.name.Contains("TMP SubMeshUI")) return;
  300. if (!graphic.material) return;
  301. if (!graphic.material.shader) return;
  302. if (graphic.material.shader.name != "Hidden/UI/Default (SoftMaskable)") return;
  303. graphic.material = null;
  304. graphic.SetMaterialDirtyEx();
  305. };
  306. }
  307. #endif
  308. }
  309. }