close #31; Mask interaction for each layer

pull/87/head
mob-sakai 2019-01-26 21:30:41 +09:00
parent a135ea1a25
commit 4e533c22bf
4 changed files with 274 additions and 77 deletions

View File

@ -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<MaterialEditor> _materialEditors = new List<MaterialEditor> ();
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<Mask> tmpMasks = new List<Mask>();
void DrawMaskInteractions()
{
(target as SoftMaskable).GetComponentsInParent<Mask>(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<Graphic> (true, s_Graphics);
var fixTargets = s_Graphics.Where (x => x.gameObject != current.gameObject && !x.GetComponent<SoftMaskable> () && (!x.GetComponent<Mask> () || x.GetComponent<Mask> ().showMaskGraphic)).ToList ();

View File

@ -12,7 +12,7 @@ namespace Coffee.UIExtensions
/// Soft mask.
/// Use instead of Mask for smooth masking.
/// </summary>
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;
}
}
/// <summary>
/// Perform material modification in this function.
/// </summary>
@ -203,34 +212,23 @@ namespace Coffee.UIExtensions
/// <param name="sp">Screen position.</param>
/// <param name="eventCamera">Raycast camera.</param>
/// <param name="g">Target graphic.</param>
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);
}
/// <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 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
/// <summary>
/// This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).
/// </summary>
@ -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
/// </summary>
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
/// <summary>
/// Gets the pixel value.
/// </summary>
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;
}
}
}

View File

@ -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.
/// </summary>
[ExecuteInEditMode]
public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter
public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter, ISerializationCallbackReceiver
{
//################################
// Constant or Static Members.
//################################
static List<SoftMaskable> 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);
}
/// <summary>
/// The graphic will be visible only in areas where no mask is present.
/// </summary>
[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
/// </summary>
public Graphic graphic{ get { return _graphic ? _graphic : _graphic = GetComponent<Graphic>(); } }
/// <summary>
/// Set the interaction for each mask.
/// </summary>
public void SetMaskInteraction(SpriteMaskInteraction intr)
{
SetMaskInteraction(intr, intr, intr, intr);
}
/// <summary>
/// Set the interaction for each mask.
/// </summary>
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<SoftMaskable> s_ActiveSoftMaskables;
static int[] s_Interactions = new int[4];
static Material s_DefaultMaterial;
#if UNITY_EDITOR
/// <summary>
@ -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<Shader> ("UI-Default-SoftMask")) { hideFlags = HideFlags.HideAndDontSave, });
g.material = s_DefaultMaterial ?? (s_DefaultMaterial = new Material(Resources.Load<Shader>("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);
}
}
}
}

View File

@ -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