diff --git a/Demo/Font/FontLicense.txt b/Demo/Font/FontLicense.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/Demo/Font/FontLicense.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Demo/Font/FontLicense.txt.meta b/Demo/Font/FontLicense.txt.meta new file mode 100644 index 0000000..226a43c --- /dev/null +++ b/Demo/Font/FontLicense.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cb89e33902259704db92150f0a4ef290 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Demo/Font/LuckiestGuy.ttf b/Demo/Font/LuckiestGuy.ttf new file mode 100644 index 0000000..01b535a Binary files /dev/null and b/Demo/Font/LuckiestGuy.ttf differ diff --git a/Demo/Font/LuckiestGuy.ttf.meta b/Demo/Font/LuckiestGuy.ttf.meta new file mode 100644 index 0000000..6050d1b --- /dev/null +++ b/Demo/Font/LuckiestGuy.ttf.meta @@ -0,0 +1,20 @@ +fileFormatVersion: 2 +guid: ab2cfde409d710b47b0b502877abb479 +timeCreated: 1473937939 +licenseType: Pro +TrueTypeFontImporter: + serializedVersion: 4 + fontSize: 72 + forceTextureCase: -1 + characterSpacing: 0 + characterPadding: 1 + includeFontData: 1 + fontNames: + - Luckiest Guy + fallbackFontReferences: [] + customCharacters: + fontRenderingMode: 0 + ascentCalculationMode: 2 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources.meta b/Resources.meta new file mode 100644 index 0000000..d6d7456 --- /dev/null +++ b/Resources.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 65287cc8c961442bdb8f0b1fb876c058 +folderAsset: yes +timeCreated: 1540097485 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/SoftMask.shader b/Resources/SoftMask.shader new file mode 100644 index 0000000..305e720 --- /dev/null +++ b/Resources/SoftMask.shader @@ -0,0 +1,30 @@ +Shader "Hidden/SoftMask" { + +SubShader { + Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} + LOD 100 + + ZWrite Off + Blend SrcAlpha OneMinusSrcAlpha + ColorMask [_ColorMask] + + Pass { + CGPROGRAM + #pragma vertex vert_img + #pragma fragment frag + #pragma target 2.0 + + #include "UnityCG.cginc" + + sampler2D _MainTex; + float _Softness; + + fixed4 frag (v2f_img i) : SV_Target + { + return saturate(tex2D(_MainTex, i.uv).a/_Softness); + } + ENDCG + } +} + +} diff --git a/Resources/SoftMask.shader.meta b/Resources/SoftMask.shader.meta new file mode 100644 index 0000000..aa25903 --- /dev/null +++ b/Resources/SoftMask.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 2933b413a51fc4ff3a83c7ef4177ae84 +timeCreated: 1539779942 +licenseType: Pro +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Resources/UI-Default-SoftMask.shader b/Resources/UI-Default-SoftMask.shader new file mode 100644 index 0000000..cb08b37 --- /dev/null +++ b/Resources/UI-Default-SoftMask.shader @@ -0,0 +1,120 @@ +Shader "UI/Default-SoftMask" +{ + Properties + { + [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} + _Color ("Tint", Color) = (1,1,1,1) + + _StencilComp ("Stencil Comparison", Float) = 8 + _Stencil ("Stencil ID", Float) = 0 + _StencilOp ("Stencil Operation", Float) = 0 + _StencilWriteMask ("Stencil Write Mask", Float) = 255 + _StencilReadMask ("Stencil Read Mask", Float) = 255 + + _ColorMask ("Color Mask", Float) = 15 + + [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 + } + + SubShader + { + Tags + { + "Queue"="Transparent" + "IgnoreProjector"="True" + "RenderType"="Transparent" + "PreviewType"="Plane" + "CanUseSpriteAtlas"="True" + } + + Stencil + { + Ref [_Stencil] + Comp [_StencilComp] + Pass [_StencilOp] + ReadMask [_StencilReadMask] + WriteMask [_StencilWriteMask] + } + + Cull Off + Lighting Off + ZWrite Off + ZTest [unity_GUIZTestMode] + Blend SrcAlpha OneMinusSrcAlpha + ColorMask [_ColorMask] + + Pass + { + Name "Default" + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma target 2.0 + + #include "UnityCG.cginc" + #include "UnityUI.cginc" + + #pragma multi_compile __ UNITY_UI_CLIP_RECT + #pragma multi_compile __ UNITY_UI_ALPHACLIP + + #include "Assets/Coffee/UIExtensions/SoftMaskForUGUI/SoftMask.cginc" // Add for soft mask + #pragma shader_feature __ SOFTMASK_EDITOR // Add for soft mask + + struct appdata_t + { + float4 vertex : POSITION; + float4 color : COLOR; + float2 texcoord : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + struct v2f + { + float4 vertex : SV_POSITION; + fixed4 color : COLOR; + float2 texcoord : TEXCOORD0; + float4 worldPosition : TEXCOORD1; + UNITY_VERTEX_OUTPUT_STEREO + }; + + sampler2D _MainTex; + fixed4 _Color; + fixed4 _TextureSampleAdd; + float4 _ClipRect; + float4 _MainTex_ST; + + v2f vert(appdata_t v) + { + v2f OUT; + UNITY_SETUP_INSTANCE_ID(v); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); + OUT.worldPosition = v.vertex; + OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); + + OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); + + OUT.color = v.color * _Color; + + return OUT; + } + + fixed4 frag(v2f IN) : SV_Target + { + half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; + + #ifdef UNITY_UI_CLIP_RECT + color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); + #endif + + #ifdef UNITY_UI_ALPHACLIP + clip (color.a - 0.001); + #endif + + color.a *= SoftMask(IN.vertex); // Add for soft mask + + return color; + } + ENDCG + } + } +} diff --git a/Resources/UI-Default-SoftMask.shader.meta b/Resources/UI-Default-SoftMask.shader.meta new file mode 100644 index 0000000..6326867 --- /dev/null +++ b/Resources/UI-Default-SoftMask.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9839189d918374a318d397a86e90aa73 +timeCreated: 1539847292 +licenseType: Pro +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor.meta b/Scripts/Editor.meta new file mode 100644 index 0000000..b231f99 --- /dev/null +++ b/Scripts/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: fd8b4f97015bf4bb3936f3cf874c89a3 +folderAsset: yes +timeCreated: 1539820783 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/SoftMaskEditor.cs b/Scripts/Editor/SoftMaskEditor.cs new file mode 100644 index 0000000..3b1eb24 --- /dev/null +++ b/Scripts/Editor/SoftMaskEditor.cs @@ -0,0 +1,78 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; +using UnityEditor; + + +namespace Coffee.UIExtensions.Editors +{ + /// + /// SoftMask editor. + /// + [CustomEditor(typeof(SoftMask))] + [CanEditMultipleObjects] + public class SoftMaskEditor : Editor + { + //%%%% Context menu for editor %%%% + [MenuItem("CONTEXT/Mask/Convert To SoftMask", true)] + static bool _ConvertToSoftMask(MenuCommand command) + { + return CanConvertTo(command.context); + } + + [MenuItem("CONTEXT/Mask/Convert To SoftMask", false)] + static void ConvertToSoftMask(MenuCommand command) + { + ConvertTo(command.context); + } + + [MenuItem("CONTEXT/Mask/Convert To Mask", true)] + static bool _ConvertToMask(MenuCommand command) + { + return CanConvertTo(command.context); + } + + [MenuItem("CONTEXT/Mask/Convert To Mask", false)] + static void ConvertToMask(MenuCommand command) + { + ConvertTo(command.context); + } + + /// + /// Verify whether it can be converted to the specified component. + /// + protected static bool CanConvertTo(Object context) + where T : MonoBehaviour + { + return context && context.GetType() != typeof(T); + } + + /// + /// Convert to the specified component. + /// + protected static void ConvertTo(Object context) where T : MonoBehaviour + { + var target = context as MonoBehaviour; + var so = new SerializedObject(target); + so.Update(); + + bool oldEnable = target.enabled; + target.enabled = false; + + // Find MonoScript of the specified component. + foreach (var script in Resources.FindObjectsOfTypeAll()) + { + if (script.GetClass() != typeof(T)) + continue; + + // Set 'm_Script' to convert. + so.FindProperty("m_Script").objectReferenceValue = script; + so.ApplyModifiedProperties(); + break; + } + + (so.targetObject as MonoBehaviour).enabled = oldEnable; + } + } +} \ No newline at end of file diff --git a/Scripts/Editor/SoftMaskEditor.cs.meta b/Scripts/Editor/SoftMaskEditor.cs.meta new file mode 100644 index 0000000..f12c4ec --- /dev/null +++ b/Scripts/Editor/SoftMaskEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c2615ef08e99d4d898049fb9da8626c6 +timeCreated: 1539820794 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/SoftMask.cs b/Scripts/SoftMask.cs new file mode 100644 index 0000000..5c505d2 --- /dev/null +++ b/Scripts/SoftMask.cs @@ -0,0 +1,530 @@ +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.UI; + +namespace Coffee.UIExtensions +{ + /// + /// Soft mask. + /// Use instead of Mask for smooth masking. + /// + public class SoftMask : Mask, IMeshModifier, ICanvasRaycastFilter + { + //################################ + // Constant or Static Members. + //################################ + /// + /// Desampling rate. + /// + public enum DesamplingRate + { + None = 0, + x1 = 1, + x2 = 2, + x4 = 4, + x8 = 8, + } + + static readonly List[] s_TmpSoftMasks = new List[] + { + new List(), + new List(), + new List(), + new List(), + }; + + static readonly Color[] s_ClearColors = new Color[] + { + new Color(0, 0, 0, 0), + new Color(1, 0, 0, 0), + new Color(1, 1, 0, 0), + new Color(1, 1, 1, 0), + }; + + + //################################ + // Serialize Members. + //################################ + [Tooltip("The desampling rate for soft mask buffer.")] + [SerializeField] DesamplingRate m_DesamplingRate = DesamplingRate.None; + [Tooltip("The value used by the soft mask to select the area of influence defined over the soft mask's graphic.")] + [SerializeField][Range(0.01f, 1)] float m_Softness = 1; + [Tooltip("Should the soft mask ignore parent soft masks?")] + [SerializeField] bool m_IgnoreParent = false; + + + //################################ + // Public Members. + //################################ + /// + /// The desampling rate for soft mask buffer. + /// + public DesamplingRate desamplingRate + { + get { return m_DesamplingRate; } + set + { + if (m_DesamplingRate != value) + { + m_DesamplingRate = value; + } + } + } + + /// + /// The value used by the soft mask to select the area of influence defined over the soft mask's graphic. + /// + public float softness + { + get { return m_Softness; } + set + { + value = Mathf.Clamp01(value); + if (m_Softness != value) + { + m_Softness = value; + } + } + } + + /// + /// Should the soft mask ignore parent soft masks? + /// + /// If set to true the soft mask will ignore any parent soft mask settings. + public bool ignoreParent + { + get { return m_IgnoreParent; } + set + { + if (m_IgnoreParent != value) + { + m_IgnoreParent = value; + OnTransformParentChanged(); + } + } + } + + /// + /// The soft mask buffer. + /// + public RenderTexture softMaskBuffer + { + get + { + if (_parent) + { + ReleaseRT(ref _softMaskBuffer); + return _parent.softMaskBuffer; + } + + // Check the size of soft mask buffer. + int w, h; + GetDesamplingSize(m_DesamplingRate, out w, out h); + if (_softMaskBuffer && (_softMaskBuffer.width != w || _softMaskBuffer.height != h)) + { + ReleaseRT(ref _softMaskBuffer); + } + + return _softMaskBuffer ? _softMaskBuffer : _softMaskBuffer = RenderTexture.GetTemporary(w, h, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default); + } + } + + /// + /// Perform material modification in this function. + /// + /// Modified material. + /// Configured Material. + public override Material GetModifiedMaterial(Material baseMaterial) + { + var result = base.GetModifiedMaterial(baseMaterial); + if (m_IgnoreParent && result != baseMaterial) + { + result.SetInt(s_StencilCompId, (int)CompareFunction.Always); + } + return result; + } + + + /// + /// Call used to modify mesh. + /// + void IMeshModifier.ModifyMesh(Mesh mesh) + { + _mesh = mesh; + } + + /// + /// Call used to modify mesh. + /// + void IMeshModifier.ModifyMesh(VertexHelper verts) + { + if (isActiveAndEnabled) + { + verts.FillMesh(mesh); + } + } + + /// + /// Given a point and a camera is the raycast valid. + /// + /// Valid. + /// Screen position. + /// Raycast camera. + /// Target graphic. + public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera, Graphic g) + { + 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); + } + + /// + /// 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); + } + + + //################################ + // Protected Members. + //################################ + + /// + /// This function is called when the object becomes enabled and active. + /// + protected override void OnEnable() + { + // Register. + if (s_ActiveSoftMasks.Count == 0) + { + Canvas.willRenderCanvases += UpdateMaskTextures; + + if (s_StencilCompId == 0) + { + s_StencilCompId = Shader.PropertyToID("_StencilComp"); + s_ColorMaskId = Shader.PropertyToID("_ColorMask"); + s_MainTexId = Shader.PropertyToID("_MainTex"); + s_SoftnessId = Shader.PropertyToID("_Softness"); + } + } + s_ActiveSoftMasks.Add(this); + + // Reset the parent-child relation. + GetComponentsInChildren(false, s_TempRelatables); + for (int i = s_TempRelatables.Count - 1; 0 <= i; i--) + { + s_TempRelatables[i].OnTransformParentChanged(); + } + s_TempRelatables.Clear(); + + // Create objects. + _mpb = new MaterialPropertyBlock(); + _cb = new CommandBuffer(); + + graphic.SetVerticesDirty(); + + base.OnEnable(); + } + + /// + /// This function is called when the behaviour becomes disabled. + /// + protected override void OnDisable() + { + // Unregister. + s_ActiveSoftMasks.Remove(this); + if (s_ActiveSoftMasks.Count == 0) + { + Canvas.willRenderCanvases -= UpdateMaskTextures; + } + + // Reset the parent-child relation. + for (int i = _children.Count - 1; 0 <= i; i--) + { + _children[i].SetParent(_parent); + } + _children.Clear(); + SetParent(null); + + // Destroy objects. + _mpb.Clear(); + _mpb = null; + _cb.Release(); + _cb = null; + + ReleaseObject(_mesh); + _mesh = null; + ReleaseObject(_material); + _material = null; + ReleaseRT(ref _softMaskBuffer); + + base.OnDisable(); + } + + /// + /// This function is called when the parent property of the transform of the GameObject has changed. + /// + protected override void OnTransformParentChanged() + { + SoftMask newParent = null; + if (isActiveAndEnabled && !m_IgnoreParent) + { + var parentTransform = transform.parent; + while (parentTransform && (!newParent || !newParent.enabled)) + { + newParent = parentTransform.GetComponent(); + parentTransform = parentTransform.parent; + } + } + SetParent(newParent); + } + + #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). + /// + protected override void OnValidate() + { + graphic.SetMaterialDirty(); + OnTransformParentChanged(); + base.OnValidate(); + } + #endif + + //################################ + // Private Members. + //################################ + static Shader s_SoftMaskShader; + static Texture2D s_ReadTexture; + static List s_ActiveSoftMasks = new List(); + static List s_TempRelatables = new List(); + static int s_StencilCompId; + static int s_ColorMaskId; + static int s_MainTexId; + static int s_SoftnessId; + MaterialPropertyBlock _mpb; + CommandBuffer _cb; + Material _material; + RenderTexture _softMaskBuffer; + int _stencilDepth; + Mesh _mesh; + SoftMask _parent; + List _children = new List(); + + Material material { get { return _material ? _material : _material = new Material(s_SoftMaskShader ? s_SoftMaskShader : s_SoftMaskShader = Resources.Load("SoftMask")){ hideFlags = HideFlags.HideAndDontSave }; } } + + Mesh mesh { get { return _mesh ? _mesh : _mesh = new Mesh(){ hideFlags = HideFlags.HideAndDontSave }; } } + + /// + /// Update all soft mask textures. + /// + static void UpdateMaskTextures() + { + foreach (var sm in s_ActiveSoftMasks) + { + sm.UpdateMaskTexture(); + } + } + + /// + /// Update the mask texture. + /// + void UpdateMaskTexture() + { + if (_parent) + return; + + Transform stopAfter = MaskUtilities.FindRootSortOverrideCanvas(transform); + _stencilDepth = MaskUtilities.GetStencilDepth(transform, stopAfter); + + // Collect children soft masks. + int depth = 0; + s_TmpSoftMasks[0].Add(this); + while (_stencilDepth + depth < 3) + { + int count = s_TmpSoftMasks[depth].Count; + for (int i = 0; i < count; i++) + { + s_TmpSoftMasks[depth + 1].AddRange(s_TmpSoftMasks[depth][i]._children); + } + depth++; + } + + // Clear. + _cb.Clear(); + _cb.SetRenderTarget(softMaskBuffer); + _cb.ClearRenderTarget(false, true, s_ClearColors[_stencilDepth]); + + // Set view and projection matrices. + var c = graphic.canvas; + if (c && c.renderMode != RenderMode.ScreenSpaceOverlay && c.worldCamera) + { + _cb.SetViewProjectionMatrices(c.worldCamera.worldToCameraMatrix, c.worldCamera.projectionMatrix); + } + else + { + _cb.SetViewMatrix(Matrix4x4.TRS(new Vector3(-1, -1, 0), Quaternion.identity, new Vector3(2f / Screen.width, 2f / Screen.height, 1f))); + } + + // Draw soft masks. + for (int i = 0; i < s_TmpSoftMasks.Length; i++) + { + int count = s_TmpSoftMasks[i].Count; + for (int j = 0; j < count; j++) + { + var sm = s_TmpSoftMasks[i][j]; + + // Set material property. + sm.material.SetInt(s_ColorMaskId, (int)1 << (3 - _stencilDepth - i)); + sm._mpb.SetTexture(s_MainTexId, sm.graphic.mainTexture); + sm._mpb.SetFloat(s_SoftnessId, sm.m_Softness); + + // Draw mesh. + _cb.DrawMesh(sm.mesh, sm.transform.localToWorldMatrix, sm.material, 0, 0, sm._mpb); + } + s_TmpSoftMasks[i].Clear(); + } + + Graphics.ExecuteCommandBuffer(_cb); + } + + /// + /// Gets the size of the desampling. + /// + void GetDesamplingSize(DesamplingRate rate, out int w, out int h) + { + #if UNITY_EDITOR + if (!Application.isPlaying) + { + var res = UnityEditor.UnityStats.screenRes.Split('x'); + w = int.Parse(res[0]); + h = int.Parse(res[1]); + } + else + #endif + { + w = Screen.width; + h = Screen.height; + } + + if (rate == DesamplingRate.None) + return; + + float aspect = (float)w / h; + if (w < h) + { + h = Mathf.ClosestPowerOfTwo(h / (int)rate); + w = Mathf.CeilToInt(h * aspect); + } + else + { + w = Mathf.ClosestPowerOfTwo(w / (int)rate); + h = Mathf.CeilToInt(w / aspect); + } + } + + /// + /// Release the specified obj. + /// + /// Object. + void ReleaseRT(ref RenderTexture tmpRT) + { + if (tmpRT) + { + RenderTexture.ReleaseTemporary(tmpRT); + tmpRT = null; + } + } + + /// + /// Release the specified obj. + /// + /// Object. + void ReleaseObject(Object obj) + { + if (obj) + { + #if UNITY_EDITOR + if (!Application.isPlaying) + DestroyImmediate(obj); + else + #endif + Destroy(obj); + obj = null; + } + } + + + /// + /// Set the parent of the soft mask. + /// + /// The parent soft mask to use. + void SetParent(SoftMask newParent) + { + if (_parent != newParent && this != newParent) + { + if (_parent && _parent._children.Contains(this)) + { + _parent._children.Remove(this); + _parent._children.RemoveAll(x => x == null); + } + _parent = newParent; + } + + if (_parent && !_parent._children.Contains(this)) + { + _parent._children.Add(this); + } + } + + /// + /// Gets the pixel value. + /// + float GetPixelValue(int x, int y) + { + if (!s_ReadTexture) + { + s_ReadTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); + } + var currentRT = RenderTexture.active; + + RenderTexture.active = softMaskBuffer; + s_ReadTexture.ReadPixels(new Rect(x, y, 1, 1), 0, 0); + s_ReadTexture.Apply(false, false); + RenderTexture.active = currentRT; + + var colors = s_ReadTexture.GetRawTextureData(); + 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; + } + } + } +} \ No newline at end of file diff --git a/Scripts/SoftMask.cs.meta b/Scripts/SoftMask.cs.meta new file mode 100644 index 0000000..da2479e --- /dev/null +++ b/Scripts/SoftMask.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 385b7d1277b6c4007a84c065696e0f8c +timeCreated: 1539755712 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/SoftMaskable.cs b/Scripts/SoftMaskable.cs new file mode 100644 index 0000000..cbbc74d --- /dev/null +++ b/Scripts/SoftMaskable.cs @@ -0,0 +1,262 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.UI; + +namespace Coffee.UIExtensions +{ + /// + /// Soft maskable. + /// Add this component to Graphic under SoftMask for smooth masking. + /// + [ExecuteInEditMode] + public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter + { + //################################ + // Constant or Static Members. + //################################ + static List 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. + //################################ + /// + /// Perform material modification in this function. + /// + /// Modified material. + /// Configured Material. + 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(); + 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; + + Debug.LogFormat("GetModifiedMaterial {0}",this); + + #if UNITY_EDITOR + result.EnableKeyword("SOFTMASK_EDITOR"); + UpdateSceneViewMatrixForShader(); + #endif + } + else + { + baseMaterial.SetTexture(s_SoftMaskTexId, Texture2D.whiteTexture); + } + + return result; + } + + /// + /// Given a point and a camera is the raycast valid. + /// + /// Valid. + /// Screen position. + /// Raycast camera. + 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; + } + + /// + /// The graphic will be visible only in areas where no mask is present. + /// + public bool inverse + { + get { return m_Inverse; } + set + { + if (m_Inverse != value) + { + m_Inverse = value; + graphic.SetMaterialDirty(); + } + } + } + + /// + /// The graphic associated with the soft mask. + /// + public Graphic graphic{ get { return _graphic ? _graphic : _graphic = GetComponent(); } } + + + //################################ + // 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 + /// + /// Update the scene view matrix for shader. + /// + static void UpdateSceneViewMatrixForShader() + { + UnityEditor.SceneView sceneView = UnityEditor.SceneView.lastActiveSceneView; + if (!sceneView || !sceneView.camera) + { + Debug.LogWarning("hoge!"); + return; + } + + Camera cam = sceneView.camera; + Matrix4x4 w2c = cam.worldToCameraMatrix; + Matrix4x4 prj = cam.projectionMatrix; + + foreach (var sm in s_ActiveSoftMaskables) + { + Material mat = sm._maskMaterial; + if (mat) + { + mat.SetMatrix("_SceneView", w2c); + mat.SetMatrix("_SceneProj", prj); + } + Debug.Log(sm + ", "+ mat, sm); + } + } + + /// + /// This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only). + /// + void OnValidate() + { + if (graphic) + { + graphic.SetMaterialDirty(); + } + } + #endif + + /// + /// This function is called when the object becomes enabled and active. + /// + void OnEnable() + { + // Register. + if (s_ActiveSoftMaskables == null) + { + s_ActiveSoftMaskables = new List(); + + UnityEditor.EditorApplication.update += UpdateSceneViewMatrixForShader; +// Canvas.willRenderCanvases += UpdateSceneViewMatrixForShader; + + 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("UI-Default-SoftMask"))); + } + g.SetMaterialDirty(); + } + _softMask = null; + } + + /// + /// This function is called when the behaviour becomes disabled. + /// + 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; + } + + /// + /// Release the material. + /// + void ReleaseMaterial(ref Material mat) + { + if (mat) + { + StencilMaterial.Remove(mat); + + #if UNITY_EDITOR + if (!Application.isPlaying) + { + DestroyImmediate(mat); + } + else + #endif + { + Destroy(mat); + } + mat = null; + } + } + } +} \ No newline at end of file diff --git a/Scripts/SoftMaskable.cs.meta b/Scripts/SoftMaskable.cs.meta new file mode 100644 index 0000000..f9a532b --- /dev/null +++ b/Scripts/SoftMaskable.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 97bc2ebab6563400c95b036136d26ea6 +timeCreated: 1539851864 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SoftMask.cginc b/SoftMask.cginc new file mode 100644 index 0000000..13dfabd --- /dev/null +++ b/SoftMask.cginc @@ -0,0 +1,42 @@ +#ifndef UI_SOFTMASK_INCLUDED +#define UI_SOFTMASK_INCLUDED + +sampler2D _SoftMaskTex; +fixed _SoftMaskInverse; +float _Stencil; +float4x4 _SceneView; +float4x4 _SceneProj; + + +fixed Approximately(float4x4 a, float4x4 b) +{ + float4x4 d = abs(a - b); + return step( + max(d._m00,max(d._m01,max(d._m02,max(d._m03, + max(d._m10,max(d._m11,max(d._m12,max(d._m13, + max(d._m20,max(d._m21,max(d._m22,max(d._m23, + max(d._m30,max(d._m31,max(d._m32,d._m33))))))))))))))), + 0.01); +} + +half SoftMask(float4 clipPos) +{ + half2 view = clipPos.xy/_ScreenParams.xy; + 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); + + #if SOFTMASK_EDITOR + fixed isSceneView = max(Approximately(UNITY_MATRIX_V, _SceneView), Approximately(UNITY_MATRIX_P, _SceneProj)); + alpha = lerp(alpha, 1, isSceneView); + #endif + + return alpha; +} + + +#endif // UI_SOFTMASK_INCLUDED diff --git a/SoftMask.cginc.meta b/SoftMask.cginc.meta new file mode 100644 index 0000000..fb793f3 --- /dev/null +++ b/SoftMask.cginc.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c0f7e0d8262ac42cc9ec30a5aea12d72 +timeCreated: 1539995458 +licenseType: Pro +ShaderImporter: + defaultTextures: [] + userData: + assetBundleName: + assetBundleVariant: