特效shader添加裁剪区域
使用裁剪区域进行裁剪, 粒子的shader得支持裁剪区域,需要在vert函数中加上下面的代码:
#ifdef UNITY_UI_CLIP_RECT c.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect); //xy在裁剪矩形范围内就返回1, 否则返回0 clip(c.a - 0.001); #endif
上面的代码参考自UI-Default.shader
效果
下面的脚本用于把裁剪区域设置到Renderer的Material中,该脚本挂在Content或粒子上都行。
注意:Unity2018挂在Viewport-RectMask2D上也行,但在Unity2019中挂在Viewport-RectMask2D就不行了,应该是RectMask2D的实现有改动。
该代码是在MaskableGraphic的基础上精简下了,核心点:
1) 在裁剪回调函数SetClipRect中将裁剪区域设置到Renderer的Material中 2) 在事件函数(OnEnable, OnDisable, OnTransformParentChanged, OnCanvasHierarchyChanged)中把自己添加到RectMask2D的裁剪列表中using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class Clip3D : UIBehaviour, IClippable { [NonSerialized] private RectTransform m_RectTransform; [NonSerialized] private RectMask2D m_ParentMask; private List<Renderer> m_rendererList = new List<Renderer>(); private List<Material> m_matList = new List<Material>(); public RectTransform rectTransform { get { // The RectTransform is a required component that must not be destroyed. Based on this assumption, a // null-reference check is sufficient. if (ReferenceEquals(m_RectTransform, null)) { m_RectTransform = GetComponent<RectTransform>(); } return m_RectTransform; } } public void SetClipSoftness(Vector2 clipSoftness) { //not implement } public void Cull(Rect clipRect, bool validRect) { //not implement } public virtual void SetClipRect(Rect clipRect, bool validRect) { Debug.Log($"({clipRect.xMin}, {clipRect.yMin}), ({clipRect.xMax}, {clipRect.yMax})"); //rootCanvas坐标空间下的, 我们这边要用世界坐标空间下的 GetComponentsInChildren<Renderer>(m_rendererList); if (m_rendererList.Count > 0) { if (validRect) { var worldCorners = new Vector3[4]; //lb, lt, rt, rb m_ParentMask.rectTransform.GetWorldCorners(worldCorners); var worldClipRect = new Vector4(worldCorners[0].x, worldCorners[0].y, worldCorners[2].x, worldCorners[2].y); foreach (var render in m_rendererList) { render.GetMaterials(m_matList); if (m_matList.Count > 0) { foreach (var mat in m_matList) { if (null != mat && mat.HasProperty("_ClipRect")) { mat.SetVector("_ClipRect", worldClipRect); mat.EnableKeyword("UNITY_UI_CLIP_RECT"); } } m_matList.Clear(); } } } else { foreach (var render in m_rendererList) { render.GetMaterials(m_matList); if (m_matList.Count > 0) { foreach (var mat in m_matList) { if (null != mat) { mat.DisableKeyword("UNITY_UI_CLIP_RECT"); } } m_matList.Clear(); } } } m_rendererList.Clear(); } } protected override void OnEnable() { base.OnEnable(); UpdateClipParent(); } protected override void OnDisable() { base.OnDisable(); UpdateClipParent(); } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); UpdateClipParent(); } #endif protected override void OnTransformParentChanged() { base.OnTransformParentChanged(); if (!isActiveAndEnabled) return; UpdateClipParent(); } protected override void OnCanvasHierarchyChanged() { base.OnCanvasHierarchyChanged(); if (!isActiveAndEnabled) return; UpdateClipParent(); } private void UpdateClipParent() { var newParent = IsActive() ? MaskUtilities.GetRectMaskForClippable(this) : null; // if the new parent is different OR is now inactive if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive())) { m_ParentMask.RemoveClippable(this); } // don't re-add it if the newparent is inactive if (newParent != null && newParent.IsActive()) newParent.AddClippable(this); m_ParentMask = newParent; } /// <summary> /// See IClippable.RecalculateClipping /// </summary> public virtual void RecalculateClipping() { UpdateClipParent(); } }
粒子用到的支持裁剪的shader,这边仅仅为了演示随意写了一个(只求能看见粒子就行)
UnlitParticle.shader, UnlitParticle.mat
Shader "My/UnlitParticle" { Properties { _MainTex ("Texture", 2D) = "white" {} _ClipRect ("Clip Rect", Vector) = (0, 0, 0, 0) } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "UnityUI.cginc" #pragma multi_compile __ UNITY_UI_CLIP_RECT struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 worldPosition : TEXCOORD1; }; sampler2D _MainTex; float4 _MainTex_ST; float4 _ClipRect; v2f vert (appdata v) { v2f o; o.worldPosition = v.vertex; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 c = tex2D(_MainTex, i.uv); #ifdef UNITY_UI_CLIP_RECT c.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect); clip(c.a - 0.001); #endif return c; } ENDCG } } }
粒子的材质设置为UnlitParticle.mat,_ClipRect放在Properties中仅仅是为了在运行时看下裁剪区域点值(调试用)
Canvas不能用Overlay模式,否则粒子就看不到,因为Overlay模式,粒子不是直接在相机视口范围内
这边用Camera模式
UI相机得用正交相机,否则粒子会因为透视的原因(近大远小),而导致裁剪区域不对
这边没处理到的问题
1) 粒子的层级问题,上面的粒子实际是白色的,但我们看到的是灰色的,因为粒子出现在了ScrollView背景的后面,这个涉及到ugui和3d物体的层级关系控制了,不在这边的处理范围,这边只处理裁剪
2) Renderer材质生成副本的问题,这个暂时还没想好
关于step函数
step(a, b)函数可以用于代替if (a>=b)的判断,它的逻辑:
if (a>=b) return 0; else return 1;
UnityGet2DClipping函数
CGIncludes/UnityUI.cginc
inline float UnityGet2DClipping (in float2 position, in float4 clipRect) { float2 inside = step(clipRect.xy, position.xy) * step(position.xy, clipRect.zw); return inside.x * inside.y; }
参考
【Shader案例】实现UGUI裁剪功能_两水先木示的博客-CSDN博客_ugui scroll 裁剪3d模型 shader
Unity游戏开发-UI中裁剪特效_shitangdejiaozi的博客-CSDN博客
标签:特效,clipRect,mat,void,裁剪,matList,ScrollView,UI From: https://www.cnblogs.com/sailJs/p/17061809.html