Unity3D Shader 一文教会你使用不同形状的引导遮罩, 支持圆形、矩形、圆角矩形框

本贴最后更新于 400 天前,其中的信息可能已经斗转星移

前言

背景

在项目工作中,新手引导总是由于场景不同用到不同形状的遮罩 ✌,而每个新手引导不能制作单独的遮罩来实现,同时网上的相关资源也没有适合的实例 😳 ,因此写了这个 shader,一文解决类似问题。

概要

🎉 unity3D 新手引导遮罩,支持圆形,矩形框,圆角矩形框。图形位置和大小可以根据控件的位置和大小调节,通用所有分辨率设备。黄色区域遮挡,只有白色区域可以点穿。

👍 内容如下:

  • 实现不同形状遮罩包括:圆形、双圆形、矩形、圆角矩形
  • 提供漏洞点击实现代码及使用方法
  • 给出 MyGuideMask 源码及 GuideMask.Shader 源码

下面,进入正题~😋

一、基本图形

1、圆形

实现代码:

/// <summary> 
/// 创建圆形点击区域 
/// </summary> 
/// <param name="pos">矩形中心点坐标</param> 
/// <param name="widthAndHeight">矩形宽高</param>  
public void CreateCircleMask(Vector3 pos, float rad, Vector3 pos1, float rad1) 
{
     _materia.SetFloat("_MaskType", 0f);
     _materia.SetVector("_Origin", new Vector4(pos.x, pos.y, rad, 0));
     _materia.SetVector("_TopOri", new Vector4(0,0, 0, 0)); 
}

效果如下:

2、双圆形

实现代码:

/// <summary> 
/// 创建双圆形点击区域 
/// </summary> 
/// <param name="pos">大圆形中心点坐标</param> 
/// <param name="rad">大圆形半径</param> 
/// <param name="pos1">小圆形中心点坐标</param> 
/// <param name="rad1">小圆形半径</param>  
public void CreateCircleMask(Vector3 pos, float rad, Vector3 pos1, float rad1) 
{   
     _materia.SetFloat("_MaskType", 0f);
     _materia.SetVector("_Origin", new Vector4(pos.x, pos.y, rad, 0));
     _materia.SetVector("_TopOri", new Vector4(pos1.x, pos1.y, rad1, 0)); 
}

效果如下:

3、矩形

实现代码:

/// <summary> 
/// 创建矩形点击区域 
/// </summary> 
/// <param name="pos">矩形中心点坐标</param> 
/// <param name="widthAndHeight">矩形宽高</param> 
public void CreateRectangleMask(Vector2 pos, Vector2 widthAndHeight, float raid) 
{
     _materia.SetFloat("_MaskType", 1f);
     _materia.SetVector("_Origin", new Vector4(pos.x, pos.y, widthAndHeight.x, widthAndHeight.y));
}

效果如下:

4、圆角矩形

实现代码:

/// <summary> 
/// 创建圆角矩形点击区域 
/// 水平圆角矩形_MaskType为2,垂直为3 
/// </summary> 
/// <param name="pos">矩形中心点坐标</param> 
/// <param name="widthAndHeight">矩形宽高</param> 
/// <param name="raid">圆角大小</param> 
public void CreateCircleRectangleMask(Vector2 pos, Vector2 widthAndHeight,float raid) 
{
     _materia.SetFloat("_MaskType", 2f);
     _materia.SetVector("_Origin", new Vector4(pos.x, pos.y, widthAndHeight.x, widthAndHeight.y));
     _materia.SetFloat("_Raid", raid); 
}

效果如下:

通过参数设置可实现不同的圆角效果,如下:

二、漏洞点击实现

1、实现代码

/// <summary> 
/// 设置目标不被Mask遮挡 
/// </summary> 
/// <param name="tg">目标</param> 
public void SetTargetImage(GameObject tg) 
{
     target = tg; 
} 
/// <summary> 
/// 需要继承ICanvasRaycastFilter接口 
/// </summary> 
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) 
{
     //没有目标则捕捉事件渗透
     if (target == null)
     {
         return true;
     }
     //在目标范围内做事件渗透
     return !RectTransformUtility.RectangleContainsScreenPoint(
     target.GetComponent<RectTransform>(),sp, eventCamera); 
}

2、使用方法

✌ 将 MyGuideMask 挂载到脚本上,然后通过 GuideMask 创建材质并赋值,根据需要调用图形对应方法。

❗ 注意:如果需点击漏洞下的组件,需将其赋值给 Target。

三、源码

1、MyGuideMask 源码

using UnityEngine;
using UnityEngine.UI;

public class MyGuideMask : MonoBehaviour, ICanvasRaycastFilter
{
    public Image _Mask; //遮罩图片
    private Material _materia;
    private GameObject target;

