刚好前几天有人问我这个问题,再加上新项目也可能用,所以这两天就研究了一下。其实如果粒子特效 和3D模型 都用RenderTexture来做的话就不会有裁切的问题,但是粒子特效用RenderTexture来做会有显示的问题,所以还是得用摄像机。废话不多说了,进入正题。
原理就是把Mask的裁切区域传给粒子特效Shader,当超出这个区域那么直接让它完全透明即可。粒子特效的源生shader大家可以去unity官网下载,我在这里把需要修改的地方标注给大家。
//add 注释中的内容就是我做修改的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
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来确定裁切的区域
1 2 3 4 5 6 7 |
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中。
1 2 3 4 5 |
Material m = GetMaterial (renderer); m.SetFloat("_MinX",minX); m.SetFloat("_MinY",minY); m.SetFloat("_MaxX",maxX); m.SetFloat("_MaxY",maxY); |
为了做到不影响美术,所以美术开发特效的时候还是用以前的shader。程序在运行中对它进行更换,这样可以无缝进行切换。
如果运行时裁切区域发生变化, 可以重写OnRectTransformDimensionsChange()方法来重新给材质赋新的裁切区域
1 2 3 4 5 |
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);
这是计算位置的代码
粒子特效 3D模型 都用RenderTexture来做 这个是什么意思啊
粒子特效可以用sortorder 3d模型可以用rendertexture
雨松大神,你最后改的脚本,有完整的吗,没有看明白呢,能留一个demo嘛,谢谢了,最近急用
demo没有了, 核心代码就在这篇博文里了。
momo你好,请教下,这里是裁剪粒子,那面片呢?模仿上面的实现,获取出meshrender里面的mat,好像裁剪不了面片?求教
一样的 就是改shader 核心shader 代码 就在文中。。
发现如果绑mask的结点整体发生移动后,裁剪出来的效果有偏差?是传入的裁剪区域和粒子的位置之间的转换有偏差??
找到原因了,是ugui的缩放比例问题,试出来发现不是100,而是114
雨松大神,用mask遮罩,超出遮罩区域的的物体或UI仍然被每帧渲染,有什么办法解决吗?
momo,你shader是在哪学的呀?还有就是ShaderForge这个东西好用吗?
我使用unity3d 5.3.5版本打包,裁剪的位置有问题,向下偏移的几十个像素,是版本不兼容问题吗?
这个场景里面UiCamera有偏移导致的,代码里面RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, transform.position, canvas.camera, out pos)这个传的是transform.position,只有在UICamera位置为0的时候才可以,所以你把UICamera位置调到0显示就会对
雨松大大,相机的OrthographicSize变化之后,裁剪的位置就不对了。大神,这个怎么处理?
变化要把参数传到shader里
拜读大神博客久矣。 一直仰慕!在下有一个方法可以更便捷的解决以上问题,也是我偶然翻查国外论坛找到的。创建两个Shader,一个给粒子用,一个给UI Mask 用,两个Shader的内容均采用Build-in Shader的Particle-Additive 和 UI-Default。利用Shader 的 Stencil 功能, 分别对两个Shader加入以下内容://UI Mask Stencil{Ref 1Comp AlwaysPass Replace}//ParitcleStencil {Ref 1Comp equal}之后分别创建相应材质球,将其赋予UIImage 和 想被遮罩的 Particle 即可。 如果看不见效果,调整一下Particle的位置。 亲测可用。
拜读大神博客久矣。 一直仰慕!在下有一个方法可以更便捷的解决以上问题,也是我偶然翻查国外论坛找到的。创建两个Shader,一个给粒子用,一个给UI Mask 用,两个Shader的内容均采用Build-in Shader的Particle-Additive 和 UI-Default。利用Shader 的 Stencil 功能, 分别对两个Shader加入以下内容://UI Mask Stencil { Ref 1 Comp Always Pass Replace }//Paritcle Stencil { Ref 1 Comp equal }之后分别创建相应材质球,将其赋予UIImage 和 想被遮罩的 Particle 即可。 如果看不见效果,调整一下Particle的位置。 亲测可用。
可以耶,非常感谢
你好,请问下有没有详细的介绍或DEMO可参考下
自己搞了下,果然可以,非常感谢
大神,我最近碰到一个问题就是说rendertexture在某些android机型上无法显示,你有碰到过这个问题吗?
ngui之前遇到过 new出来的rendertexture在某些android机器上无法显示。 ugui目前没发现。。
嗯 是的 就是Ngui 引用的 rendertexture 在某些 android机器上无法显示,后来是怎么解决的呢?
renderTexture文件放在本地 而不是运行时去new
嗯 好像就是本地出的问题………………
额, 那我就不知道了。。 我没遇到过你这种问题。。
恩 后来我放弃用这个了 把 meshrender 和 ngui的 渲染层级统一都在uipanel下处理,
Unity3D 5.2.2 我这样写,如何?
【C#】
RectTransform rectTransform = transform as RectTransform;
Vector2 min = rectTransform.TransformPoint(rectTransform.rect.min);
Vector2 max = rectTransform.TransformPoint(rectTransform.rect.max);
float minX = min.x;
float minY = min.y;
float maxX = max.x;
float maxY = max.y;
ParticleSystem[] pss = transform.GetComponentsInChildren();
foreach (ParticleSystem ps in pss)
{
Material mat = ps.GetComponent().sharedMaterial;
mat.SetFloat(“_MinX”, minX);
mat.SetFloat(“_MinY”, minY);
mat.SetFloat(“_MaxX”, maxX);
mat.SetFloat(“_MaxY”, maxY);
}
【Shader】
fixed4 col = 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
col.a *= (i.vpos.x >= _MinX && i.vpos.x = _MinY && i.vpos.y <= _MaxY) ? 1 : 0;
return col;
另外:
o.vpos = v.vertex.xyz;
要改为:
o.vpos = mul(_Object2World, v.vertex).xyz;
不管用啊
请教下:我觉得这边_MinX这4个值直接传rectTransform.rect.x等就好了啊,因为着色器里面直接跟顶点的vertex.xyz比较,而这个是粒子顶点的局部坐标吧?为什么还要加pos.x和pos.y呢?
雨松大大,我最近做ui界面播放粒子特效,效果不对啊,NGUI摄像机显示不对啊,特效是美术在maincamera下做的
雨松大大 你好。最近在关注UI上显示3D模型的问题,用的是UGUI。很多都在说关于用RenderTexture, 也有的说直接在canvas 的render mode 选 screen space-camera。请问究竟这两个哪个好呢?
雨松大大 你好,请问用ugui的mask能做新手引导那种的遮罩吗?就是中间会有一小部分不遮,可以提供点击。。感谢
实现一个ICanvasRaycastFilter就行了。
无意间试出来了的:
把
c.a *= (i.vpos.x >= _MinX );
c.a *= (i.vpos.x = _MinY);
c.a *= (i.vpos.y = _MinX && i.vpos.x = _MinY && i.vpos.y <= _MaxY);
我就R了G了……
UI 正交摄像机的Y轴,默认是1? 修改成0 就没偏移了。
第 66行 修改为:o.vpos = mul(_Object2World, v.vertex).xyz;
momo, 我有个问题想请教一下UGUI Mask组件是怎样遮罩子结点的呢?
momo,好久不见。保重身体哦。关于你这篇文章我想问下,你的这个遮挡在手机上是否很耗性能。我了解的如果用NGUI,也可以通过UIPanel来完成你上面的遮挡,但是太贵。
效率上肯定要低一些。 最好是策划没这需求,,
momo,好久不见。保重身体哦。关于你这篇文章我想问下,你的这个遮挡在手机上是否很耗性能。我了解的如果用NGUI,也可以通过UIPanel来完成你上面的遮挡。
大神,我发现新的ui做按钮点击很不方便,比如我的按钮样子是一个背景框加一个头像,那么如果我给背景框加button组件是没有用的,Graphic Raycaster捕捉到的是头像。有没有什么办法可以设置Graphic Raycaster只捕捉特定的ui元素。我看Graphic Raycaster有个Block Mask参数可以设定一个层,但是试了下发现不起作用。用Physics 2D Raycaster替代的话也很不方便,它是根据距离判断的,而ui是在同一距离的
上unity官网上找了下,发现了个不错的办法,用Canvas Group可以是全部界面都不被捕捉,然后再在想要被捕捉的元素上加Canvas Group.
对的, Canvas Group 我也在轰轰烈烈的使用呢。。
3Q
你把背景作为父对象,其他(头像啊外框啊)作为子对象,这样点击就是点击到背景上了。