UGUI - 解决粒子特效无法被遮罩遮住问题

今天UX要我给滚动列表上的item加上粒子特效,想着没问题啊。直接把特效挂在item上,但没有考虑到particle system的canvas order问题,导致出现了例子特效出现在窗口上方,特效并不能被mask遮盖掉的问题。

额外做了个简单的demo,scrollview做窗口

方案一:用图片遮盖特效

一开始想到用最简单最naive的方法是在窗口上下加个带canvas的图片, 通过调高它的层级把特效遮住,有点打补丁的意思。但!如果在多分辨率的屏幕适配的情况下,这个方法及其有可能被发现有问题,还是要谨慎使用哈

在底下加了个白色的图片,可以看出特效已经被遮盖住了

方案二:修改shader

不管窗口大小如何变都可以完美的遮住超出来的粒子特效,我们可以通过改shader的方式解决:shader传scrollview的四个顶点的世界坐标,shader判断特效在框内的话则显示,反之则隐藏。

Step 1: 我的粒子特效挂的是unity内置的shader particle.addtive.shader, 先从unity官网上下载其源代码,注意用旧版本的比较好改,新版本的简化了很多,路径是builtin_shaders/DefaultResourcesExtra/Particle Add.shader. 把shader复制出一份并重命名Addtive.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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Additive Particle shader that can be hidden if it is not in the canvas
Shader "Particle/Additive" {
Properties {
    _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
    _MainTex ("Particle Texture", 2D) = "white" {}
    _InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
    // Record the value of the border
    _Area ("Area", Vector) = (0,0,1,1)
    // 1 means clip on, 0 means clip off
    _IsClip ("IsClip", Int) = 0
}
 
Category {
    Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
    Blend SrcAlpha One
    ColorMask RGB
    Cull Off Lighting Off ZWrite Off
 
    SubShader {
        Pass {
 
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
            #pragma multi_compile_particles
            #pragma multi_compile_fog
 
            #include "UnityCG.cginc"
 
            sampler2D _MainTex;
            fixed4 _TintColor;
 
            float4 _Area;
            int _IsClip;
 
            struct appdata_t {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            struct v2f {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                #ifdef SOFTPARTICLES_ON
                float4 projPos : TEXCOORD2;
                #endif
                float2 worldPos : TEXCOORD3;
                UNITY_VERTEX_OUTPUT_STEREO
            };
 
            float4 _MainTex_ST;
 
            v2f vert (appdata_t v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.vertex = UnityObjectToClipPos(v.vertex);
                #ifdef SOFTPARTICLES_ON
                o.projPos = ComputeScreenPos (o.vertex);
                COMPUTE_EYEDEPTH(o.projPos.z);
                #endif
                o.color = v.color;
                o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xy;
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
 
            UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
            float _InvFade;
 
            fixed4 frag (v2f i) : SV_Target
            {
                #ifdef SOFTPARTICLES_ON
                float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos)));
                float partZ = i.projPos.z;
                float fade = saturate (_InvFade * (sceneZ-partZ));
                i.color.a *= fade;
                #endif
 
                fixed4 col = 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
                col.a = saturate(col.a); // alpha should not have double-brightness applied to it, but we can't fix that legacy behavior without breaking everyone's effects, so instead clamp the output to get sensible HDR behavior (case 967476)
 
                UNITY_APPLY_FOG_COLOR(i.fogCoord, col, fixed4(0,0,0,0)); // fog towards black due to our blend mode
 
                // Check whether the vertex coordinates are in the clipping frame
                bool inArea = i.worldPos.x >= _Area.x && i.worldPos.x <= _Area.z && i.worldPos.y >= _Area.y && i.worldPos.y <= _Area.w;
                // If its position is in the clipping frame or the effect of cliping is off, return the original effect, otherwise it will be hidden
                return (!inArea && _IsClip == 1) ? fixed4(0, 0, 0, 0) : col;
            }
            ENDCG
        }
    }
}
}

Step2: 先将将UI中的粒子特效的shader都改成修改过后的Addtive.shader,再写一个用来计算裁剪框的四个顶点的世界坐标并传到shader里的脚本VFXClip.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
56
57
58
59
60
61
62
63
using System.Collections.Generic;
using UnityEngine;
 
namespace Demo
{
    public class VFXClip : MonoBehaviour
    {
        private RectTransform rectTrans; // Mask transform
        private List<Material> materialList = new List<Material>();
        private Transform canvas;
        private float halfWidth;
        private float halfHeight;
        private float canvasScale;
 
        void Awake()
        {
            var mask  = this.transform.GetComponentInParent<UnityEngine.UI.Mask>();
            if (mask != null)
            {
                this.rectTrans = mask.gameObject.GetComponent<RectTransform>();
            }
        }
        void Start()
        {
            if (this.rectTrans == null)
            {
                return;
            }
 
            this.canvas = this.transform.GetComponentInParent<Canvas>().transform;
 
            var renders = this.transform.GetComponentsInChildren<ParticleSystemRenderer>();
            for (int i = 0, j = renders.Length; i < j; i++)
            {
                var render = renders[i];
                var mat = render.material;
                this.materialList.Add(mat);
            }
 
            this.canvasScale = this.canvas.localScale.x;
            this.halfWidth = this.rectTrans.rect.width * 0.5f * this.canvasScale;
            this.halfHeight = this.rectTrans.rect.height * 0.5f * this.canvasScale;
 
            Vector4 area = this.CalculateArea(this.rectTrans.position);
            for (int i = 0, len = this.materialList.Count; i < len; i++)
            {
                this.materialList[i].SetInt("_IsClip", 1);
                this.materialList[i].SetVector("_Area", area);
            }
        }
 
        private Vector4 CalculateArea(Vector3 position)
        {
            return new Vector4()
            {
                x = position.x - this.halfWidth,
                y = position.y - this.halfHeight,
                z = position.x + this.halfWidth,
                w = position.y + this.halfHeight
            };
        }
    }
}

N.B. 要注意计算四个顶点的时候,viewport的pivot值会影响到中心点的位置, CalcaulateArea这个函数根据我scrollview所用的pivot来算

最终效果图:

 


__EOF__

本文作者cancantrbl
本文链接https://www.cnblogs.com/cancantrbl/p/15111045.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   cancantrbl  阅读(3799)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
点击右上角即可分享
微信分享提示