From 38d4c0809c24ffa01929b96d1a94a15b38584fa8 Mon Sep 17 00:00:00 2001 From: mob-sakai Date: Tue, 20 Nov 2018 08:49:14 +0900 Subject: [PATCH] Implementation --- Demo/Font/FontLicense.txt | 202 +++++++++ Demo/Font/FontLicense.txt.meta | 7 + Demo/Font/LuckiestGuy.ttf | Bin 0 -> 73728 bytes Demo/Font/LuckiestGuy.ttf.meta | 20 + Resources.meta | 9 + Resources/SoftMask.shader | 30 ++ Resources/SoftMask.shader.meta | 9 + Resources/UI-Default-SoftMask.shader | 120 +++++ Resources/UI-Default-SoftMask.shader.meta | 9 + Scripts/Editor.meta | 9 + Scripts/Editor/SoftMaskEditor.cs | 78 ++++ Scripts/Editor/SoftMaskEditor.cs.meta | 12 + Scripts/SoftMask.cs | 530 ++++++++++++++++++++++ Scripts/SoftMask.cs.meta | 12 + Scripts/SoftMaskable.cs | 262 +++++++++++ Scripts/SoftMaskable.cs.meta | 12 + SoftMask.cginc | 42 ++ SoftMask.cginc.meta | 9 + 18 files changed, 1372 insertions(+) create mode 100644 Demo/Font/FontLicense.txt create mode 100644 Demo/Font/FontLicense.txt.meta create mode 100644 Demo/Font/LuckiestGuy.ttf create mode 100644 Demo/Font/LuckiestGuy.ttf.meta create mode 100644 Resources.meta create mode 100644 Resources/SoftMask.shader create mode 100644 Resources/SoftMask.shader.meta create mode 100644 Resources/UI-Default-SoftMask.shader create mode 100644 Resources/UI-Default-SoftMask.shader.meta create mode 100644 Scripts/Editor.meta create mode 100644 Scripts/Editor/SoftMaskEditor.cs create mode 100644 Scripts/Editor/SoftMaskEditor.cs.meta create mode 100644 Scripts/SoftMask.cs create mode 100644 Scripts/SoftMask.cs.meta create mode 100644 Scripts/SoftMaskable.cs create mode 100644 Scripts/SoftMaskable.cs.meta create mode 100644 SoftMask.cginc create mode 100644 SoftMask.cginc.meta 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 0000000000000000000000000000000000000000..01b535af2029558af7408f36da67b3602151f8d6 GIT binary patch literal 73728 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

v=CYZB|(d!B~UVQN}oOi>l(nA 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 S7Rltw|KI)|o%(;;{{I_`@C&#A literal 0 HcmV?d00001 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: