示例:
这是一个ScrollView上的特效矩形遮挡,实现原理是修改特效的shader。
判断渲染的顶点坐标是否在可渲染的矩形范围呢,可以则在片元返回颜色透明度为0,不是则返回应有颜色。
这里不一定是矩形范围 ,圆形也行,需要自行编辑区域范围和判断顶点位置。先来讲讲矩形范围的UI上的特效遮挡的实现。
首先编辑特效:
如果是unity自带shader,可以网上copy源码,然后加入以下代码段,这里使用的shader是Additive,然后我们需要自己加入自己代码段。
先附上Additive源码:
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
}
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) }
BindChannels {
Bind "Color", color
Bind "Vertex", vertex
Bind "TexCoord", texcoord
}
// ---- Fragment program cards
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_particles
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 _TintColor;
struct appdata_t {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
#ifdef SOFTPARTICLES_ON
float4 projPos : TEXCOORD1;
#endif
};
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
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 _CameraDepthTexture;
float _InvFade;
fixed4 frag (v2f i) : COLOR
{
#ifdef SOFTPARTICLES_ON
float sceneZ = LinearEyeDepth (UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
float partZ = i.projPos.z;
float fade = saturate (_InvFade * (sceneZ-partZ));
i.color.a *= fade;
#endif
return 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord);
}
ENDCG
}
}
// ---- Dual texture cards
SubShader {
Pass {
SetTexture [_MainTex] {
constantColor [_TintColor]
combine constant * primary
}
SetTexture [_MainTex] {
combine texture * previous DOUBLE
}
}
}
// ---- Single texture cards (does not do color tint)
SubShader {
Pass {
SetTexture [_MainTex] {
combine texture * primary
}
}
}
}
}
然后我们创建一个新的shader在原有名字末尾加上Mask =》 AdditiveMask
在属性里加入框选的代码,这段代码是用于矩形区域判断的(也可以自己编辑圆的范围)
在片元着色阶段编辑修改成框选代码
用于判断是否在矩形范围内,是则使用本身颜色,不是则透明化。
下面是完整代码。
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
Shader "Legacy Shaders/Particles/AdditiveMask" {
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 记录裁剪框的四个边界的值
_Area ("Area", Vector) = (0,0,1,1)
//----end----
//MASK SUPPORT ADD
_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
//MASK SUPPORT END
}
Category {
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
Blend SrcAlpha One
ColorMask RGB
Cull Off Lighting Off ZWrite Off
SubShader {
//MASK SUPPORT ADD
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
ColorMask [_ColorMask]
//MASK SUPPORT END
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;
//新增,对应上面的_Area
float4 _Area;
//----end----
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
//新增,记录顶点的世界坐标
float2 worldPos : TEXCOORD3;
//----end----
};
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);
//新增,计算顶点的世界坐标
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xy;
//----end----
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
//新增,判断顶点坐标是否在裁剪框内
bool inArea = i.worldPos.x >= _Area.x && i.worldPos.x <= _Area.z && i.worldPos.y >= _Area.y && i.worldPos.y <= _Area.w;
//----end----
return inArea ? col : fixed4(0,0,0,0);
}
ENDCG
}
}
}
}
所有需要被遮挡的shader都需要被重新编辑。
编辑修改特效shader的脚本:
需要添加获取整体替换特效中shader的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ParticleMask : MonoBehaviour
{
[SerializeField] RectTransform m_rectTrans;//遮挡容器,即ScrollView
List<Material> m_materialList = new List<Material>();//存放需要修改Shader的Material
public bool isStart = false;
public bool isEnable = false;
public bool isDelay = false;
public float delayTime = 0.5f;
void Start()
{
if (!isEnable)
{
if (isDelay)
{
Invoke(nameof(Delay), delayTime);
if (isStart)
{
InitParticleMask(gameObject);
}
}
else
{
InitParticleMask(gameObject);
}
}
}
private void OnEnable()
{
if (isEnable)
{
if (isDelay)
{
Invoke(nameof(Delay), delayTime);
}
else
{
InitParticleMask(gameObject);
}
}
}
private void Delay()
{
InitParticleMask(gameObject);
}
/// <summary>
/// 添加新的item
/// </summary>
/// <param name="obj"></param>
public void AddNewItem(GameObject obj)
{
InitParticleMask(obj);
}
private void InitParticleMask(GameObject parent)
{
//获取所有需要修改shader的material,并替换shader
var particleSystems = parent.GetComponentsInChildren<ParticleSystem>(true);
for (int i = 0, j = particleSystems.Length; i < j; i++)
{
var ps = particleSystems[i];
var mat = ps.GetComponent<Renderer>().material;
if (mat.shader.name.Contains("Mask"))
continue;
if (!m_materialList.Contains(mat))
m_materialList.Add(mat);
if (!mat.shader.name.Contains("Mask"))
mat.shader = Shader.Find(mat.shader.name + "Mask");
TrailRenderer tRender = ps.GetComponent<TrailRenderer>();
if (tRender != null)
{
var tmat = tRender.material;
if (tmat != null)
{
if (!m_materialList.Contains(mat))
m_materialList.Add(mat);
if (!mat.shader.name.Contains("Mask"))
mat.shader = Shader.Find(mat.shader.name + "Mask");
}
}
}
var renders = parent.GetComponentsInChildren<MeshRenderer>(true);
for (int i = 0, j = renders.Length; i < j; i++)
{
var ps = renders[i];
var mat = ps.material;
if(!m_materialList.Contains(mat))
m_materialList.Add(mat);
if (!mat.shader.name.Contains("Mask"))
mat.shader = Shader.Find(mat.shader.name + "Mask");
}
var canvasMasks = parent.GetComponentsInChildren<CanvasMask>(true);
for (int i = 0, j = canvasMasks.Length; i < j; i++)
{
var ps = canvasMasks[i];
var mat = ps.mat;
if (mat == null)
{
ps.Init();
}
if (mat == null)
continue;
if (!m_materialList.Contains(mat))
m_materialList.Add(mat);
if (!mat.shader.name.Contains("Mask"))
mat.shader = Shader.Find(mat.shader.name + "Mask");
}
Vector3[] corners = new Vector3[4];
m_rectTrans.GetWorldCorners(corners);
Vector4 area = new Vector4(corners[0].x, corners[0].y, corners[2].x, corners[2].y);
for (int i = 0, len = m_materialList.Count; i < len; i++)
{
m_materialList[i].SetVector("_Area", area);
}
}
}
将这个脚本挂载在 需要遮挡的特效的父物体上
我这里将他挂在同时遮挡ui的UI上。它会将自己的世界位置的四个顶点传入shader中。
这样就可以实现同时遮挡UI和遮挡3d物体的功能了。