前两个项目我一直用的是 UGUI研究院之Mask裁切UI粒子特效或者3D模型(十七) 做的裁切,但是实际开发中由循环列表中需要动态创建元素,每个新创建的元素都需要根据MaskRect的区域重新给Shader中指定,这个小小的隐患总是引起BUG。最近在做技术储备看到了Stencil一个东西,发现可以很好的代替原来裁切的方法,如下图所示,一共有2个裁切区域,同时裁切 UI 模型 粒子特效。
首先无论怎么做,我觉得需要一个脚本,只需要将模型 UI 特效挂在下面就可以全部裁切,运行期间动态添加不需要调用刷新方法。如下图所示,添加一个新的脚本RectMask3D来代替UGUI自带的RectMask2D
RectMask3D.cs 代码中的m_ID是为了区分裁切区域,大部分情况下裁切区域都是一致的,当然也不排除有重叠的情况,就像最上面的图一样。
C#
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
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
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中。
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,如果有知道的朋友欢迎告诉我。