    private void Awake()
    {
        _materia = _Mask.material;
    }
  
    /// <summary>
    /// 创建圆角矩形区域
    /// </summary>
    /// <param name="pos">矩形的屏幕位置</param>
    /// <param name="pos1">左下角位置</param>
    /// <param name="pos2">右上角位置</param>
    /// <param name="CallBack">回调</param>
    public void CreateCircleRectangleMask(Vector2 pos, Vector2 widthAndHeight, float raid)
    {
        _materia.SetFloat("_MaskType", 2f);
        _materia.SetVector("_Origin", new Vector4(pos.x, pos.y, widthAndHeight.x, widthAndHeight.y));
        _materia.SetFloat("_Raid", raid);
    }
  
    /// <summary>
    /// 创建矩形点击区域
    /// </summary>
    /// <param name="pos">矩形中心点坐标</param>
    /// <param name="widthAndHeight">矩形宽高</param>
    public void CreateRectangleMask(Vector2 pos, Vector2 widthAndHeight, float raid)
    {
        _materia.SetFloat("_MaskType", 1f);
        _materia.SetVector("_Origin", new Vector4(pos.x, pos.y, widthAndHeight.x, widthAndHeight.y));
    }
  
    /// <summary>
    /// 创建双圆形点击区域
    /// </summary>
    /// <param name="pos">大圆形中心点坐标</param>
    /// <param name="rad">大圆形半径</param>
    /// <param name="pos1">小圆形中心点坐标</param>
    /// <param name="rad1">小圆形半径</param>
    public void CreateCircleMask(Vector3 pos, float rad, Vector3 pos1, float rad1)
    {
        _materia.SetFloat("_MaskType", 0f);
        _materia.SetVector("_Origin", new Vector4(pos.x, pos.y, rad, 0));
        _materia.SetVector("_TopOri", new Vector4(pos1.x, pos1.y, rad1, 0));
    }

    /// <summary>
    /// 设置目标不被Mask遮挡
    /// </summary>
    /// <param name="tg">目标</param>
    public void SetTargetImage(GameObject tg)
    {
        target = tg;
    }
  
    public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
    {
        //没有目标则捕捉事件渗透
        if (target == null)
        {
            return true;
        }

        //在目标范围内做事件渗透
        return !RectTransformUtility.RectangleContainsScreenPoint(target.GetComponent<RectTransform>(),
            sp, eventCamera);
    }
}

2、GuideMask.Shader 源码

