前两个项目我一直用的是 UGUI研究院之Mask裁切UI粒子特效或者3D模型(十七) 做的裁切,但是实际开发中由循环列表中需要动态创建元素,每个新创建的元素都需要根据MaskRect的区域重新给Shader中指定,这个小小的隐患总是引起BUG。最近在做技术储备看到了Stencil一个东西,发现可以很好的代替原来裁切的方法,如下图所示,一共有2个裁切区域,同时裁切 UI 模型 粒子特效。
首先无论怎么做,我觉得需要一个脚本,只需要将模型 UI 特效挂在下面就可以全部裁切,运行期间动态添加不需要调用刷新方法。如下图所示,添加一个新的脚本RectMask3D来代替UGUI自带的RectMask2D
RectMask3D.cs 代码中的m_ID是为了区分裁切区域,大部分情况下裁切区域都是一致的,当然也不排除有重叠的情况,就像最上面的图一样。
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 70 71 |
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面板上。
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 |
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)
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 |
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 64 65 |
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,如果有知道的朋友欢迎告诉我。
- 本文固定链接: https://www.xuanyusong.com/archives/4562
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
雨松大大, 我用了这个裁切 ,但是shader 在安卓和ios 手机上提示不支持GPU, Mobile/Particles/AdditiveMask shader is not supported on this GPU (none of subshaders/fallbacks are suitable)
m_Mask3D.transform.SetParent(transform); 报错Setting the parent of a transform which resides in a Prefab Asset is disabled to prevent data corruption (GameObject: ‘Mask3D’).
你好,我也遇到了同样的问题,请问你解决了么
工程能否发出来参考一下?
这样用应该更简单而且有效,你觉得呢?
Shader.SetGlobalVector(“_MaskClip”, new Vector4(200,200,200));
请问大大,我在UGUI的滑动列表中的Viewport那里使用了这个方案,把UGUI的Mask组件删除替换成了RectMask3D
但只有列表的第一项里的特效可以被裁切,之后的列表项目里的特效就不会被裁切了,怎么办?