unity反向椭圆遮罩shader

策划要求做一个新手引导的遮罩,之前一直没做过新手引导,记得之前游戏都是方框的可以点击区域,其他地方鼠标不能穿透点击。网上找了一下还没找到相关现成的资源,花了一些功夫研究了下。

思路如下:
遮挡点击可以利用Image组件的RaycastTarget.只有透明部分让鼠标可以穿透,就可以复写Image的IsRaycastLocationValid方法.计算是否点击在椭圆内。

制作如下:
因为遮罩是椭圆或者圆形的可点击区域。随便找一个绘制椭圆的shader,改一改
改的一个从椭圆遮罩变为反向椭圆遮罩,椭圆部分alpha为0,可以实现新手引导使用,增加了一点边缘alpha过渡,看起来没毛刺。
_LengthRate用来调整椭圆大小.
_LengthRateTransition是我增加用来增加一点alpha过渡,这样显示出来的圆没毛刺。不过偷懒了,椭圆远的地方和近的地方过渡长度不一样,后面可以优化下。

//对图片的mask处理,只有椭圆部分
Shader "Unlit/Transparent Colored Mask UnRound"
{
	Properties
	{
		_MainTex("Base (RGB), Alpha (A)", 2D) = "black" {}
		_CenterX("CenterX", float) = 0.5
		_CenterY("CenterY", float) = 0.5
		_Width("Width", float) = 0.5
		_Height("Height", float) = 0.15
		_LengthRate("LengthRate", float) = 0.1 
		_LengthRateTransition("LengthRateLerp", float) = 0.01
	}

		SubShader
		{
			LOD 200

			Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
		}

			Pass
		{
			Cull Off
			Lighting Off
			ZWrite Off
			Fog{ Mode Off }
			Offset -1,-1
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag            
	#include "UnityCG.cginc"

			sampler2D _MainTex;
		half4 _MainTex_ST;
		half _CenterX;
		half _CenterY;
		half _Width;
		half _Height;
		half _LengthRate;
		half _LengthRateTransition;

		struct appdata_t
		{
			half4 vertex : POSITION;
			half2 texcoord : TEXCOORD0;
			fixed4 color : COLOR;
		};

		struct v2f
		{
			half4 vertex : SV_POSITION;
			half2 texcoord : TEXCOORD0;
			fixed4 color : COLOR;
		};

		v2f o;

		v2f vert(appdata_t v)
		{
			o.vertex = UnityObjectToClipPos(v.vertex);
			o.texcoord = v.texcoord;
			o.color = v.color;
			return o;
		}

		fixed4 frag(v2f IN) : COLOR
		{
			half2 pt = IN.texcoord - half2(_CenterX,(1 - _CenterY));
			half h = (pt.x*pt.x) / (_Width*_Width) + (pt.y*pt.y) / (_Height*_Height);

			half4  c = tex2D(_MainTex, IN.texcoord) * IN.color;
			half sub = _LengthRate - h;
			if (h < _LengthRate && sub < _LengthRateTransition)
			{
				c.a = c.a - sub / _LengthRateTransition ;
				return c;
			}
			else if (h < _LengthRate)
			{
				discard;
			}

			return c;
		}
			ENDCG
		}
		}

			SubShader
		{
			LOD 100

			Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
		}

			Pass
		{
			Cull Off
			Lighting Off
			ZWrite Off
			Fog{ Mode Off }
			Offset -1,-1
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha
			ColorMaterial AmbientAndDiffuse

			SetTexture[_MainTex]
		{
			Combine Texture * Primary
		}
		}
		}
}

配合Image组件使用,令透明区域可以穿透点击,其他地方阻挡。修改IsRaycastLocationValid方法,计算鼠标点击位置。

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

public class CGuideImage : Image
{
    [SerializeField]
    protected Vector2 _Center = new Vector2(0.5f, 0.5f);

    [SerializeField]
    protected Vector2 _Size = new Vector2(0.5f, 0.15f);

    [SerializeField]
    protected float radius = 0.1f;

    public bool LocationCanClick = false;

    //BoxCollider2D coll;
    //protected override void Start()
    //{
    //    base.Start();
    //    coll = gameObject.GetComponent();
    //}

    public void changePlace(Vector2 pos, Vector2 sz, float r)
    {
        _Center = pos;
        _Size = sz;
        radius = r;

        material.SetFloat("_CenterX", pos.x);
        material.SetFloat("_CenterY", pos.y);
        material.SetFloat("_Width", sz.x);
        material.SetFloat("_Height", sz.y);
        material.SetFloat("_LengthRate", r);
    }

    Vector2 local;
    //是否是有效点击位置,返回true表示不能穿透.
    public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
    {
        if (alphaHitTestMinimumThreshold <= 0)
        {
            LocationCanClick = false;
            return true;
        }

        if (alphaHitTestMinimumThreshold > 1)
        {
            LocationCanClick = true;
            return false;
        }



        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local))
        {
            LocationCanClick = true;
            return false;
        }

        //Debug.Log("local" + local.ToString());

        Rect rect = GetPixelAdjustedRect();

        // Convert to have lower left corner as reference point.
        local.x += rectTransform.pivot.x * rect.width;
        local.y += rectTransform.pivot.y * rect.height;

        //Debug.Log("local2" + local.ToString());
        Vector2 at = new Vector2(local.x / rectTransform.sizeDelta.x, 1f - local.y / rectTransform.sizeDelta.y);
        Vector2 pt = at - _Center;
        float h = (pt.x * pt.x) / (_Size.x * _Size.x) + (pt.y * pt.y) / (_Size.y * _Size.y);

        //Debug.Log("to:" + at.x.ToString() + "," + at.y.ToString() + " h :" + h.ToString());

        if (h < radius)
        {
            LocationCanClick = true;
            return false;
        }
        LocationCanClick = false;
        return true;
    }
}

这样就实现了鼠标穿透了,如果用了Input的鼠标点击方法,还可以取LocationCanClick变量来判断是否在区域内。

unity反向椭圆遮罩shader_第1张图片

材质:
unity反向椭圆遮罩shader_第2张图片

IsRaycastLocationValid方法当时想法是获取alpha来判断是否椭圆区域,但是发现根本没办法拿到,因为走的材质球的,渲染都在显卡,除非是用相机加一个RenderTexture来获取再给Image的图片附加,感觉复杂了,没有进一步研究。

方法有点麻烦,欢迎探讨,相互交流。

你可能感兴趣的:(C#,Unity,Shader,guide,newbie,mask)