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,修改的部分在注释上标出:
// 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,把其挂到相对应的特效上:
using System.Collections.Generic;
using UnityEngine;
namespace Demo
{
public class VFXClip : MonoBehaviour
{
private RectTransform rectTrans; // Mask transform
private List materialList = new List();
private Transform canvas;
private float halfWidth;
private float halfHeight;
private float canvasScale;
void Awake()
{
var mask = this.transform.GetComponentInParent();
if (mask != null)
{
this.rectTrans = mask.gameObject.GetComponent();
}
}
void Start()
{
if (this.rectTrans == null)
{
return;
}
this.canvas = this.transform.GetComponentInParent
N.B. 要注意计算四个顶点的时候,viewport的pivot值会影响到中心点的位置, CalcaulateArea这个函数根据我scrollview所用的pivot来算
最终效果图: