前两个项目我一直用的是 UGUI研究院之Mask裁切UI粒子特效或者3D模型(十七) 做的裁切,但是实际开发中由循环列表中需要动态创建元素,每个新创建的元素都需要根据MaskRect的区域重新给Shader中指定,这个小小的隐患总是引起BUG。最近在做技术储备看到了Stencil一个东西,发现可以很好的代替原来裁切的方法,如下图所示,一共有2个裁切区域,同时裁切 UI 模型 粒子特效。
首先无论怎么做,我觉得需要一个脚本,只需要将模型 UI 特效挂在下面就可以全部裁切,运行期间动态添加不需要调用刷新方法。如下图所示,添加一个新的脚本RectMask3D来代替UGUI自带的RectMask2D
RectMask3D.cs 代码中的m_ID是为了区分裁切区域,大部分情况下裁切区域都是一致的,当然也不排除有重叠的情况,就像最上面的图一样。
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | using UnityEngine; using UnityEngine.UI; public class RectMask3D : RectMask2D { GameObject m_Mask3D = null; [SerializeField] private Material m_Material; [SerializeField] private int m_ID = 1;
public int id { get { return m_ID; } set { if (value != m_ID) { m_ID = value; Refresh(); } } }
#if UNITY_EDITOR protected void OnValidate() { if (Application.isPlaying) { Refresh(); } } #endif protected override void Awake() { base.Awake(); if (Application.isPlaying) { Refresh(); } }
private void Refresh() { if (m_Mask3D == null) { m_Mask3D = GameObject.CreatePrimitive(PrimitiveType.Quad); m_Mask3D.layer = LayerMask.NameToLayer("UI"); m_Mask3D.name = "Mask3D"; m_Mask3D.hideFlags = HideFlags.NotEditable; m_Mask3D.GetComponent<MeshRenderer>().material = m_Material; m_Mask3D.transform.SetParent(transform); } m_Mask3D.transform.localPosition = Vector3.zero; m_Mask3D.transform.localScale = this.rectTransform.sizeDelta;
var material = m_Mask3D.GetComponent<Renderer>().material; material.SetInt("_ID", m_ID); }
} #if UNITY_EDITOR [UnityEditor.CustomEditor(typeof(RectMask3D))] class SuperMask2DInspector : UnityEditor.Editor {
public override void OnInspectorGUI() { base.OnInspectorGUI(); } } #endif |
m_Material 是裁切3D所需要的,Shader中需要Stencil,为了避免每次添加一个新的裁切区域手动绑定,可以提前挂在Inspector面板上。
Mask.shader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | Shader "Unlit/Mask" { Properties { [HideInInspector] _ID ("_ID",int) = 1 } SubShader { Pass { Tags{ "RenderType" = "Opaque" "Queue" = "Geometry-1" } ColorMask 0 ZWrite off ZTest off Stencil { Ref [_ID] Comp always Pass replace //替换相同ID模板像素 } CGINCLUDE struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; return o; } half4 frag(v2f i) : SV_Target{ return 0; } ENDCG } } } |
接着就是Mask裁切的元素了,由于脚本继承了RectMask2D所以自带就可以裁切UI,需要处理的就是特效和模型,这里我用特效来举例,其他的做法都类似。给参与裁切的特效或者模型绑定RectImage3D脚本,(也可以不绑定,主要就是ID的值需要和RectMask3D里填的一致)。Type枚举可以设置永远显示(Always)或者被裁切(Equal)
RectItem3D.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | using UnityEngine; public class RectItem3D : MonoBehaviour { [SerializeField] private int m_ID = 1; [SerializeField] private MaskType m_Type = MaskType.Always;
public MaskType type { get { return m_Type; } set { if(value != m_Type) { m_Type = value; Refresh(); } } }
#if UNITY_EDITOR protected void OnValidate() { if (Application.isPlaying) { Refresh(); } } #endif
private void Awake() { Refresh(); }
void Refresh() { foreach (var render in GetComponentsInChildren<Renderer>(true)) { var material = render.material;
material.SetInt("_ID", m_ID); material.SetInt("_StencilComp", (int)m_Type); } }
public enum MaskType : byte { Always = 8, Equal = 3 }
} |
同样还需要给粒子或者模型绑定新的Shader,这里我就举个简单的粒子,将//—-add—-中间的代码添加到需要裁切的模型或者粒子的Shader中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | Shader "Unlit/Cube" { Properties { _MainTex ("Texture", 2D) = "white" {} //----add---- [HideInInspector] _ID ("_ID",int) = 1 [HideInInspector] _StencilComp ("_StencilComp",Float) = 8 //----add---- } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry"} Pass { //----add---- Stencil { Ref [_ID] Comp [_StencilComp] Pass keep } //----add----
CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc"
struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0;
};
struct v2f { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; };
sampler2D _MainTex; float4 _MainTex_ST;
v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; return o; } fixed4 frag (v2f IN) : SV_Target { return tex2D(_MainTex, IN.texcoord); } ENDCG } } } |
这样需要裁切的粒子或者模型就可以保证只使用同一个Shader,运行时代码灵活的控制。
最后说说我的总结:
1.我还是建议模型使用RT,因为UI界面需要叠层挡住模型的现象,关于AlphaBlend或者显示不清楚的现象,可以参考我之前的文章 UGUI研究院之在UI上使用RenderTexture显示模型+AlphaBlend特效(二十五)
2.粒子特效适合使用这篇文章的方法,其实UI的粒子特效需要裁切的 无非就是 滑动列表中的图标上的转圈特效,配合SortOrder可以很好解决层级的问题。
3.RectTransform的Scale是(1,1,1) 但是模板需要添加scale的区域,我不知道怎么可以和RectTransform公用一个,所以我不得不创建一个和RectTransform区域相同的m_Mask3D,如果有知道的朋友欢迎告诉我。