Shader "UI/GuideMask"
{
	Properties{
	    [PerRendererData] _MainTex("Sprite Texture", 2D)="white"{}
         _Color("Tint",Color)=(1,1,1,1)

        _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

		_Origin("Rect",Vector) = (0,0,0,0)
		_TopOri("TopCircle",Vector) = (0,0,0,0)
		_Raid("RectRaid",Range(0,100)) = 0
		_MaskType("Type",Float) = 0

    }
	SubShader{
		Tags{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
			"PreviewType" = "Plane"
			"CanUseSpriteAtlas" = "True"
        }
		Stencil{
			Ref[_Stencil]
			Comp[_StencilComp]
			Pass[_StencilOp]
			ReadMask[_StencilReadMask]
			WriteMask[_StencilWriteMask]
		}

		Cull Off
		Lighting Off
		ZWrite Off
		ZTest[unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha
		ColorMask[_ColorMask]

		Pass{
			Name "Default"
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"
			#include "UnityUI.cginc"

			struct appdata_t
			{
				float4 vertex : POSITION;
				float4 color : COLOR;
				float2 texcoord : TEXCOORD0;
            };

			struct v2f{
				float4 vertex:SV_POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
            };

			sampler2D _MainTex;
			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;
			float4 _Origin;
			float4 _TopOri;
			float _Raid;
			float _MaskType;
			//0 圆形 1 矩形 2 圆角矩形 

			v2f vert(appdata_t IN){
				v2f OUT;
				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
				OUT.texcoord = IN.texcoord;
				OUT.color = IN.color * _Color;
				return OUT;
			}
			//垂直圆角矩形
			fixed checkInCircleRectVectory (float4 worldPosition) {
				float4 rec1Pos=float4(_Origin.x-_Origin.z/2,_Origin.y-_Origin.w/2-_Raid,_Origin.x+_Origin.z/2,_Origin.y+_Origin.w/2+_Raid);
				float4 rec2Pos=float4(_Origin.x-_Origin.z/2+_Raid,_Origin.y-_Origin.w/2-2*_Raid,_Origin.x+_Origin.z/2-_Raid,_Origin.y+_Origin.w/2+2*_Raid);
				fixed2 step1=step(rec1Pos.xy, worldPosition.xy) * step(worldPosition.xy, rec1Pos.zw);
				fixed2 step2=step(rec2Pos.xy, worldPosition.xy) * step(worldPosition.xy, rec2Pos.zw);
				fixed rec1=step1.x*step1.y<1?0:1;
				fixed rec2=step2.x*step2.y<1?0:1;
				fixed dis1=distance(float2(_Origin.x+_Origin.z/2-_Raid,_Origin.y+_Origin.w/2+_Raid),worldPosition.xy)<_Raid?1:0;
				fixed dis2=distance(float2(_Origin.x-_Origin.z/2+_Raid,_Origin.y-_Origin.w/2-_Raid),worldPosition.xy)<_Raid?1:0;
				fixed dis3=distance(float2(_Origin.x+_Origin.z/2-_Raid,_Origin.y-_Origin.w/2-_Raid),worldPosition.xy)<_Raid?1:0;
				fixed dis4=distance(float2(_Origin.x-_Origin.z/2+_Raid,_Origin.y+_Origin.w/2+_Raid),worldPosition.xy)<_Raid?1:0;
				return (dis1+dis2+dis3+dis4+rec1+rec2)>0?0:1;
			}

			//水平圆角矩形
			fixed checkInCircleRectHorizontal (float4 worldPosition) {

				float4 rec1Pos=float4(_Origin.x-_Origin.z/2-_Raid,_Origin.y-_Origin.w/2,_Origin.x+_Origin.z/2+_Raid,_Origin.y+_Origin.w/2);
				float4 rec2Pos=float4(_Origin.x-_Origin.z/2-2*_Raid,_Origin.y-_Origin.w/2+_Raid,_Origin.x+_Origin.z/2+2*_Raid,_Origin.y+_Origin.w/2-_Raid);
				fixed2 step1=step(rec1Pos.xy, worldPosition.xy) * step(worldPosition.xy, rec1Pos.zw);
				fixed2 step2=step(rec2Pos.xy, worldPosition.xy) * step(worldPosition.xy, rec2Pos.zw);
				fixed rec1=step1.x*step1.y<1?0:1;
				fixed rec2=step2.x*step2.y<1?0:1;
				fixed dis1=distance(float2(_Origin.x-_Origin.z/2-_Raid,_Origin.y+_Origin.w/2-_Raid),worldPosition.xy)<_Raid?1:0;
				fixed dis2=distance(float2(_Origin.x-_Origin.z/2-_Raid,_Origin.y-_Origin.w/2+_Raid),worldPosition.xy)<_Raid?1:0;
				fixed dis3=distance(float2(_Origin.x+_Origin.z/2+_Raid,_Origin.y+_Origin.w/2-_Raid),worldPosition.xy)<_Raid?1:0;
				fixed dis4=distance(float2(_Origin.x+_Origin.z/2+_Raid,_Origin.y-_Origin.w/2+_Raid),worldPosition.xy)<_Raid?1:0;
				return (dis1+dis2+dis3+dis4+rec1+rec2)>0?0:1;
			}
			//圆形/双圆形
			fixed checkInCircle (float4 worldPosition) {
				fixed dis1=distance(worldPosition, _Origin.xy)< _Origin.z?1:0;
				fixed dis2=distance(worldPosition.xy, _TopOri.xy)< _TopOri.z?1:0;
				return (dis1+dis2)>0?0:1;
			}
			//矩形
			fixed checkInRect (float4 worldPosition) {
				float4 temp=float4(_Origin.x-_Origin.z/2,_Origin.y-_Origin.w/2,_Origin.x+_Origin.z/2,_Origin.y+_Origin.w/2);
				float2 inside = step(temp.xy, worldPosition.xy) * step(worldPosition.xy, temp.zw);
				return inside.x*inside.y>0?0:1;

			}
			fixed4 frag(v2f IN) : SV_Target{
				half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
				if(_MaskType==0){
					color.a=checkInCircle(IN.worldPosition)==0?0:color.a;
                }else if(_MaskType==1){
					color.a=checkInRect(IN.worldPosition)==0?0:color.a;
                }else if(_MaskType==3){
					color.a=checkInCircleRectVectory(IN.worldPosition)==0?0:color.a;
                }
				else if(_MaskType==2){
					color.a=checkInCircleRectHorizontal(IN.worldPosition)==0?0:color.a;
                }
				return color;
			}



			ENDCG
        }
    }
}
  • Unity

    Unity 是由 Unity Technologies 开发的一个让开发者可以轻松创建诸如 2D、3D 多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

    25 引用 • 7 回帖 • 250 关注
  • shader
    4 引用
1 操作
LiuYanPeng 在 2023-03-16 09:58:37 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...