From 4e533c22bfc13fd13009df3ed46c1b4324cfa9b8 Mon Sep 17 00:00:00 2001 From: mob-sakai Date: Sat, 26 Jan 2019 21:30:41 +0900 Subject: [PATCH] close #31; Mask interaction for each layer --- Scripts/Editor/SoftMaskableEditor.cs | 134 +++++++++++++++++++++++++++ Scripts/SoftMask.cs | 89 ++++++++++-------- Scripts/SoftMaskable.cs | 103 +++++++++++++++----- SoftMask.cginc | 25 ++--- 4 files changed, 274 insertions(+), 77 deletions(-) diff --git a/Scripts/Editor/SoftMaskableEditor.cs b/Scripts/Editor/SoftMaskableEditor.cs index ad49b1d..085bf46 100644 --- a/Scripts/Editor/SoftMaskableEditor.cs +++ b/Scripts/Editor/SoftMaskableEditor.cs @@ -6,6 +6,7 @@ using System.Linq; using System; using System.Reflection; using Object = UnityEngine.Object; +using MaskIntr = UnityEngine.SpriteMaskInteraction; using System.IO; namespace Coffee.UIExtensions.Editors @@ -17,6 +18,38 @@ namespace Coffee.UIExtensions.Editors [CanEditMultipleObjects] public class SoftMaskableEditor : Editor { + //################################ + // Constant or Static Members. + //################################ + public enum MaskInteraction : int + { + VisibleInsideMask = (1 << 0) + (1 << 2) + (1 << 4) + (1 << 6), + VisibleOutsideMask = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6), + Custom = -1, + } + + MaskInteraction maskInteraction + { + get + { + int value = _spMaskInteraction.intValue; + return _custom + ? MaskInteraction.Custom + : System.Enum.IsDefined(typeof(MaskInteraction), value) + ? (MaskInteraction)value + : MaskInteraction.Custom; + } + set + { + _custom = (value == MaskInteraction.Custom); + if (!_custom) + { + _spMaskInteraction.intValue = (int)value; + } + } + } + bool _custom = false; + static readonly Type s_TypeTMPro = AppDomain.CurrentDomain.GetAssemblies ().SelectMany (x => x.GetTypes ()).FirstOrDefault (x => x.Name == "TMP_Text"); static readonly Type s_TypeTMP_SpriteAsset = AppDomain.CurrentDomain.GetAssemblies ().SelectMany (x => x.GetTypes ()).FirstOrDefault (x => x.Name == "TMP_SpriteAsset"); static readonly Type s_TypeTMProSettings = AppDomain.CurrentDomain.GetAssemblies ().SelectMany (x => x.GetTypes ()).FirstOrDefault (x => x.Name == "TMP_Settings"); @@ -36,9 +69,13 @@ namespace Coffee.UIExtensions.Editors Shader _mobileShader; Shader _spriteShader; List _materialEditors = new List (); + SerializedProperty _spMaskInteraction; private void OnEnable () { + _spMaskInteraction = serializedObject.FindProperty("m_MaskInteraction"); + _custom = (maskInteraction == MaskInteraction.Custom); + ClearMaterialEditors (); _shader = Shader.Find ("TextMeshPro/Distance Field (SoftMaskable)"); @@ -58,6 +95,8 @@ namespace Coffee.UIExtensions.Editors s_PiDefaultFontAssetPath = s_TypeTMProSettings.GetProperty ("defaultFontAssetPath", BindingFlags.Static | BindingFlags.Public); s_PiDefaultSpriteAssetPath = s_TypeTMProSettings.GetProperty ("defaultSpriteAssetPath", BindingFlags.Static | BindingFlags.Public); } + + s_MaskWarning = new GUIContent(EditorGUIUtility.FindTexture("console.warnicon.sml"), "This component is not SoftMask. Use SoftMask instead of Mask."); } private void OnDisable () @@ -65,10 +104,105 @@ namespace Coffee.UIExtensions.Editors ClearMaterialEditors (); } + List tmpMasks = new List(); + + void DrawMaskInteractions() + { + (target as SoftMaskable).GetComponentsInParent(true, tmpMasks); + tmpMasks.RemoveAll(x => !x.enabled); + tmpMasks.Reverse(); + + maskInteraction = (MaskInteraction)EditorGUILayout.EnumPopup("Mask Interaction", maskInteraction); + if (_custom) + { + var l = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 45; + + using (var ccs = new EditorGUI.ChangeCheckScope()) + { + int intr0 = DrawMaskInteraction(0); + int intr1 = DrawMaskInteraction(1); + int intr2 = DrawMaskInteraction(2); + int intr3 = DrawMaskInteraction(3); + + if (ccs.changed) { + _spMaskInteraction.intValue = (intr0 << 0) + (intr1 << 2) + (intr2 << 4) + (intr3 << 6); + } + } + + EditorGUIUtility.labelWidth = l; + } + } + + static GUIContent s_MaskWarning = new GUIContent(); + + int DrawMaskInteraction(int layer) + { + Mask mask = layer < tmpMasks.Count ? tmpMasks[layer] : null; + MaskIntr intr = (MaskIntr)((_spMaskInteraction.intValue >> layer * 2) & 0x3); + if (!mask) + { + return (int)intr; + } + + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.LabelField(mask is SoftMask ? GUIContent.none : s_MaskWarning, GUILayout.Width(16)); + GUILayout.Space(-5); + EditorGUILayout.ObjectField("Mask " + layer, mask, typeof(Mask), false); + GUILayout.Space(-15); + return (int)(MaskIntr)EditorGUILayout.EnumPopup(intr); + } + } + public override void OnInspectorGUI () { base.OnInspectorGUI (); + serializedObject.Update(); + DrawMaskInteractions(); + +// maskInteraction = (MaskInteraction)EditorGUILayout.EnumPopup("Mask Interaction", maskInteraction); + serializedObject.ApplyModifiedProperties(); + /* + EditorGUI.indentLevel++; + var l = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 60; + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.ObjectField("Mask 0", null, typeof(Mask), false); + EditorGUILayout.EnumPopup (MaskIntr.None); + } + EditorGUIUtility.labelWidth = l; + EditorGUI.indentLevel--; + + var spMaskInteraction = serializedObject.FindProperty ("m_MaskInteraction"); + MaskIntr intr0 = (MaskIntr)((spMaskInteraction.intValue >> 0) & 0x3); + MaskIntr intr1 = (MaskIntr)((spMaskInteraction.intValue >> 2) & 0x3); + MaskIntr intr2 = (MaskIntr)((spMaskInteraction.intValue >> 4) & 0x3); + MaskIntr intr3 = (MaskIntr)((spMaskInteraction.intValue >> 6) & 0x3); + + using (var ccs = new EditorGUI.ChangeCheckScope ()) { + + intr0 = (MaskIntr)EditorGUILayout.EnumPopup ("Layer 0", intr0); + intr1 = (MaskIntr)EditorGUILayout.EnumPopup ("Layer 1", intr1); + intr2 = (MaskIntr)EditorGUILayout.EnumPopup ("Layer 2", intr2); + intr3 = (MaskIntr)EditorGUILayout.EnumPopup ("Layer 3", intr3); + + if (ccs.changed) { + current.SetMaskInteractions (intr0,intr1,intr2,intr3); + } + } + */ + +// spMaskInteraction.intValue = (intr0 << 0) | (intr1 << 2) | (intr2 << 4) | (intr3 << 6); +// +// serializedObject.ApplyModifiedProperties (); + + + + +// var current = target as SoftMaskable; var current = target as SoftMaskable; current.GetComponentsInChildren (true, s_Graphics); var fixTargets = s_Graphics.Where (x => x.gameObject != current.gameObject && !x.GetComponent () && (!x.GetComponent () || x.GetComponent ().showMaskGraphic)).ToList (); diff --git a/Scripts/SoftMask.cs b/Scripts/SoftMask.cs index 3d44849..c3a16f2 100644 --- a/Scripts/SoftMask.cs +++ b/Scripts/SoftMask.cs @@ -12,7 +12,7 @@ namespace Coffee.UIExtensions /// Soft mask. /// Use instead of Mask for smooth masking. /// - public class SoftMask : Mask, IMeshModifier, ICanvasRaycastFilter + public class SoftMask : Mask, IMeshModifier { //################################ // Constant or Static Members. @@ -106,7 +106,7 @@ namespace Coffee.UIExtensions { m_IgnoreParent = value; hasChanged = true; - OnTransformParentChanged (); + OnTransformParentChanged(); } } } @@ -132,9 +132,9 @@ namespace Coffee.UIExtensions ReleaseRT(ref _softMaskBuffer); } - if(!_softMaskBuffer) + if (!_softMaskBuffer) { - _softMaskBuffer = RenderTexture.GetTemporary (w, h, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default); + _softMaskBuffer = RenderTexture.GetTemporary(w, h, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default); hasChanged = true; } @@ -150,7 +150,7 @@ namespace Coffee.UIExtensions } private set { - if(_parent) + if (_parent) { _parent.hasChanged = value; } @@ -158,6 +158,15 @@ namespace Coffee.UIExtensions } } + public SoftMask parent + { + get + { + return _parent; + } + } + + /// /// Perform material modification in this function. /// @@ -203,34 +212,23 @@ namespace Coffee.UIExtensions /// Screen position. /// Raycast camera. /// Target graphic. - public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera, Graphic g) + public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera, Graphic g, int[] interactions) { if (!isActiveAndEnabled || (g == graphic && !g.raycastTarget)) { return true; } - if (!RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera)) - { - return false; - } int x = (int)(softMaskBuffer.width * sp.x / Screen.width); int y = (int)(softMaskBuffer.height * sp.y / Screen.height); - return 0.5f < GetPixelValue(x, y); + return 0.5f < GetPixelValue(x, y, interactions); } - /// - /// Given a point and a camera is the raycast valid. - /// - /// Valid. - /// Screen position. - /// Raycast camera. public override bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) { - return IsRaycastLocationValid(sp, eventCamera, graphic); + return true; } - //################################ // Protected Members. //################################ @@ -329,12 +327,12 @@ namespace Coffee.UIExtensions hasChanged = true; } - protected override void OnRectTransformDimensionsChange () + protected override void OnRectTransformDimensionsChange() { hasChanged = true; } -#if UNITY_EDITOR + #if UNITY_EDITOR /// /// This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only). /// @@ -382,13 +380,13 @@ namespace Coffee.UIExtensions continue; var rt = sm.rectTransform; - if(rt.hasChanged) + if (rt.hasChanged) { rt.hasChanged = false; sm.hasChanged = true; } #if UNITY_EDITOR - if(!Application.isPlaying) + if (!Application.isPlaying) { sm.hasChanged = true; } @@ -403,7 +401,7 @@ namespace Coffee.UIExtensions sm._hasChanged = false; if (!sm._parent) { - sm.UpdateMaskTexture (); + sm.UpdateMaskTexture(); } } } @@ -413,13 +411,12 @@ namespace Coffee.UIExtensions /// void UpdateMaskTexture() { - if(!graphic || !graphic.canvas) + if (!graphic || !graphic.canvas) { return; } - Transform stopAfter = MaskUtilities.FindRootSortOverrideCanvas(transform); - _stencilDepth = MaskUtilities.GetStencilDepth(transform, stopAfter); + _stencilDepth = MaskUtilities.GetStencilDepth(transform, MaskUtilities.FindRootSortOverrideCanvas(transform)); // Collect children soft masks. int depth = 0; @@ -448,9 +445,9 @@ namespace Coffee.UIExtensions else { var pos = c.transform.localPosition; - var vm = Matrix4x4.TRS (-pos, Quaternion.identity, new Vector3 (1, 1, -1f)); - var pm = Matrix4x4.TRS (new Vector3 (0, 0, -1), Quaternion.identity, new Vector3 (1 / pos.x, 1 / pos.y, -2 / 1000f)); - _cb.SetViewProjectionMatrices (vm, pm); + var vm = Matrix4x4.TRS(-pos, Quaternion.identity, new Vector3(1, 1, -1f)); + var pm = Matrix4x4.TRS(new Vector3(0, 0, -1), Quaternion.identity, new Vector3(1 / pos.x, 1 / pos.y, -2 / 1000f)); + _cb.SetViewProjectionMatrices(vm, pm); } // Draw soft masks. @@ -461,6 +458,11 @@ namespace Coffee.UIExtensions { var sm = s_TmpSoftMasks[i][j]; + if (i != 0) + { + sm._stencilDepth = MaskUtilities.GetStencilDepth(sm.transform, MaskUtilities.FindRootSortOverrideCanvas(sm.transform)); + } + // Set material property. sm.material.SetInt(s_ColorMaskId, (int)1 << (3 - _stencilDepth - i)); sm._mpb.SetTexture(s_MainTexId, sm.graphic.mainTexture); @@ -562,7 +564,7 @@ namespace Coffee.UIExtensions /// /// Gets the pixel value. /// - float GetPixelValue(int x, int y) + float GetPixelValue(int x, int y, int[] interactions) { if (!s_ReadTexture) { @@ -576,18 +578,23 @@ namespace Coffee.UIExtensions RenderTexture.active = currentRT; var colors = s_ReadTexture.GetRawTextureData(); + + for (int i = 0; i < 4; i++) + { + switch (interactions[(i + 3)%4]) + { + case 0: colors[i] = 255; break; + case 2: colors[i] = (byte)(255 - colors[i]); break; + } + } + switch (_stencilDepth) { - case 0: - return (colors[1] / 255f); - case 1: - return (colors[1] / 255f) * (colors[2] / 255f); - case 2: - return (colors[1] / 255f) * (colors[2] / 255f) * (colors[3] / 255f); - case 3: - return (colors[1] / 255f) * (colors[2] / 255f) * (colors[3] / 255f) * (colors[0] / 255f); - default: - return 0; + case 0: return (colors[1] / 255f); + case 1: return (colors[1] / 255f) * (colors[2] / 255f); + case 2: return (colors[1] / 255f) * (colors[2] / 255f) * (colors[3] / 255f); + case 3: return (colors[1] / 255f) * (colors[2] / 255f) * (colors[3] / 255f) * (colors[0] / 255f); + default: return 0; } } } diff --git a/Scripts/SoftMaskable.cs b/Scripts/SoftMaskable.cs index 9021e07..bebcd67 100644 --- a/Scripts/SoftMaskable.cs +++ b/Scripts/SoftMaskable.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.UI; +using MaskIntr = UnityEngine.SpriteMaskInteraction; +using UnityEngine.Serialization; namespace Coffee.UIExtensions { @@ -11,20 +13,27 @@ namespace Coffee.UIExtensions /// Add this component to Graphic under SoftMask for smooth masking. /// [ExecuteInEditMode] - public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter + public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter, ISerializationCallbackReceiver { //################################ // Constant or Static Members. //################################ - static List s_ActiveSoftMaskables; - static Material defaultMaterial = null; + const int kVisibleInside = (1 << 0) + (1 << 2) + (1 << 4) + (1 << 6); + const int kVisibleOutside = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6); //################################ // Serialize Members. //################################ [Tooltip("The graphic will be visible only in areas where no mask is present.")] + [System.Obsolete] + [HideInInspector] [SerializeField] bool m_Inverse = false; + [Tooltip("The interaction for each masks.")] + [HideInInspector] + [SerializeField] int m_MaskInteraction = kVisibleInside; + [Tooltip("Use stencil for masking.")] + [SerializeField] bool m_UseStencil = true; //################################ @@ -62,17 +71,13 @@ namespace Coffee.UIExtensions 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); - } + result.SetInt(s_StencilCompId, m_UseStencil ? (int)CompareFunction.Equal : (int)CompareFunction.Always); + result.SetVector(s_MaskInteractionId, new Vector4( + (m_MaskInteraction & 0x3), + ((m_MaskInteraction >> 2) & 0x3), + ((m_MaskInteraction >> 4) & 0x3), + ((m_MaskInteraction >> 6) & 0x3) + )); StencilMaterial.Remove(baseMaterial); ReleaseMaterial(ref _maskMaterial); @@ -103,22 +108,34 @@ namespace Coffee.UIExtensions return true; if (!RectTransformUtility.RectangleContainsScreenPoint(transform as RectTransform, sp, eventCamera)) + { return false; + } - return _softMask.IsRaycastLocationValid(sp, eventCamera, graphic) != m_Inverse; + var sm = _softMask; + for (int 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); } + /// /// The graphic will be visible only in areas where no mask is present. /// + [System.Obsolete("Use SetMaskInteractions method instead.")] public bool inverse { - get { return m_Inverse; } + get { return m_MaskInteraction == kVisibleOutside; } set { - if (m_Inverse != value) + int intValue = value ? kVisibleOutside : kVisibleInside; + if (m_MaskInteraction != intValue) { - m_Inverse = value; + m_MaskInteraction = intValue; graphic.SetMaterialDirty(); } } @@ -129,6 +146,25 @@ namespace Coffee.UIExtensions /// public Graphic graphic{ get { return _graphic ? _graphic : _graphic = GetComponent(); } } + /// + /// Set the interaction for each mask. + /// + public void SetMaskInteraction(SpriteMaskInteraction intr) + { + SetMaskInteraction(intr, intr, intr, intr); + } + + /// + /// Set the interaction for each mask. + /// + public void SetMaskInteraction(SpriteMaskInteraction layer0, SpriteMaskInteraction layer1, SpriteMaskInteraction layer2, SpriteMaskInteraction layer3) + { + m_MaskInteraction = (int)layer0 + ((int)layer1 << 2) + ((int)layer2 << 4) + ((int)layer3 << 6); + if (graphic) + { + graphic.SetMaterialDirty(); + } + } //################################ // Private Members. @@ -138,7 +174,10 @@ namespace Coffee.UIExtensions Material _maskMaterial = null; static int s_SoftMaskTexId; static int s_StencilCompId; - static int s_SoftMaskInverseId; + static int s_MaskInteractionId; + static List s_ActiveSoftMaskables; + static int[] s_Interactions = new int[4]; + static Material s_DefaultMaterial; #if UNITY_EDITOR /// @@ -158,13 +197,13 @@ namespace Coffee.UIExtensions foreach (var sm in s_ActiveSoftMaskables) { - if(sm) + if (sm) { Material mat = sm._maskMaterial; if (mat) { - mat.SetMatrix ("_SceneView", w2c); - mat.SetMatrix ("_SceneProj", prj); + mat.SetMatrix("_SceneView", w2c); + mat.SetMatrix("_SceneProj", prj); } } } @@ -198,7 +237,7 @@ namespace Coffee.UIExtensions s_SoftMaskTexId = Shader.PropertyToID("_SoftMaskTex"); s_StencilCompId = Shader.PropertyToID("_StencilComp"); - s_SoftMaskInverseId = Shader.PropertyToID("_SoftMaskInverse"); + s_MaskInteractionId = Shader.PropertyToID("_MaskInteraction"); } s_ActiveSoftMaskables.Add(this); @@ -208,7 +247,7 @@ namespace Coffee.UIExtensions { if (!g.material || g.material == Graphic.defaultGraphicMaterial) { - g.material = defaultMaterial ?? (defaultMaterial = new Material (Resources.Load ("UI-Default-SoftMask")) { hideFlags = HideFlags.HideAndDontSave, }); + g.material = s_DefaultMaterial ?? (s_DefaultMaterial = new Material(Resources.Load("UI-Default-SoftMask")) { hideFlags = HideFlags.HideAndDontSave, }); } g.SetMaterialDirty(); } @@ -225,7 +264,7 @@ namespace Coffee.UIExtensions var g = graphic; if (g) { - if (g.material == defaultMaterial) + if (g.material == s_DefaultMaterial) { g.material = null; } @@ -258,5 +297,19 @@ namespace Coffee.UIExtensions mat = null; } } + + + void ISerializationCallbackReceiver.OnBeforeSerialize() + { + } + + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + if (m_Inverse) + { + m_Inverse = false; + m_MaskInteraction = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6); + } + } } } \ No newline at end of file diff --git a/SoftMask.cginc b/SoftMask.cginc index be63faf..c5e4e47 100644 --- a/SoftMask.cginc +++ b/SoftMask.cginc @@ -2,11 +2,10 @@ #define UI_SOFTMASK_INCLUDED sampler2D _SoftMaskTex; -fixed _SoftMaskInverse; float _Stencil; float4x4 _SceneView; float4x4 _SceneProj; - +half4 _MaskInteraction; fixed Approximately(float4x4 a, float4x4 b) { @@ -19,20 +18,25 @@ fixed Approximately(float4x4 a, float4x4 b) 0.01); } +fixed GetMaskAlpha(fixed alpha, fixed stencilId, fixed interaction) +{ + fixed onStencil = step(stencilId, _Stencil); + alpha = lerp(1, alpha, onStencil * step(1, interaction)); + return lerp(alpha, 1 - alpha, onStencil * step(2, interaction)); +} + half SoftMask(float4 clipPos) { half2 view = clipPos.xy/_ScreenParams.xy; #if UNITY_UV_STARTS_AT_TOP view.y = 1.0 - view.y; #endif - - half alpha = - lerp(1, tex2D(_SoftMaskTex, view).a, step(15, _Stencil)) - * lerp(1, tex2D(_SoftMaskTex, view).b, step(7, _Stencil)) - * lerp(1, tex2D(_SoftMaskTex, view).g, step(3, _Stencil)) - * lerp(1, tex2D(_SoftMaskTex, view).r, step(1, _Stencil)); - alpha = lerp(alpha, 1 - alpha, _SoftMaskInverse); + fixed4 mask = tex2D(_SoftMaskTex, view); + half alpha = GetMaskAlpha(mask.x, 1, _MaskInteraction.x) + * GetMaskAlpha(mask.y, 3, _MaskInteraction.y) + * GetMaskAlpha(mask.z, 7, _MaskInteraction.z) + * GetMaskAlpha(mask.w, 15, _MaskInteraction.w); #if SOFTMASK_EDITOR fixed isSceneView = max(Approximately(UNITY_MATRIX_V, _SceneView), Approximately(UNITY_MATRIX_P, _SceneProj)); @@ -42,5 +46,4 @@ half SoftMask(float4 clipPos) return alpha; } - -#endif // UI_SOFTMASK_INCLUDED +#endif // UI_SOFTMASK_INCLUDED \ No newline at end of file