ScrollRect clips ParticleSystem particles

ScrollRect clips ParticleSystem particles

UGUI's ScrollRect can correctly crop UGUI's own components including Image, Text, etc., but it cannot correctly crop ParticleSystem particles, which causes a lot of trouble to the client and artists. Today, we will solve the cropping problem by modifying the shader.

Environment construction

  1. Create a new scene, add a ScrollView, and replace the Mask of the sub-object Viewport with Rect Mask 2D (important)
  2. Create UI camera, modify Projection to Orthographic, Culling Mask only select UI
  3. Modify the RenderMode of Canvas to Screen-Space Camera and bind the camera created in 2
  4. Add a ParticleSystem under the content of the ScrollView
  5. You can see that at this time
    , the particles penetrated from the edge of the ScrollView

Modify Unity's native particle shader

  1. Download the shader code of the corresponding version of Unity (if the link fails, you can go directly to the official website of Unity to find the download), I am using the unity5.5.1 version
  2. Open and locate the Particle Add.shader file, copy a copy named UIParticleAdditiveClip.shader and save it to the Asset folder
  3. Modify the shader name in UIParticleAdditiveClip.shader to "Custom/UI/Particle_Additive_Clip"
  4. Create a Material named pt, and use the shader created in 3, select any image as the particle image, and replace the default material of ParticleSystem with pt

Running the program at this time does not see any effect, because our shader lacks a clipping area, that is, we need to tell the GPU which area of ​​the particle needs to be displayed, and the particles outside the area do not need to be displayed. To this end, we Add variables in shader

   float4 _ClipRect;

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

接下来修改我们需要在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上即可

最终效果

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

后记

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325734995&siteId=291194637