刚好前几天有人问我这个问题,再加上新项目也可能用,所以这两天就研究了一下。其实如果粒子特效 和3D模型 都用RenderTexture来做的话就不会有裁切的问题,但是粒子特效用RenderTexture来做会有显示的问题,所以还是得用摄像机。废话不多说了,进入正题。
原理就是把Mask的裁切区域传给粒子特效Shader,当超出这个区域那么直接让它完全透明即可。粒子特效的源生shader大家可以去unity官网下载,我在这里把需要修改的地方标注给大家。
//add 注释中的内容就是我做修改的地方。
Shader "Particles/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
//-------------------add----------------------
_MinX ("Min X", Float) = -10
_MaxX ("Max X", Float) = 10
_MinY ("Min Y", Float) = -10
_MaxY ("Max Y", Float) = 10
//-------------------add----------------------
}
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
Blend SrcAlpha One
AlphaTest Greater .01
ColorMask RGB
Cull Off Lighting Off ZWrite Off Fog { Color (0,0,0,0) }
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_particles
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 _TintColor;
//-------------------add----------------------
float _MinX;
float _MaxX;
float _MinY;
float _MaxY;
//-------------------add----------------------
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
#ifdef SOFTPARTICLES_ON
float4 projPos : TEXCOORD1;
#endif
//-------------------add----------------------
float3 vpos : TEXCOORD2;
//-------------------add----------------------
};
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
//-------------------add----------------------
o.vpos = v.vertex.xyz;
//-------------------add----------------------
o.vertex = mul(UNITY_MATRIX_MVP, 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);
return o;
}
sampler2D_float _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
//-------------------add----------------------
fixed4 c =2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
c.a *= (i.vpos.x >= _MinX );
c.a *= (i.vpos.x <= _MaxX);
c.a *= (i.vpos.y >= _MinY);
c.a *= (i.vpos.y <= _MaxY);
c.rgb *= c.a;
return c;
//-------------------add----------------------
}
ENDCG
}
}
}
}
然后是自己写了个类继承Mask。把Mask的区域传给shader。using UnityEngine; using System.Collections; using UnityEngine.UI;
public class MyMask :Mask { protected override void Start () { base.Start ();
int width = Screen.width; int height = Screen.height; int designWidth = 960;//开发时分辨率宽 int designHeight = 640;//开发时分辨率高 float s1 = (float)designWidth / (float)designHeight; float s2 = (float)width / (float)height;
//目标分辨率小于 960X640的 需要计算缩放比例 float contentScale =1f; if(s1 > s2) { contentScale = s1/s2; } Canvas canvas = GameObject.Find(“Canvas”).GetComponent<Canvas>(); Vector2 pos; if(RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, transform.position, canvas.camera, out pos)){ ParticleSystem [] particlesSystems = transform.GetComponentsInChildren<ParticleSystem>(); RectTransform rectTransform = transform as RectTransform; float minX,minY,maxX,maxY; minX = rectTransform.rect.x + pos.x; minY = rectTransform.rect.y+ pos.y; maxX = minX + rectTransform.rect.width ; maxY = minY + rectTransform.rect.height;
//这里 100 是因为ugui默认的缩放比例是100 你也可以去改这个值,但是我觉得最好别改。 foreach(ParticleSystem particleSystem in particlesSystems) { particleSystem.renderer.sharedMaterial.SetFloat(“_MinX”,minX/100/contentScale); particleSystem.renderer.sharedMaterial.SetFloat(“_MinY”,minY/100/contentScale); particleSystem.renderer.sharedMaterial.SetFloat(“_MaxX”,maxX/100/contentScale); particleSystem.renderer.sharedMaterial.SetFloat(“_MaxY”,maxY/100/contentScale); } } } }
上面这段代码写的不太好,有一个更好的办法来取Mask的裁切区域。
通过GetWorlCornets来确定裁切的区域
Vector3[] corners = new Vector3[4];
RectTransform rectTransform = transform as RectTransform;
rectTransform.GetWorldCorners (corners);
minX = corners [0].x;
minY = corners [0].y;
maxX = corners [2].x;
maxY = corners [2].y;
然后在把裁切的区域传到shader中。
Material m = GetMaterial (renderer);
m.SetFloat("_MinX",minX);
m.SetFloat("_MinY",minY);
m.SetFloat("_MaxX",maxX);
m.SetFloat("_MaxY",maxY);
为了做到不影响美术,所以美术开发特效的时候还是用以前的shader。程序在运行中对它进行更换,这样可以无缝进行切换。
如果运行时裁切区域发生变化, 可以重写OnRectTransformDimensionsChange()方法来重新给材质赋新的裁切区域
protected override void OnRectTransformDimensionsChange ()
{
base.OnRectTransformDimensionsChange ();
Change ();//重新再给材质赋裁切参数
}
OK,如下图所示,把粒子特效直接挂在Mask下面, 就可以进行裁切了。。
在说一下3D模型, 理论上用上述的shader改一改就可以。 但是我还是建议3D模型用RenderTexture。比较好控制深度。
最后是工程的下载地址:http://pan.baidu.com/s/1pJFV5ph
希望大家可以多多测试一下,看看有没有问题。 或者你有更好的方法,欢迎在下面给我留言。谢谢啦~
后记:
1.感谢楼下 @姜华 提出了一个新方案
这个方法非常巧妙, 我也尝试的使用了一下。但是遇到了个问题,当我有两个裁切区域,发生重合的时候就会出问题。如果大家没有这样重合的需求。也可以参考他的方法。
2.asset store上有一个Unity Particle 2D 的插件,大家也可以试试。
- 本文固定链接: https://www.xuanyusong.com/archives/3518
- 转载请注明: 雨松MOMO于 雨松MOMO程序研究院 发表


Unity2017以后 ParticleSystem的Renderer中有了Masking属性(None、Visible Inside Mask、Visible Outside Mask),和SpriteMask组件结合和作出粒子遮罩的效果。参考链接:https://blog.csdn.net/Fenglele_Fans/article/details/81101511
雨松大大,NGUI用这个shader发现,粒子特效是可以的,模型却不行,这是为啥啊
Vector3 t = transform.TransformPoint(new Vector3(-cr.z + cr.x, 0)) – _uiroot.transform.position;
ren.sharedMaterial.SetFloat(“_MinX”, t.x);
Vector3 t1 = transform.TransformPoint(new Vector3(cr.z + cr.x, 0)) – _uiroot.transform.position;
ren.sharedMaterial.SetFloat(“_MaxX”, t1.x);
Vector3 t2 = transform.TransformPoint(new Vector3(0,-cr.w + cr.y)) – _uiroot.transform.position;
ren.sharedMaterial.SetFloat(“_MinY”, t2.y);
Vector3 t3= transform.TransformPoint(new Vector3(0,cr.w + cr.y)) – _uiroot.transform.position;
ren.sharedMaterial.SetFloat(“_MaxY”, t3.y);
这是计算位置的代码