ScrollRect裁剪ParticleSystem粒子(UGUI)(一)

时间:2024-04-04 11:22:07

UGUI的ScrollRect可以正确的裁剪包括Image,Text等UGUI自身的组件,但是不能正确裁剪ParticleSystem粒子,这给客户端和美术人员造成了很大麻烦,今天我们就通过修改shader的方式,解决裁剪问题

环境搭建

  1. 创建新的场景,添加ScrollView,并将子对象Viewport的Mask替换成Rect Mask 2D(重要)
  2. 创建UI摄像机,修改Projection为Orthographic,Culling Mask仅选择UI
  3. 修改Canvas的RenderMode为Screen-Space Camera,并绑定2中创建的摄像机
  4. 在ScrollView的content下添加一个ParticleSystem
    ScrollRect裁剪ParticleSystem粒子(UGUI)(一)
  5. 运行
    可以看到此时,粒子从ScrollView的边缘穿透出去
    ScrollRect裁剪ParticleSystem粒子(UGUI)(一)

修改Unity原生particle shader

  1. 下载Unity对应版本shader代码(如果链接失效,可以直接去Unity官网找下载),我使用的是unity5.5.1版本
  2. 打开并定位到Particle Add.shader文件,复制一份命名为UIParticleAdditiveClip.shader保存到Asset文件夹中
  3. 修改UIParticleAdditiveClip.shader中的shader名称为“Custom/UI/Particle_Additive_Clip”
  4. 创建一个Material命名为pt,并使用3中创建的shader,选择任意一张图片作为粒子的图片,用pt替换ParticleSystem的默认材质
    ScrollRect裁剪ParticleSystem粒子(UGUI)(一)

这时运行程序,还看不到任何效果,因为我们的shader中缺少裁剪区域,也就是说,我们需要告诉GPU,哪个区域中的粒子需要显示,区域外的粒子不需要显示,为此,我们在shader中添加变量

   float4 _ClipRect;

该变量用来保存2D裁剪框的左下角和右上角2个点,共4个float值。为了简化计算,我们设定这个变量中保存的是world space下的坐标,并且限定摄像机的平行是椎体是朝向Z轴正方向的
ScrollRect裁剪ParticleSystem粒子(UGUI)(一)

接下来修改我们需要在vertex shader中计算粒子的世界坐标,由于我们需要的仅仅是world space中顶点的xy坐标,所以我将这两个坐标保存到了uv坐标的zw中,代码如下:

...
// UnityUI.cginc中包含了UnityGet2DClipping的实现
#include "UnityUI.cginc"
...
struct v2f 
{
  ...
  // 将texcoord扩展为float4,会使shader代码编译错误,这时只要将错误的位置改为texcoord.xy即可
  float4 texcoord : TEXCOORD0;    
  ...
};
...
v2f vert(appdata_t v)
{
  ...
  // 原有uv坐标保存到texcoord.xy中,world space中的xy坐标保存到o.texcoord.zw中
  o.texcoord.xy = TRANSFORM_TEX(v.texcoord.xy,_MainTex).xy;
  o.texcoord.zw = mul(unity_ObjectToWorld, v.vertex).xy;
  ...
  return o;
}
// C#代码需要传入的裁剪区域变量, 这里我们增加一个变量(_UseClipRect)用来标记是否需要裁剪
float4 _ClipRect;
float _UseClipRect;
...
fixed4 frag(v2f i) : SV_Target
{
  ...
  // fragment shader非常简单,我们只需要在最后,对fragment进行裁剪即可,
  // UnityGet2DClipping这个函数实现了判断2D空间中的一点是否在一个矩形区域中
  // lerp函数用来标记是否需要进行裁剪,当_UseClipRect值为1时表示裁剪
  float c = UnityGet2DClipping(i.texcoord.zw, _ClipRect);
  col.a = lerp(col.a, c * col.a, _UseClipRect);
  return col;
}

编写C#代码,计算裁剪区域

[RequireComponent(typeof(ParticleSystem))]
public class ParticleMask : MonoBehaviour
{
   public RectMask2D mask;
   public Material mt;
   private void Awake()
   {
       mt = GetComponent<ParticleSystem>().GetComponent<Renderer>().material;
       mask = GetComponentInParent<RectMask2D>();
       // ScrollView位置变化时重新计算裁剪区域
       GetComponentInParent<ScrollRect>().onValueChanged.AddListener((e) => { setClip(); });
       setClip();
   }

   void setClip()
   {
       Vector3[] wc = new Vector3[4];
       mask.GetComponent<RectTransform>().GetWorldCorners(wc);        // 计算world space中的点坐标
       var clipRect = new Vector4(wc[0].x, wc[0].y, wc[2].x, wc[2].y);// 选取左下角和右上角
       mt.SetVector("_ClipRect", clipRect);                           // 设置裁剪区域
       mt.SetFloat("_UseClipRect", 1.0f);                             // 开启裁剪
   }
}

完成代码后,将这个代码绑定到ParticleSystem上即可

ScrollRect裁剪ParticleSystem粒子(UGUI)(一)

最终效果

ScrollRect裁剪ParticleSystem粒子(UGUI)(一)

可以看到粒子被正确裁剪了

后记

  1. 一般情况下,美术制作的粒子特效会包含多个ParticleSystem,这时就需要获取所有ParticleSystem的materil分别设置裁剪区域和裁剪开关
  2. 这种裁剪方法有一定局限性,需要修改UI的原生shader,并限定美术的shader使用,一般情况下,仅适用于界面上的小特效,比如选中,技能高亮等
  3. Unity中右键创建的ScrollView的Mask不是Rect Mask 2D,需要手动进行修改