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


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
  5. 運行
    可以看到此時,粒子從ScrollView的邊緣穿透出去

修改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的默認材質

這時運行程序,還看不到任何效果,因為我們的shader中缺少裁剪區域,也就是說,我們需要告訴GPU,哪個區域中的粒子需要顯示,區域外的粒子不需要顯示,為此,我們在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,需要手動進行修改


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM