【备忘】Unity 单界面UI多个挖洞实现(shader+mask)

引用:1:UGUIshader扣洞_那个妹子留步的博客-CSDN博客

           
制作新手引导时,遇到需要在界面显示多个挖洞的情况。
 在翻阅一些博客之后,找到一种实现方法。具体思路是:用shader实现多个挖洞,主挖洞的位置上覆盖一个自己实现,继承于Mask组件的用于点击穿透的组件。

多个挖洞的shader:

Shader "Custom/Holeshader"
{
	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
 
		//add
		// _Center("Center", vector) = (0, 0, 0, 0)//单个扣洞 面板赋值
		_Silder ("_Silder", Range (0,1000)) = 1000 // sliders
		//add
 
 
		[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", 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"
 
			#pragma multi_compile __ UNITY_UI_ALPHACLIP
			
			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};
 
			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color    : COLOR;
				float2 texcoord  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
				UNITY_VERTEX_OUTPUT_STEREO
			};
			
			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;
			float _Silder;
            // float2 _Center;//单个
			uniform float2 _PointsArr[10];
            uniform uint _PointCount;
 
			v2f vert(appdata_t IN)
			{
				v2f OUT;
				UNITY_SETUP_INSTANCE_ID(IN);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
 
				OUT.texcoord = IN.texcoord;
				
				OUT.color = IN.color * _Color;
				return OUT;
			}
 
			sampler2D _MainTex;
 
			fixed4 frag(v2f IN) : SV_Target
			{
				half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
				
				color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
				
				#ifdef UNITY_UI_ALPHACLIP
				clip (color.a - 0.001);
				#endif
 
				//-------------------add----------------------
				//循环扣洞
				[loop]
				for(uint index;index<_PointCount;index++){
					color.a*=(distance(IN.worldPosition.xy,_PointsArr[index].xy) > _Silder);
					//1像素渐变透明 以免洞边缘尖锐
					float d = distance(IN.worldPosition.xy,_PointsArr[index].xy);
					if( d > _Silder && d < (_Silder+1)){
						color.a *= (d-_Silder);
					}
				}
				
				//单个扣洞
               	// color.a*=(distance(IN.worldPosition.xy,_Center.xy) > _Silder);
				// //1像素渐变透明 以免洞边缘尖锐
				// float d = distance(IN.worldPosition.xy,_Center.xy);
				// if( d > _Silder && d < (_Silder+1)){
				// 	color.a *= (d-_Silder);
				// }
 
               	color.rgb*= color.a;
               	//-------------------add----------------------
 
 
				return color;
			}
		ENDCG
		}
	}
}

