在功能开发中,有时候为了更好的效果会在UI上添加一些特效,比如在头像框上增加一个圆环的粒子特效,但由于粒子和UI的渲染方式有些不同,导致会出现UI和特效之间穿插,显示上不理想。并且如果在ScrollRect下显示人物列表,滑动滚动条时还无法对粒子特效进行遮罩处理。这里集中解决这两个问题:
首先解决如何使粒子特效显示在UI上:
1.设定Canvas渲染模式:
当添加一个UI对象时,如果Hierarchy下没有Canvas,则UGUI系统会自动添加该对象,并且Canvas的渲染模式默认为Screen Space - Overlay。Canvas有三种渲染模式:
Screen Space - Overlay:无论其他任何3D物体,UI始终显示在屏幕最前方
Screen Space - Camera:此时Camera与Canvas有一定的距离,可以在这段距离内添加一些3D物体,如粒子特效等,通过调节粒子物体的Z坐标即可在UI上显示出来。当然为了实现这个效果也有别的方式,后面会介绍
World Space:这种模式下,UI就和普通的3D空间物体,比如引擎自带的Sphere、Box等一样。
由于粒子特效属于3D物体,为了使得粒子特效能够显示在UI上,这里需要改变Canvas的渲染模式为“Screen Space - Camera”:
这种模式下需要为Canvas指定渲染相机,同时设置本Canvas的Layer,以及order in layer。设置完成后,本Canvas下的所有UI都属于同一层,并且Order in layer保持一致,渲染时根据从上到下的顺序决定UI之间的遮挡关系
2.设置UI和粒子特效之间的Order in Layer:
首先具体解释下UI之间的渲染关系:
各个UI根据自身设定的Layer分隔,在同一layer根据Order in layer进行排序,数值越大越显示在上面。在同一order in layer的UI则按照Hirearchy中从上到下的顺序依次渲染。后面的会遮挡之前渲染的UI。
如下图Hierarchy中UI设置以及显示效果如下:
在同一canvas下,按照从上到下的渲染顺序,Red一定会遮挡住Blue的画面。那么如何能够做到即使Blue在Red之前渲染,也依然能可以显示在Red上面呢?这种情况就需要改变UI的layer或者Order in layer —— 一般情况下改变Order in layer就可以达到效果,所以也用不着改变layer层了
为了实现效果有两种方式:
一、因为同一canvas下Red必定会挡住Blue,因此这里把Red从Blue下提取出来,这里为了演示的需要,仍然将Red放在Blue之下。然后给Blue添加Canvas用于改变Blue的Order in layer
Red在默认的Canvas下渲染,因此其layer - default, Order in layer - 0。当给Blue添加Canvas并开启Override sorting,设置Order in layer 为0后,会将该Order in layer已渲染完成的UI进行重置,默认显示重置之后的UI —— 但仅针对同一屏幕坐标有多个UI重叠的区域进行重置,在没有重叠的区域,则依然显示之前的UI:
上述情况Blue - size: 200 x 200, Red - size: 100 x 100,当重置Blue的canvas后,Blue会将同一Order in layer重置,因此会完全挡住Red。但如果改变Blue - size:50 x 50,则会显示:
由上可知,所谓的重置也仅仅只是针对同一区域有多个UI显示的情况,区域外的则不会受到影响
二、第二种方式和第一种的原理类似,但不用改变Blue 和 Red之间的父子关系,直接改变Red的Order in layer。在默认情况下,Blue 和 Red都在 Layer - default, Order in layer - 0,然后按照从上到下的顺序渲染,要使得Blue显示在Red上面,则需要重置Red。
虽然为Red添加了Canvas来重置Order in layer,但由于Blue 和 Red渲染layer、order设置相同,且Red - size较小,即使重置也只会影响很小的区域。所以就必须改变Red的Order,使得Red在Blue之后渲染,这样就可以实现Blue显示在Red上面了。由于Blue默认order in layer为0,因此这里设置Red的order in layer为“ -1”即可。
注意:UGUI在进行Drawcall合并时是按照Canvas来进行的,不同的Canvas即使layer,order in layer相同,也无法合并Drawcall。因此对于项目整体UI布局需要事先设计好,为了不影响后期优化,尽量不要随意添加Canvas来改变UI渲染关系
根据以上的解释,要使得粒子特效显示在UI上则只需要改变粒子特效的“Sorting layer” 和 “Order in layer”,通常情况下改变“Order in layer”即可达到效果。UI默认的“Order in layer”为“0”,因此只要设置粒子特效的“Order in layer”大于0即可。
运行效果如下:
如此粒子特效就显示在UI上了,通过设置粒子和UI之间的order in layer即可解决相关的UI穿插问题
2.如何解决ScrollRect滑动时粒子特效无法被遮罩处理的问题?
首先我们需要知道遮罩Mask是如何起作用的?
这里需要引入一个“模板缓存 - Stencil Buffer”的Shader功能,当材质所使用的Shader支持Stencil Buffer时,则可以通过调节参数来自定义UI之间的遮挡剔除关系。
1.参数解析:Shader中的Stencil Buffer的关键参数:
Properties
{
.................
_Stencil ("Stencil ID", Float) = 0
_StencilComp ("Stencil Comparison", Float) = 8
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask("Color Mask", Float) = 15
.................
}
SubShader
{
.................
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
..................
..................
ColorMask[_ColorMask]
.................
.................
}
通常自定义效果时改变的参数为:Ref [_Stencil], Comp [_StencilComp], Pass [_StencilOp]
Ref:该UI所使用的referenceValue,在计算颜色时会把该数值与Stencil Buffer中存储的数值进行比较。对于使用具有模板缓存功能的shader,会把该UI的各个像素点使用referenceValue进行标记并将该referenceValue存入模板缓存即stencilBufferValue = referenceValue,用于后续与其他referenceValue的比较。取值范围是 0 - 255 的整数
Stencil Buffer在初始状态下referenceValue数值默认为0
Comp:将Ref数值与Stencil Buffer中存储的数值进行比较的操作,如大于、等于、小于等。
各种操作的情况如下:代表各个操作的数值从0 - 8 依次递增
Pass:代表通过上述的referenceValue比较后对该像素执行的操作,代表各个操作的数值从0 - 7依次递增
PS: 一般情况下并不会对ReadMask、WriteMask进行修改,保持默认值即可。
ReadMask :
从字面意思的理解就是读遮罩,readMask将和referenceValue以及stencilBufferValue进行按位与(&)操作,readMask取值范围也是0-255的整数,默认值为255,二进制位11111111,即读取的时候不对referenceValue和stencilBufferValue产生效果,读取的还是原始值。
WriteMask:
是当写入模板缓冲时进行掩码操作(按位与&),writeMask取值范围是0-255的整数,默认值也是255,即当修改stencilBufferValue值时,写入的仍然是原始值。
和深度测试一样,在unity中,每个像素的模板测试也有它自己一套独立的依据,具体公式如下:
if(referenceValue&readMask comparisonFunction stencilBufferValue&readMask)
通过像素
else
抛弃像素
通过的像素操作则根据 stencilOperation命令值 来确定,并更新模板缓冲内的值:
在更新模板缓冲值的时候,也有writeMask进行掩码操作,用来对特定的位进行写入和屏蔽,默认值为255(11111111),即所有位数全部写入,不进行屏蔽操作。
注意:
当“_StencilComp”通过测试时,该像素点即会被绘制显示出来,但“_StencilOp”指的是通过模板或深度测试后,对“Stencil Buffer”中该像素点对应的标记值进行处理,这个与本像素点是否会显示出来是无关的,仅仅只是针对模板缓存中存储的数值进行操作,默认是keep,即本UI所使用的referenceValue并不会被写入模板缓存中,Stencil Buffer依然保留之前的数值——该数值会继续用于下次的“stencilComp”比较
2.Stencil Buffer实例操作:
当新建某个UI image时,可以看到该image默认使用的shader为“UI/Default”,其已经支持了Stencil Buffer的功能:
但由于其使用的是UGUI中默认的材质“Default UI Material”,因此无法自定义其中的参数。
为了实现自定义遮挡的效果,可以新建一个materialA,并设置其shader为“UI/Default”即可:
如此即可以自由设定参数实现想要的效果
如上参数所示,默认情况下referenceValue = 0, Comp = Always, Op = Keep
如下所示,创建两个image,都使用默认的材质:
由于参数保持默认,因此先绘制Blue时,始终通过stencil test,Blue整个被绘制出来;然后绘制Red,在两张image的重叠处,Red通过stencil test被绘制出来,所以Red会将Blue进行部分遮挡,此时stencil buffer中存储的数据由于stencilOp = keep,因此仍然保持为0
根据以上情况,如何在按照从上到下依次绘制Blue、Red的情况下,在重叠处依然显示Blue呢?
此时可以直接调节参数,只需要将Red设置为不通过stencil test即可:
解析:因为要实现在Blue与Red重叠处,对于Red不能通过stencil test,但是非重叠处则可以通过stencil test,因此这就必然要求在绘制Red之前就已经使得Blue显示区域和非显示区域的stencil buffer中存储的数值不同。默认情况下,stencil buffer中 stencilBufferValue = 0,因此需要改变Blue的referenceValue = 1,stencilOp = 2(Replace),stencilComp = 8(Always)。在通过stencil test后,根据stencilOp将referenceValue写入stencilBufferValue中。
此时Blue绘制区域的stencilBufferValue = 1,非绘制区域stencilBufferValue保持为默认的0。即满足了要求。
对于Red来讲,重叠处无法通过stencil test,而重叠处的stencilBufferValue = 1。因此设置Red的referenceValue = 0, stencilComp = Equal(3),stencilOp = 0。由于重叠处使用Equal比较,因此对于Red必然无法通过stencil test,不会被绘制,而非重叠处referenceValue = stencilBufferValue,所以通过stencil test,Red可以被绘制。针对于stencilOp参数,一般情况不需要改变,这里依然保持默认值Keep。效果如下:
PS: 参数“Use Alpha Clip” 和 “Color Mask” 的作用:
1.参数“Use Alpha Clip”:
这是由于在将导入的Texture设置为“Sprite”格式时,Unity会自动裁切掉该texture多余的像素,但有些边角依然会有少许残留。当勾选“Use Alpha Clip”后:
该参数勾选后,会自动将image中alpha小于0.001的像素舍弃掉。如此在绘制UI时则相当于此处完全没有像素,也就不会对UI之间的遮挡有影响。
注意:如果美术人员的某张texture部分区域alpha很低,但由于此处依然有像素,因此还是会挡住其他的UI,并且如果stencilOp为replace,则此alpha很低的区域依然会写入stencilBufferValue中。为了解决这个问题,“Use Alpha Clip”会将image中alpha很低的像素舍弃掉
如上图标记位置,虽然alpha很小,但依然会遮挡后面的图片。当舍弃该区域的像素时,
如此则能得到更好的显示效果。
2.参数“Color Mask”:对本UI中通过stencil test的像素做屏蔽处理,注意这里的屏蔽仅仅只是针对自身而言。比如该区域之前已经有像素被绘制,本UI又没有通过stencil test,此时即使设置“Color Mask = 0”也不会对该区域之前的像素有影响
像素是否会显示出来取决于该像素是否通过stencil test,和 color mask设置的数值。默认情况下,“color mask = 15”,因此只要该像素通过stencil test即会在显示出来。
例如当设置BlueMat的“color mask = 0”时—— 可以使用这种方式达到一些镂空挖洞的效果
虽然Blue已经通过了stencil test,但由于color mask的作用,其并不会显示出来。并且通过调节“Color Mask”的数值可以得到不同的显示效果:
Color Mask = 5: Color Mask = 10:
3.在粒子shader中加入stencil buffer功能:
如下所示,在Blue下添加粒子效果,可以明显的看到粒子超出了image的边界——这里使用的shader是"Legacy Shaders/Particles/Additive"。出现这样的原因在于粒子shader并没有Stencil Buffer功能
为了实现遮罩效果,需要在粒子的shader中加入Stencil Buffer。新建Shader脚本,将该粒子所使用的shader源码拷贝到新建的shader脚本中,并在其中添加上述的Stencil Buffer关键参数Property 和 Stencil,并新建material使用该shader,这样就可以自由调节参数达到效果了。
例如:这里使用Unity内置的Shader:“Particle Add.shader”
新建Shader —— CustomParticleShader.shader,拷贝“Particle Add.shader”的源码,并添加StencilBuffer关键参数到CustomParticleShader.shader脚本文件中:
Shader "Custom/CustomParticleShader"
{
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
_Stencil("Stencil ID", Float) = 0
_StencilComp("Stencil Comparison", Float) = 8
_StencilOp("Stencil Operation", Float) = 0
_StencilWriteMask("Stencil Write Mask", Float) = 255
_StencilReadMask("Stencil Read Mask", Float) = 255
_ColorMask("Color Mask", Float) = 15
}
Category{
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" }
Stencil
{
Ref[_Stencil]
Comp[_StencilComp]
Pass[_StencilOp]
ReadMask[_StencilReadMask]
WriteMask[_StencilWriteMask]
}
Blend SrcAlpha One
ColorMask RGB
Cull Off Lighting Off ZWrite Off
ColorMask[_ColorMask]
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_particles
#pragma multi_compile_fog
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 _TintColor;
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
#ifdef SOFTPARTICLES_ON
float4 projPos : TEXCOORD2;
#endif
UNITY_VERTEX_OUTPUT_STEREO
};
float4 _MainTex_ST;
v2f vert(appdata_t v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
o.vertex = UnityObjectToClipPos(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);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
UNITY_DECLARE_DEPTH_TEXTURE(_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
fixed4 col = 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
col.a = saturate(col.a); // alpha should not have double-brightness applied to it, but we can't fix that legacy behavior without breaking everyone's effects, so instead clamp the output to get sensible HDR behavior (case 967476)
UNITY_APPLY_FOG_COLOR(i.fogCoord, col, fixed4(0,0,0,0)); // fog towards black due to our blend mode
return col;
}
ENDCG
}
}
}
}
此时将使用CustomParticleShader.shader的新材质ParticleMat赋值给粒子特效:
要实现的效果是粒子超出Blue边界时会自动被裁切掉,不会显示出来,所以需要调节ParticleMat的参数。由于Blue区域的stencilBufferValue = 1,区域外的stencilBufferValue = 0,因此可以设置ParticleMat中“referenceValue = 1,stencilComp = Equal(3),stencilOp = Keep(0)”,效果如下:
如此粒子特效的遮罩效果也实现了。
PS:
1.Unity的内置Shader源码:
一是在官网下载:https://unity3d.com/get-unity/download/archive
选择对应的平台和版本后,点击“Built in shaders”即可
二是在Github上下载,有个开源项目地址:https://github.com/TwoTailsGames/Unity-Built-in-Shaders
2.如上使用的粒子stencil buffer的shader在实际使用中显示效果上会有一些问题,导致粒子呈现方形的效果。可以使用如下shader: —— 支持stencil buffer:
Shader "UI/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
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
// Soft Mask support
// Soft Mask determines that shader supports soft masking by presence of this property.
[PerRendererData] _SoftMask("Mask", 2D) = "white" {}
}
Category {
Tags {
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Blend SrcAlpha One
ColorMask RGB
Cull Off Lighting Off ZWrite Off
SubShader {
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#pragma multi_compile_particles
#pragma multi_compile_fog
#include "UnityCG.cginc"
#include "UnityUI.cginc"
// Soft Mask Support
// You also can use full path (Assets/...)
#include "Assets/Plugins/SoftMask/Shaders/SoftMask.cginc"
#pragma multi_compile __ UNITY_UI_ALPHACLIP
// Soft Mask Support
#pragma multi_compile __ SOFTMASK_SIMPLE SOFTMASK_SLICED SOFTMASK_TILED
sampler2D _MainTex;
fixed4 _TintColor;
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
//#ifdef SOFTPARTICLES_ON
//float4 projPos : TEXCOORD2;
//#endif
// Soft Mask Support
// The number in braces determines what TEXCOORDn Soft Mask may use
// (it required only one TEXCOORD).
SOFTMASK_COORDS(2)
};
float4 _MainTex_ST;
v2f vert (appdata_t IN)
{
v2f v;
v.vertex = UnityObjectToClipPos(IN.vertex);
//#ifdef SOFTPARTICLES_ON
//v.projPos = ComputeScreenPos (v.vertex);
//COMPUTE_EYEDEPTH(v.projPos.z);
//#endif
v.color = IN.color;
v.texcoord = TRANSFORM_TEX(IN.texcoord,_MainTex);
UNITY_TRANSFER_FOG(v,v.vertex);
SOFTMASK_CALCULATE_COORDS(v, IN.vertex) // Soft Mask Support
return v;
}
sampler2D_float _CameraDepthTexture;
float _InvFade;
fixed4 frag (v2f IN) : SV_Target
{
//#ifdef SOFTPARTICLES_ON
//float sceneZ = LinearEyeDepth (SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(IN.projPos)));
//float partZ = IN.projPos.z;
//float fade = saturate (_InvFade * (sceneZ-partZ));
//IN.color.a *= fade;
//#endif
fixed4 col = 2.0f * IN.color * _TintColor * tex2D(_MainTex, IN.texcoord);
UNITY_APPLY_FOG_COLOR(IN.fogCoord, col, fixed4(0,0,0,0)); // fog towards black due to our blend mode
col.a *= SOFTMASK_GET_MASK(IN); // Soft Mask Support
return col;
}
ENDCG
}
}
}
}