![4905eff2f597039f8dbe1a21b4c60f15.png](https://i-blog.csdnimg.cn/blog_migrate/64a875ffa87253c7e0599173393bd579.jpeg)
起因:
使用ScrollRect和遮罩实现滑动列表,列表中的物体带有粒子特效时,通过下面这张图片可以看出问题
![25653ed7b8033a1f9c3db853b3ce29eb.png](https://i-blog.csdnimg.cn/blog_migrate/4cfed47b69526394f765b92b1e2df2dd.png)
粒子特效会透过遮罩显示出来。
解决方案:
修改shader + C#脚本来控制粒子特效显示区域
本例中粒子使用的shader是unity的Particle Add.shader
在官网下载shader源码,找到Particle Add.shade,修改后的shader如下:
Shader "Shader Forge/AdditiveClip" {
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
//新增,添加裁剪区域
_Area("Area", Vector) = (0,0,1,1)
}
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;
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);
//新增,通过矩阵把顶点从Object空间转换到世界空间
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
//新增,判断顶点坐标是否在裁剪框内
bool inArea = i.worldPos.x >= _Area.x && i.worldPos.x <= _Area.z && i.worldPos.y >= _Area.y && i.worldPos.y <= _Area.w;
//----end---
fixed4 col = 2.0 * 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
return inArea ? col : fixed4(0, 0, 0, 0);
//return col;
}
ENDCG
}
}
}
}
需要修改添加的地方,已经标记了注释,然后通过C#代码来动态设置显示的区域
C#代码:
public class EffectClip : MonoBehaviour
{
[SerializeField] RectTransform m_rectTrans;//裁剪范围
List<Material> materialList = new List<Material>();//需要修改的材质球列表
Transform canvas;//UI的根,Canvas
float halfWidth, halfHeight, canvasScale;
void Start()
{
canvas = GameObject.Find("Canvas").transform;
ScrollRect scroll = GetComponentInParent<ScrollRect>();
m_rectTrans = scroll.GetComponent<RectTransform>();
//获取所有需要修改shader的material,并替换shader
var particleSystems = GetComponentsInChildren<ParticleSystem>();
for (int i = 0, j = particleSystems.Length; i < j; i++)
{
var ps = particleSystems[i];
var mat = ps.GetComponent<Renderer>().material;
materialList.Add(mat);
}
var renders = GetComponentsInChildren<Image>();
for (int i = 0, j = renders.Length; i < j; i++)
{
var ps = renders[i];
var mat = ps.material;
materialList.Add(mat);
}
//获取UI的scale,容器的宽高的一半的值
canvasScale = canvas.localScale.x;
halfWidth = m_rectTrans.sizeDelta.x * 0.5f * canvasScale;
halfHeight = m_rectTrans.sizeDelta.y * 0.5f * canvasScale;
//修改shader的_Area值
Vector4 area = CalculateArea(m_rectTrans.position);
for (int i = 0, len = materialList.Count; i < len; i++)
{
materialList[i].SetVector("_Area", area);
}
}
//计算容器在世界坐标的Vector4,xz为左右边界的值,yw为下上边界值
Vector4 CalculateArea(Vector3 position)
{
return new Vector4()
{
x = position.x - halfWidth,
y = position.y - halfHeight,
z = position.x + halfWidth,
w = position.y + halfHeight
};
}
}
PS:借鉴了网上方法,当作笔记记录下来,加深印象
用法
把C#脚本挂到需要裁剪的粒子特效物体上面
如果成功的话如下图:
![a10766698d4c2532c763feae85e09f6e.png](https://i-blog.csdnimg.cn/blog_migrate/9834369d4fdb857e50bbfbf0b6ac0051.png)
总结:
由于不同项目使用的粒子特效shader不同,但修改的方法大同小异,可以把上面的添加到shader的代码添加到你们项目使用的shader中去。
通过判断顶点是否在显示区域内,来裁剪不必要渲染的点,实现粒子特效在UI上面的裁剪。