继承于Mask的HoleMask:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


    public class HoleMask : Mask
    {
        float alpahThreshold = 0.3f;
        public override bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
        {
            if (!isActiveAndEnabled)
            {
                return true;
            }
            //当没有指定精灵时返回true,因为不指定Spirte的时候,Unity将其区域填充为默认的白色,全部区域都是可以响应点击的
            Image _image = this.GetComponent<Image>();
            Sprite _sprite = this.GetComponent<Image>().sprite;
            if (_sprite == null || _image == null)
            {
                return true;
            }

            //坐标系转换 
            Vector2 localPositionPivotRelative;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, sp, eventCamera, out localPositionPivotRelative);
            Rect pixelAdjustedRect = this.GetComponent<Image>().GetPixelAdjustedRect();
            // 转换为以屏幕左下角为原点的坐标系
            var localPosition = new Vector2(localPositionPivotRelative.x + rectTransform.pivot.x * rectTransform.rect.width,
                localPositionPivotRelative.y + rectTransform.pivot.y * rectTransform.rect.height);

            var spriteRect = _sprite.textureRect;
            var maskRect = rectTransform.rect;

            var x = 0;
            var y = 0;
            // 转换为纹理空间坐标
            switch (_image.type)
            {

                case Image.Type.Sliced:
                    {
                        var border = _sprite.border;
                        // x 轴裁剪
                        if (localPosition.x < border.x)
                        {
                            x = Mathf.FloorToInt(spriteRect.x + localPosition.x);
                        }
                        else if (localPosition.x > maskRect.width - border.z)
                        {
                            x = Mathf.FloorToInt(spriteRect.x + spriteRect.width - (maskRect.width - localPosition.x));
                        }
                        else
                        {
                            x = Mathf.FloorToInt(spriteRect.x + border.x +
                                                 ((localPosition.x - border.x) /
                                                 (maskRect.width - border.x - border.z)) *
                                                 (spriteRect.width - border.x - border.z));
                        }
                        // y 轴裁剪
                        if (localPosition.y < border.y)
                        {
                            y = Mathf.FloorToInt(spriteRect.y + localPosition.y);
                        }
                        else if (localPosition.y > maskRect.height - border.w)
                        {
                            y = Mathf.FloorToInt(spriteRect.y + spriteRect.height - (maskRect.height - localPosition.y));
                        }
                        else
                        {
                            y = Mathf.FloorToInt(spriteRect.y + border.y +
                                                 ((localPosition.y - border.y) /
                                                 (maskRect.height - border.y - border.w)) *
                                                 (spriteRect.height - border.y - border.w));
                        }
                    }
                    break;
                case Image.Type.Simple:
                default:
                    {
                        // 转换为统一UV空间
                        x = Mathf.FloorToInt(spriteRect.x + spriteRect.width * localPosition.x / maskRect.width);
                        y = Mathf.FloorToInt(spriteRect.y + spriteRect.height * localPosition.y / maskRect.height);
                    }
                    break;
            }

            // 如果texture导入过程报错,则删除组件
            try
            {
                //if (_sprite.texture.GetPixel(x, y).a < alpahThreshold)
                //{
                //    Debug.Log("click work");
                //}
                return _sprite.texture.GetPixel(x, y).a < alpahThreshold;
            }
            catch (UnityException e)
            {
                Debug.LogError("Mask texture not readable, set your sprite to Texture Type 'Advanced' and check 'Read/Write Enabled'" + e.Message);
                return !RectTransformUtility.RectangleContainsScreenPoint(rectTransform, sp, eventCamera);
            }


        }
    }

界面摆放:

mask物体上挂载带shader的材质:

 obj物体上挂载HoleMask组件(Image图形就是个圆形):

 代码调用:


void Main()
{
    int len = 4;
    Vector4[] vec4Points = new Vector4[len];//传给shader的值

    vec4Points[0] = new Vector4(200, -300, 0, 0);
    vec4Points[1] = new Vector4(100, 200, 0, 0);
    vec4Points[2] = new Vector4(-200, 300, 0, 0);
    vec4Points[3] = new Vector4(-100, -200, 0, 0);
    maskImg.material.SetVectorArray("_PointsArr", vec4Points);
    maskImg.material.SetInt("_PointCount", vec4Points.Length);
}

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Unity中,要实现多个UI界面中放置模型,可以按照以下步骤进行操作: 1. 创建所需的UI界面:首先,需要创建多个UI界面,可以使用Unity提供的UI系统(如Canvas、Panel等)或自定义的UI界面组件。确保每个UI界面都有一个独立的Canvas,并将其设置为Overlay(覆盖)模式,以确保UI界面可以叠加显示。 2. 添加3D模型:在每个UI界面上,需要添加一个用于放置模型的容器。可以使用Unity中的空物体(Empty GameObject)来充当容器。选择所需的UI界面,然后在场景中创建一个空物体,并将其作为该UI界面的子物体。 3. 放置模型:在每个容器中,通过添加3D模型来实现模型的放置。可以将模型资源拖拽到对应的容器中,或使用代码动态加载模型。确保所添加的模型与容器处于同一个局部坐标系下,以确保正确的位置和旋转。 4. 控制模型显示:通过在UI界面的相关脚本中编写逻辑,来控制模型的显示。例如,在UI界面的脚本中,可以添加逻辑来根据用户的操作,切换不同的UI界面以及对应的模型显示。 需要注意的是,为了在UI界面中放置模型,需要确保所使用的UI界面和3D模型的渲染方式兼容。例如,可以使用屏幕空间渲染(Screen Space)的UI界面和正常的3D模型渲染方式。同时,还要确保模型与UI界面之间的相对位置和大小适配,以确保在不同的分辨率和设备上都能正常显示。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值