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

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

前言

背景

在项目工作中,新手引导总是由于场景不同用到不同形状的遮罩 ✌,而每个新手引导不能制作单独的遮罩来实现,同时网上的相关资源也没有适合的实例 😳 ,因此写了这个 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 回帖 • 134 关注
  • shader
    4 引用
1 操作
LiuYanPeng 在 2023-03-16 09:58:37 更新了该帖

相关帖子

欢迎来到这里!

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

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