User:Baozhenliang/GraphicMask

From Wikibooks, open books for an open world
Jump to navigation Jump to search

GraphicMask是一个UGUI绘制遮罩的一个组件,一开始是做引导的遮罩时想着怎么做能更高效点,后面做着做着发现引导只是一个遮罩绘制的一个应用,所以就将GraphicMask抽象成一个组件了。

1.UGUIExtend.cs 是UGUI操作的一个静态扩展。

2.Geometry2DUtility.cs 是几何计算工具。

3.Polygon 是多边相关的类。

4.UIGraphicMaskShader.cs 是以shader方式绘制的组件。

5.UIGraphicMaskVertex.cs 是以Vertex方式绘制的组件。

6.方法AddGraphicMask和RemoveGraphicMask是主要的操作函数。

UGUIExtend.cs

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// <summary>
/// UGUI扩展
/// </summary>
public static class UGUIExtend
{
    #region 固定位置在指定画布区域
    /// <summary>
    /// 固定位置在指定画布区域
    /// </summary>
    /// <param name="_source">源位置</param>
    /// <param name="_canvas">画布</param>
    public static void FixedPositionInCanvas(this RectTransform _source, RectTransform _canvas)
    {
        Rect mRect = new Rect(Vector2.zero,_canvas.sizeDelta);
        _source.FixedPositionInCanvas(_canvas, mRect);
    }

    /// <summary>
    /// 固定位置在指定画布区域内
    /// </summary>
    /// <param name="_source">源位置</param>
    /// <param name="_canvas">画布</param>
    /// <param name="_rect">限制区域(画布左下角为坐标原点(0,0))</param>
    public static void FixedPositionInCanvas(this RectTransform _source, RectTransform _canvas, Rect _rect)
    {
        Bounds bounds = RectTransformUtility.CalculateRelativeRectTransformBounds(_canvas, _source);        
        Vector2 delta = Vector2.zero;
        Vector3 tempCenter = bounds.center;
        tempCenter.x += _canvas.sizeDelta.x * 0.5f;
        tempCenter.y += _canvas.sizeDelta.y * 0.5f;
        bounds.center = tempCenter;
        if (bounds.center.x - bounds.extents.x < _rect.x)//target超出area的左边框
        {
            delta.x += Mathf.Abs(bounds.center.x - bounds.extents.x - _rect.x);
        }
        else if (bounds.center.x + bounds.extents.x > _rect.width)//target超出area的右边框
        {
            delta.x -= Mathf.Abs(bounds.center.x + bounds.extents.x - _rect.width);
        }

        if (bounds.center.y - bounds.extents.y < _rect.y)//target超出area上边框
        {
            delta.y += Mathf.Abs(bounds.center.y - bounds.extents.y - _rect.y);
        }
        else if (bounds.center.y + bounds.extents.y > _rect.height)//target超出area的下边框
        {
            delta.y -= Mathf.Abs(bounds.center.y + bounds.extents.y - _rect.height);
        }
        //加上偏移位置算出在屏幕内的坐标
        _source.anchoredPosition += delta;
    }
    #endregion

    #region UI坐标转换
    /// <summary>
    /// 世界物体转换为UI坐标
    /// </summary>
    /// <param name="_worldGo">世界物体</param>
    /// <param name="_worldCamera">世界摄像机</param>
    /// <param name="_uiCanvas">UI画布</param>
    /// <param name="_uiCamera">UI摄像机</param>
    /// <returns>UI坐标</returns>
    public static Vector2 WorldToLocalPointInRectangle(this Transform _worldGo, Camera _worldCamera, Canvas _uiCanvas, Camera _uiCamera)
    {
        Vector2 mUIPoint = RectTransformUtility.WorldToScreenPoint(_worldCamera, _worldGo.position);
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            (RectTransform)_uiCanvas.transform, mUIPoint, _uiCamera, out mUIPoint);
        return mUIPoint;
    }

    /// <summary>
    /// 世界物体转换为UI坐标
    /// </summary>
    /// <param name="_worldPosition">世界坐标</param>
    /// <param name="_worldCamera">世界摄像机</param>
    /// <param name="_uiCanvas">UI画布</param>
    /// <param name="_uiCamera">UI摄像机</param>
    /// <returns>UI坐标</returns>
    public static Vector2 WorldToLocalPointInRectangle(this Vector3 _worldPosition, Camera _worldCamera, Canvas _uiCanvas, Camera _uiCamera)
    {
        Vector2 mUIPoint = RectTransformUtility.WorldToScreenPoint(_worldCamera, _worldPosition);
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            (RectTransform)_uiCanvas.transform, mUIPoint, _uiCamera, out mUIPoint);
        return mUIPoint;
    }
    #endregion

    #region Graphic在VertexHelper的本地四角坐标
    /// <summary>
    /// Graphic在VertexHelper的本地四角坐标
    /// </summary>
    /// <param name="_graphic">Graphic</param>
    /// <param name="_corners">四角坐标</param>
    public static void GraphicLocalCornersForVertexHelper(this Graphic _graphic, out Vector3[] _corners)
    {
        _corners = new Vector3[4];
        _graphic.rectTransform.GetWorldCorners(_corners);
        for (int i = 0; i < _corners.Length; i++)
        {
            Vector2 mUIPoint = RectTransformUtility.WorldToScreenPoint(_graphic.canvas.worldCamera, _corners[i]);
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                (RectTransform)_graphic.canvas.transform, mUIPoint, _graphic.canvas.worldCamera, out mUIPoint);
            _corners[i] = mUIPoint;
        }
    }
    #endregion

    #region Graphic在VertexHelper的本地矩形
    /// <summary>
    /// Graphic在VertexHelper的本地矩形
    /// </summary>
    /// <param name="_graphic">Graphic</param>
    /// <returns>矩形</returns>
    public static Rect GraphicLocalRectForVertexHelper(this Graphic _graphic)
    {
        Vector3[] fourCorners = null;
        return GraphicLocalRectForVertexHelper(_graphic,ref fourCorners);
    }
    #endregion

    #region Graphic在VertexHelper的本地矩形
    /// <summary>
    /// Graphic在VertexHelper的本地矩形
    /// </summary>
    /// <param name="_graphic">Graphic</param>
    /// <param name="_corners">矩形四角</param>
    /// <returns>矩形</returns>
    public static Rect GraphicLocalRectForVertexHelper(this Graphic _graphic, ref Vector3[] _corners)
    {
        GraphicLocalCornersForVertexHelper(_graphic, out _corners);
        Rect rect = new Rect();
        rect.xMin = rect.yMin = float.MaxValue;
        rect.xMax = rect.yMax = float.MinValue;
        foreach (Vector3 n in _corners)
        {
            rect.xMin = Mathf.Min(rect.xMin, n.x);
            rect.yMin = Mathf.Min(rect.yMin, n.y);
            rect.xMax = Mathf.Max(rect.xMax, n.x);
            rect.yMax = Mathf.Max(rect.yMax, n.y);
        }
        return rect;
    }
    #endregion

    #region 转换矩形为Bounds
    /// <summary>
    /// 转换矩形为Bounds
    /// </summary>
    /// <param name="_rect">矩形</param>
    /// <returns>矩形</returns>
    public static Bounds TransRectToBounds(this Rect _rect)
    {
        Bounds b = new Bounds();
        b.center = _rect.center;
        b.max = _rect.max;
        b.min = _rect.min;
        return b;
    }
    #endregion

    #region RectTransform 设置为全伸展
    /// <summary>
    /// RectTransform设置为全伸展
    /// </summary>
    /// <param name="_trans">转换</param>
    public static void IdentityStreech(this RectTransform _trans)
    {
        _trans.anchorMax = Vector2.one;
        _trans.anchorMin = Vector2.zero;
        _trans.pivot = Vector2.one * 0.5f;
        _trans.localRotation = Quaternion.identity;
        _trans.localScale = Vector3.one;
        _trans.anchoredPosition3D = Vector3.zero;
        _trans.anchoredPosition = Vector2.zero;
        _trans.sizeDelta = Vector2.zero;
    }
    #endregion

    #region AddListener 添加监听事件
    /// <summary>
    /// 添加监听事件
    /// </summary>
    /// <param name="_trigger">触发器</param>
    /// <param name="_eventTriggerType">事件类型</param>
    /// <param name="_callback">回调</param>
    public static void AddListener<T>(this EventTrigger _trigger, 
        EventTriggerType _eventTriggerType,
        UnityAction<T> _callback)
        where T: BaseEventData
    {
        UnityAction<BaseEventData> call = new UnityAction<BaseEventData>((data) => { _callback((T)data); });
        EventTrigger.Entry entry = new EventTrigger.Entry();
        entry.eventID = _eventTriggerType;
        entry.callback.AddListener(call);
        _trigger.triggers.Add(entry);
    }
    #endregion
}

Geometry2DUtility.cs

using UnityEngine;
/// <summary>
/// 几何学工具
/// </summary>
public sealed class Geometry2DUtility
{
    /// <summary>
    /// 求三角形的外接圆
    /// </summary>
    /// <param name="_p1">顶点1</param>
    /// <param name="_p2">顶点2</param>
    /// <param name="_p3">顶点3</param>
    /// <returns>外接圆圆心</returns>
    public static Vector2 TriangleCircumCircle(Vector3 _p1, Vector3 _p2, Vector3 _p3)
    {
        Vector3 center = Vector3.zero;
        #region 三角形的外接圆
        /*参考文章
         * http://blog.sina.com.cn/s/blog_648868460100h2b8.html
         * http://jingyan.baidu.com/article/636f38bb3caa88d6b846109d.html
         * https://www.zhihu.com/question/40422123?sort=created
         * http://blog.sina.com.cn/s/blog_15ff6002b0102xxxf.html
         */
        float a1, b1, c1, d1;
        float a2, b2, c2, d2;
        float a3, b3, c3, d3;

        float x1 = _p1.x, y1 = _p1.y, z1 = _p1.z;
        float x2 = _p2.x, y2 = _p2.y, z2 = _p2.z;
        float x3 = _p3.x, y3 = _p3.y, z3 = _p3.z;

        a1 = (y1 * z2 - y2 * z1 - y1 * z3 + y3 * z1 + y2 * z3 - y3 * z2);
        b1 = -(x1 * z2 - x2 * z1 - x1 * z3 + x3 * z1 + x2 * z3 - x3 * z2);
        c1 = (x1 * y2 - x2 * y1 - x1 * y3 + x3 * y1 + x2 * y3 - x3 * y2);
        d1 = -(x1 * y2 * z3 - x1 * y3 * z2 - x2 * y1 * z3 + x2 * y3 * z1 + x3 * y1 * z2 - x3 * y2 * z1);

        a2 = 2 * (x2 - x1);
        b2 = 2 * (y2 - y1);
        c2 = 2 * (z2 - z1);
        d2 = x1 * x1 + y1 * y1 + z1 * z1 - x2 * x2 - y2 * y2 - z2 * z2;

        a3 = 2 * (x3 - x1);
        b3 = 2 * (y3 - y1);
        c3 = 2 * (z3 - z1);
        d3 = x1 * x1 + y1 * y1 + z1 * z1 - x3 * x3 - y3 * y3 - z3 * z3;        
        
        center.x = -(b1 * c2 * d3 - b1 * c3 * d2 - b2 * c1 * d3 + b2 * c3 * d1 + b3 * c1 * d2 - b3 * c2 * d1)
            / (a1 * b2 * c3 - a1 * b3 * c2 - a2 * b1 * c3 + a2 * b3 * c1 + a3 * b1 * c2 - a3 * b2 * c1);
        center.y = (a1 * c2 * d3 - a1 * c3 * d2 - a2 * c1 * d3 + a2 * c3 * d1 + a3 * c1 * d2 - a3 * c2 * d1)
            / (a1 * b2 * c3 - a1 * b3 * c2 - a2 * b1 * c3 + a2 * b3 * c1 + a3 * b1 * c2 - a3 * b2 * c1);
        center.z = -(a1 * b2 * d3 - a1 * b3 * d2 - a2 * b1 * d3 + a2 * b3 * d1 + a3 * b1 * d2 - a3 * b2 * d1)
            / (a1 * b2 * c3 - a1 * b3 * c2 - a2 * b1 * c3 + a2 * b3 * c1 + a3 * b1 * c2 - a3 * b2 * c1);
        #endregion
        return center;
    }

    /// <summary>
    /// 边是否相交
    /// </summary>
    /// <param name="_edgeA">边A</param>
    /// <param name="_edgeB">边B</param>
    /// <returns>是否相交</returns>
    public static bool EdgeIntersection(PolygonEdge2D _edgeA, PolygonEdge2D _edgeB)
    {
        Vector3 p1 = _edgeA.vertexA.position;
        Vector3 p2 = _edgeA.vertexB.position;
        Vector3 q1 = _edgeB.vertexA.position;
        Vector3 q2 = _edgeB.vertexB.position;

        Vector3 v1 = Vector3.Cross(p2 - p1, q1 - p1).normalized;
        Vector3 v2 = Vector3.Cross(p2 - p1, q2 - p1).normalized;

        Vector3 v3 = Vector3.Cross(q2 - q1, p1 - q1).normalized;
        Vector3 v4 = Vector3.Cross(q2 - q1, p2 - q1).normalized;
        return Vector3.Dot(v1, v2) < 0 && Vector3.Dot(v3, v4) < 0;
    }

    /// <summary>
    /// 边是否相交
    /// </summary>
    /// <param name="_edgeA">边A</param>
    /// <param name="_edgeB">边B</param>
    /// <param name="_point">交点</param>
    /// <returns>是否相交</returns>
    public static bool EdgeIntersection(PolygonEdge2D _edgeA, PolygonEdge2D _edgeB, ref Vector3 _point)
    {
        bool isCross = EdgeIntersection(_edgeA,_edgeB);
        if (isCross)
        {
            float a1 = _edgeA.vertexA.position.x;
            float b1 = _edgeA.vertexA.position.y;
            float a2 = _edgeA.vertexB.position.x;
            float b2 = _edgeA.vertexB.position.y;
            float c1 = _edgeB.vertexA.position.x;
            float d1 = _edgeB.vertexA.position.y;
            float c2 = _edgeB.vertexB.position.x;
            float d2 = _edgeB.vertexB.position.y;
            _point = Vector3.zero;
            _point.x = ((a2 - a1) * (c2 - c1) * (d2 - b2) + (b2 - b1) * (c2 - c1) * a2 - (d2 - d1) * (a2 - a1) * c2) / ((b2 - b1) * (c2 - c1) - (d2 - d1) * (a2 - a1));
            _point.y = (b2 - b1) / (a2 - a1) * (_point.x - a2) + b2;
        }
        return isCross;
    }

    /// <summary>
    /// 点是否在矩形内
    /// </summary>
    /// <param name="_point">点</param>
    /// <param name="_rect">矩形</param>
    /// <returns>true:在矩形内,false:不在矩形内</returns>
    public static bool IsPointInRect(Vector2 _point, Rect _rect)
    {
        return _point.x >= _rect.xMin && _point.x <= _rect.xMax
                    && _point.y >= _rect.yMin && _point.y <= _rect.yMax;
    }
}

Polygon2DUtility.cs

using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 三角剖分工具
/// </summary>
public sealed class Polygon2DUtility
{
    /// <summary>
    /// 生成三角剖分结果
    /// </summary>
    /// <param name="_vertexs">离散顶点集</param>
    /// <returns>三角剖分结果</returns>
    public static PolygonDelaunayResult2D BuilderDelaunay2D(List<Vector3> _vertexs)
    {
        return BuilderDelaunay2D(_vertexs, null);
    }
    /// <summary>
    /// 生成三角剖分结果
    /// </summary>
    /// <param name="_vertexs">离散顶点集</param>
    /// <param name="_polygonBoundsPointIndexs">多边形边界点索引</param>
    /// <returns>三角剖分结果</returns>
    public static PolygonDelaunayResult2D BuilderDelaunay2D(List<Vector3> _vertexs, List<int> _polygonBoundsPointIndexs)
    {
        List<PolygonPoint2D> polygonVertexs = new List<PolygonPoint2D>();
        Bounds polygonBounds = new Bounds();
        PolygonTriangle2D polygonSupperTriangle = new PolygonTriangle2D(new PolygonPoint2D(0, Vector3.zero), new PolygonPoint2D(1, Vector3.zero), new PolygonPoint2D(2, Vector3.zero));
        //已完成的剖分三角形
        List<PolygonTriangle2D> polygonDelaunayTriangles = new List<PolygonTriangle2D>();
        //多边形边界边
        Dictionary<int, PolygonEdge2D> polygonBoundsEdgeMaping = new Dictionary<int, PolygonEdge2D>();
        if (_vertexs.Count >= 3)
        {
            #region 分析点
            Vector3 max = Vector3.one * float.MinValue;
            Vector3 min = Vector3.one * float.MaxValue;
            //构建顶点
            for (int i = 0; i < _vertexs.Count; i++)
            {
                polygonVertexs.Add(new PolygonPoint2D(i, _vertexs[i]));
                min.x = Mathf.Min(min.x, _vertexs[i].x);
                min.y = Mathf.Min(min.y, _vertexs[i].y);
                min.z = Mathf.Min(min.z, _vertexs[i].z);
                max.x = Mathf.Max(max.x, _vertexs[i].x);
                max.y = Mathf.Max(max.y, _vertexs[i].y);
                max.z = Mathf.Max(max.z, _vertexs[i].z);
            }

            PolygonEdge2D tempPolygonEdge2D = null;
            if (_polygonBoundsPointIndexs != null && _polygonBoundsPointIndexs.Count > 0)
            {
                for (int i = 0; i < _polygonBoundsPointIndexs.Count; i++)
                {
                    if (i < _polygonBoundsPointIndexs.Count - 1)
                    {
                        tempPolygonEdge2D = new PolygonEdge2D(polygonVertexs[i], polygonVertexs[i + 1]);
                    }
                    else
                    {
                        tempPolygonEdge2D = new PolygonEdge2D(polygonVertexs[i], polygonVertexs[0]);
                    }

                    polygonBoundsEdgeMaping.Add(tempPolygonEdge2D.key, tempPolygonEdge2D);
                }
            }

            //x轴从左往右排序
            polygonVertexs.Sort((x, y) => { return x.position.x >= y.position.x ? 1 : -1; });
            //y轴从下往上排序
            polygonVertexs.Sort((x, y) => { return (x.position.x == y.position.x && x.position.y >= y.position.y) ? 1 : -1; });

            //构建一个包含所有点集的三角形
            polygonBounds = new Bounds(Vector3.Lerp(min, max, 0.5f), Vector3.zero);
            polygonBounds.SetMinMax(min, max);
            float raudis = Vector3.Distance(polygonBounds.min, polygonBounds.max) * 0.6f;
            Vector3 tvc = polygonBounds.center + Vector3.back * raudis;
            Vector3 tvl = polygonBounds.min + Vector3.back * (raudis - Mathf.Abs(polygonBounds.extents.z));
            Vector3 axis = tvc - polygonBounds.center;
            Vector3 vc = (tvl - tvc).normalized * raudis * 2;
            Vector3 tp0 = vc + tvc;
            vc = Quaternion.AngleAxis(120, axis) * vc;
            Vector3 tp1 = vc + tvc;
            vc = Quaternion.AngleAxis(120, axis) * vc;
            Vector3 tp2 = vc + tvc;
            tp0.z = tp1.z = tp2.z = 0;
            polygonSupperTriangle = new PolygonTriangle2D(new PolygonPoint2D(_vertexs.Count, tp0, true), new PolygonPoint2D(_vertexs.Count + 1, tp1, true), new PolygonPoint2D(_vertexs.Count + 2, tp2, true));
            #endregion

            #region 三角剖分
            //缓存的剖分三角形
            List<PolygonTriangle2D> tempDelaunayTriangles = new List<PolygonTriangle2D>();
            //等待检测的三角形
            List<PolygonTriangle2D> validateTriangles = new List<PolygonTriangle2D>() { polygonSupperTriangle };
            //从第一个顶点开始,进行三角剖分检测
            int count = polygonVertexs.Count;
            for (int i = 0; i < count; i++)
            {
                List<PolygonTriangle2D> delTris = FindDelaunayTriangle(polygonVertexs[i], ref validateTriangles);
                if (delTris.Count > 0)
                {
                    tempDelaunayTriangles.AddRange(delTris);
                }
            }
            tempDelaunayTriangles.AddRange(validateTriangles);
            #endregion

            #region 删除超级三角形
            //需要补边的三角形
            List<PolygonTriangle2D> pushEdgeTriangles = new List<PolygonTriangle2D>();
            int supperPointCount = 0;
            foreach (PolygonTriangle2D tri in tempDelaunayTriangles)
            {
                //缓存三角形有任意点是超三角形的点
                supperPointCount = 0;
                foreach (PolygonPoint2D v in tri.vertexs)
                {
                    if (v.isSupperPoint)
                    {
                        supperPointCount++;
                    }
                }
                if (supperPointCount > 0)
                {
                    pushEdgeTriangles.Add(tri);
                }
                else
                {
                    polygonDelaunayTriangles.Add(tri);
                    foreach (PolygonEdge2D edge in tri.edges)
                    {
                        polygonBoundsEdgeMaping.Remove(edge.key);
                    }
                }
            }
            #endregion

            #region 对删除的超级三角形与边界比对,补充未绘制的边界三角形
            //点索引
            int pointIndex = _vertexs.Count;
            //交点
            Vector3 point = Vector3.zero;
            //边是否相交
            bool isIntersection = false;
            //边交点
            Dictionary<int, Dictionary<int, PolygonPoint2D>> edgePoint =new Dictionary<int, Dictionary<int, PolygonPoint2D>>();
            //三角形被边界线切割后的多边形点
            Dictionary<int, PolygonPoint2D> polygonPoints = new Dictionary<int, PolygonPoint2D>();
            //检测三角形
            foreach (PolygonTriangle2D tri in pushEdgeTriangles)
            {
                polygonPoints.Clear();
                foreach (PolygonEdge2D triEdge in tri.edges)
                {
                    foreach (PolygonEdge2D edge in polygonBoundsEdgeMaping.Values)
                    {
                        #region 求交点
                        isIntersection = false;
                        point = Vector3.zero;
                        if (edgePoint.ContainsKey(triEdge.key) && edgePoint[triEdge.key].ContainsKey(edge.key))
                        {
                            isIntersection = true;
                        }
                        else if (Geometry2DUtility.EdgeIntersection(triEdge, edge, ref point))
                        {
                            isIntersection = true;
                            if (!edgePoint.ContainsKey(triEdge.key))
                            {
                                edgePoint.Add(triEdge.key, new Dictionary<int, PolygonPoint2D>());
                            }
                            if (!edgePoint[triEdge.key].ContainsKey(edge.key))
                            {
                                edgePoint[triEdge.key].Add(edge.key, new PolygonPoint2D(pointIndex, point));
                            }
                            polygonVertexs.Add(edgePoint[triEdge.key][edge.key]);
                            pointIndex++;
                        }
                        if (isIntersection)
                        {
                            //加入交点
                            if (!polygonPoints.ContainsKey(edgePoint[triEdge.key][edge.key].index))
                            {
                                polygonPoints.Add(edgePoint[triEdge.key][edge.key].index, edgePoint[triEdge.key][edge.key]);
                            }
                        }
                        #endregion
                    }
                }
                if (polygonPoints.Count > 0)
                {
                    //如果有任意交点,保留三角形非超级点
                    foreach (PolygonPoint2D p in tri.vertexs)
                    {
                        if (!p.isSupperPoint)
                        {
                            if (!polygonPoints.ContainsKey(p.index))
                            {
                                polygonPoints.Add(p.index, p);
                            }
                        }
                    }
                    if (polygonPoints.Count >= 3)
                    {
                        List<PolygonPoint2D> pps = new List<PolygonPoint2D>(polygonPoints.Values);
                        if (pps.Count == 3)
                        {
                            polygonDelaunayTriangles.Add(
                               new PolygonTriangle2D(pps[0], pps[1], pps[2]));
                        }
                        else if (pps.Count == 4)
                        {
                            //如果有两个交点,则pps中前两个是交点,后两个是原三角形的两个顶点
                            //交点与顶点各取一点组成两条边,然后看两条边是否相交,相交则是对角线,不相交则不是
                            //找到对角线后,与非对角线点的另外一个交点和顶点组成新的两个三角形
                            PolygonEdge2D edge1 = new PolygonEdge2D(pps[0],pps[2]);
                            PolygonEdge2D edge2 = new PolygonEdge2D(pps[1], pps[3]);
                            if (Geometry2DUtility.EdgeIntersection(edge1, edge2))
                            {
                                //0,2,1 和0,2,3 组成两个新的三角形
                                polygonDelaunayTriangles.Add(
                                    new PolygonTriangle2D(pps[0], pps[2], pps[1]));
                                polygonDelaunayTriangles.Add(
                                    new PolygonTriangle2D(pps[0], pps[2], pps[3]));
                            }
                            else
                            {
                                //0,3,1 和 0,3,2 组成两个新的三角形
                                polygonDelaunayTriangles.Add(
                                    new PolygonTriangle2D(pps[0], pps[3], pps[1]));
                                polygonDelaunayTriangles.Add(
                                    new PolygonTriangle2D(pps[0], pps[3], pps[2]));
                            }
                        }
                    }                    
                }
            }
            #endregion
        }
        else
        {
            throw new UnityException("There are not enough vertexs to constitute polygon,the vertexs are least of three.");
        }
        return new PolygonDelaunayResult2D(polygonVertexs,polygonBounds,polygonSupperTriangle,polygonDelaunayTriangles);
    }

    /// <summary>
    /// 边切割三角形
    /// </summary>
    /// <param name="_triangle">三角形</param>
    /// <param name="_edge">边</param>
    /// <returns></returns>
    static List<PolygonTriangle2D> EdgeCutTriangle(PolygonTriangle2D _triangle,PolygonEdge2D _edge)
    {
        List<PolygonTriangle2D> triangles = new List<PolygonTriangle2D>();

        return triangles;
    }

    /// <summary>
    /// 找到三角剖分三角形
    /// </summary>
    /// <param name="_point">点</param>
    /// <param name="_validateTriangles">待检测三形</param>
    static List<PolygonTriangle2D> FindDelaunayTriangle(PolygonPoint2D _point,ref List<PolygonTriangle2D> _validateTriangles)
    {
        List<PolygonTriangle2D> result = new List<PolygonTriangle2D>();
        List<PolygonTriangle2D> cache = new List<PolygonTriangle2D>();
        Dictionary<int, PolygonEdge2D> edges = new Dictionary<int, PolygonEdge2D>();
        //两个或两个以上三角形用到的边为共同边,要用等组合边中去掉
        Dictionary<int, int> edgeUseNum = new Dictionary<int, int>();
        for (int i = 0; i < _validateTriangles.Count; i++)
        {
            if (!_validateTriangles[i].IsInCircumCircle(_point.position))
            {
                if (_validateTriangles[i].circumCircleCenter.x + _validateTriangles[i].circumCircleRadius <= _point.position.x)
                {
                    result.Add(_validateTriangles[i]);
                }
                else
                {
                    cache.Add(_validateTriangles[i]);
                }
            }
            else
            {
                foreach (PolygonEdge2D d in _validateTriangles[i].edges)
                {
                    if (!edges.ContainsKey(d.key))
                    {
                        edges.Add(d.key, d);
                    }
                    if (!edgeUseNum.ContainsKey(d.key))
                    {
                        edgeUseNum.Add(d.key, 0);
                    }
                    edgeUseNum[d.key]++;
                }
            }
        }

        //未确定的边重建三角形
        foreach (PolygonEdge2D d in edges.Values)
        {
            //只重建非共用的边,共用边是两个相邻三角形的对角线,不需要再重建了。
            if (edgeUseNum[d.key] <= 1)
            {
                cache.Add(new PolygonTriangle2D(d.vertexA, d.vertexB, _point));
            }            
        }

        _validateTriangles = cache;
        return result;
    }
}

PolygonDelaunayResult2D.cs

using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 多边形三角剖分结果
/// </summary>
public class PolygonDelaunayResult2D
{
    /// <summary>
    /// 网格顶点
    /// </summary>
    public List<PolygonPoint2D> vertexs { get; private set; }
    /// <summary>
    /// 包围盒
    /// </summary>
    public Bounds bounds { get; private set; }
    /// <summary>
    /// 超级三角形(包含所有点集的三角形)
    /// </summary>
    public PolygonTriangle2D supperTriangle { get; private set; }
    /// <summary>
    /// 剖分三角形
    /// </summary>
    public List<PolygonTriangle2D> delaunayTriangle { get; private set; }
    /// <summary>
    /// 添加顶点
    /// </summary>
    /// <param name="_vertexs">顶点</param>
    /// <param name="_bounds">包围盒</param>
    /// <param name="_supperTriangle">超级三角形</param>
    /// <param name="_delaunayTriangle">剖分三角形</param>
    public PolygonDelaunayResult2D(List<PolygonPoint2D> _vertexs,Bounds _bounds, PolygonTriangle2D _supperTriangle, List<PolygonTriangle2D> _delaunayTriangle)
    {
        vertexs = _vertexs;
        bounds = _bounds;
        supperTriangle = _supperTriangle;
        delaunayTriangle = _delaunayTriangle;
    }
}

PolygonEdge2D.cs

using System.Collections.Generic;
using System.Text;
/// <summary>
/// 多边形边
/// </summary>
public class PolygonEdge2D
{
    /// <summary>
    /// 多边形边
    /// </summary>
    /// <param name="_vertexA">顶点A</param>
    /// <param name="_vertexB">顶点B</param>
    public PolygonEdge2D(PolygonPoint2D _vertexA, PolygonPoint2D _vertexB)
    {
        vertexA = _vertexA;
        vertexB = _vertexB;
        List<int> sort = new List<int>() { _vertexA.index, _vertexB.index };
        sort.Sort();
        StringBuilder sb = new StringBuilder();
        foreach (int k in sort)
        {
            sb.Append(k);
        }
        key = sb.ToString().UniqueHashCode();
    }
    /// <summary>
    /// 边Key值
    /// </summary>
    public int key { get; private set; }
    /// <summary>
    /// 顶点A
    /// </summary>
    public PolygonPoint2D vertexA { get; private set; }
    /// <summary>
    /// 顶点B
    /// </summary>
    public PolygonPoint2D vertexB { get; private set; }
}

PolygonPoint2D.cs

using UnityEngine;
/// <summary>
/// 多边形点
/// </summary>
public class PolygonPoint2D
{
    /// <summary>
    /// 多边形点
    /// </summary>
    /// <param name="_index">点索引</param>
    /// <param name="_position">点位置</param>
    public PolygonPoint2D(int _index, Vector3 _position)
        : this(_index, _position, false)
    {
    }

    /// <summary>
    /// 多边形点
    /// </summary>
    /// <param name="_index">点索引</param>
    /// <param name="_position">点位置</param>
    /// <param name="_isSupperPoint">是否是超级点</param>
    public PolygonPoint2D(int _index, Vector3 _position,bool _isSupperPoint)
    {
        index = _index;
        position = new Vector3(_position.x, _position.y, 0);
        isSupperPoint = _isSupperPoint;
    }
    /// <summary>
    /// 索引
    /// </summary>
    public int index { get; private set; }
    /// <summary>
    /// 位置
    /// </summary>
    public Vector3 position { get; private set; }
    /// <summary>
    /// 是否是超级点
    /// </summary>
    public bool isSupperPoint { get; private set; }
}

PolygonTriangle2D.cs

using UnityEngine;
/// <summary>
/// 多边形三角形
/// </summary>
public class PolygonTriangle2D
{
    /// <summary>
    /// 多边形三角形
    /// </summary>
    /// <param name="_vertexA">顶点A</param>
    /// <param name="_vertexB">顶点B</param>
    /// <param name="_vertexC">顶点C</param>
    public PolygonTriangle2D(PolygonPoint2D _vertexA, PolygonPoint2D _vertexB, PolygonPoint2D _vertexC)
    {
        //Vector3 ab = (_vertexB.position - _vertexA.position).normalized;
        //Vector3 ac = (_vertexC.position - _vertexA.position).normalized;
        //Vector3 vc = Vector3.Cross(ab, ac);
        //if (vc.z > 0)
        //{
        //    //逆时针三角形,B,C点交换
        //    PolygonPoint2D p = _vertexC;
        //    _vertexC = _vertexB;
        //    _vertexB = p;
        //}
        vertexs = new PolygonPoint2D[3] { _vertexA, _vertexB, _vertexC };
        edges = new PolygonEdge2D[3] { new PolygonEdge2D(_vertexA, _vertexB), new PolygonEdge2D(_vertexB, _vertexC), new PolygonEdge2D(_vertexC, _vertexA) };
        circumCircleCenter = Geometry2DUtility.TriangleCircumCircle(_vertexA.position, _vertexB.position, _vertexC.position);
        circumCircleRadius = Vector3.Distance(circumCircleCenter, _vertexA.position);

        key = (_vertexA.index.ToString() + _vertexB.index.ToString() + _vertexC.ToString()).UniqueHashCode();
    }

    /// <summary>
    /// 点是否在三角形外接圆内
    /// </summary>
    /// <param name="_point">点</param>
    /// <returns>True:是,False:否</returns>
    public bool IsInCircumCircle(Vector3 _point)
    {
        return Vector3.Distance(circumCircleCenter, _point) < circumCircleRadius;
    }

    /// <summary>
    /// 点是否在三角形内
    /// </summary>
    /// <param name="_point">点</param>
    /// <returns>True:是,False:否</returns>
    public bool IsInTriangle(Vector3 _point)
    {
        Vector3 a = vertexs[0].position;
        Vector3 b = vertexs[1].position;
        Vector3 c = vertexs[2].position;
        Vector3 p = _point;
        return SameSide(a, b, c, p) &&
        SameSide(b, c, a, p) &&
        SameSide(c, a, b, p);
    }

    // Determine whether two vectors v1 and v2 point to the same direction
    // v1 = Cross(AB, AC)
    // v2 = Cross(AB, AP)
    bool SameSide(Vector3 _a, Vector3 _b, Vector3 _c, Vector3 _p)
    {
        Vector3 ab = _b - _a;
        Vector3 ac = _c - _a;
        Vector3 ap = _p - _a;
        Vector3 v1 = Vector3.Cross(ab, ac);
        Vector3 v2 = Vector3.Cross(ab, ap);
        // v1 and v2 should point to the same direction
        return Vector3.Dot(v1, v2) >= 0;
    }
    /// <summary>
    /// Key
    /// </summary>
    public int key { get; private set; }
    /// <summary>
    /// 外接圆半径
    /// </summary>
    public float circumCircleRadius { get; private set; }
    /// <summary>
    /// 外接圆圆心
    /// </summary>
    public Vector3 circumCircleCenter { get; private set; }
    /// <summary>
    /// 顶点【A,B,C】【逆时针】
    /// </summary>
    public PolygonPoint2D[] vertexs { get; private set; }
    /// <summary>
    /// 边【逆时针】
    /// </summary>
    public PolygonEdge2D[] edges { get; private set; }
}

AbsUIGraphicMask.cs

using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 绘制遮罩
/// </summary>
public abstract class AbsUIGraphicMask : MaskableGraphic, ICanvasRaycastFilter
{
    /// <summary>
    /// 遮罩事件触发分类
    /// </summary>
    [AliasTooltip("遮罩事件触发分类")]
    public enTriggerEventClassiy triggerEventClassify = enTriggerEventClassiy.MaskEnable;
    /// <summary>
    /// 遮罩绘制分类
    /// </summary>
    [AliasTooltip("遮罩绘制分类")]
    public enGraphicMaskClassify graphicClassify = enGraphicMaskClassify.ExceptMask;
    /// <summary>
    /// 绘制精灵填充分类
    /// </summary>
    [AliasTooltip("绘制精灵填充分类")]
    public enGraphicSpriteFillClassify graphicSpriteFillClassify = enGraphicSpriteFillClassify.SceneRatioFill;
    /// <summary>
    /// 精灵图
    /// </summary>
    [AliasTooltip("精灵图")]
    public Sprite sprite;

    #region 这个属性必须重写返回当前的Sprite
    /// <summary>
    /// Image's texture comes from the UnityEngine.Image.
    /// </summary>
    public override Texture mainTexture
    {
        get
        {
            return sprite == null ? s_WhiteTexture : sprite.texture;
        }
    }
    #endregion

    #region EditorDisplayParameter
#if UNITY_EDITOR
    [InvokeMethod("EditorDisplayParameter")]
    public string invoke;
    /// <summary>
    /// OnDisplayPath
    /// </summary>
    /// <param name="_position">位置</param>
    /// <param name="_property">属性</param>
    /// <param name="_label">标签</param>
    /// <returns>高度</returns>
    float EditorDisplayParameter(Rect _position, SerializedProperty _property, GUIContent _label)
    {
        float y = _position.y;
        _position.height = 16;
        if (mGraphicMasks != null && mGraphicMasks.Count > 0)
        {
            EditorGUI.LabelField(_position, "Ignore Graphic");
            _position.y += _position.height;
            for (int i = 0; i < mGraphicMasks.Count; i++)
            {
                EditorGUI.ObjectField(_position, (i + 1).PadLeft(mGraphicMasks.Count) + ".", mGraphicMasks[i], typeof(Graphic), true);
                _position.y += _position.height;
            }
        }
        return _position.y - y;
    }
#endif
    #endregion

    #region enTriggerEventClassiy 事件触发分类
    /// <summary>
    /// 事件触发分类
    /// </summary>
    public enum enTriggerEventClassiy
    {
        /// <summary>
        /// 禁用事件
        /// </summary>
        [AliasTooltip("禁用事件")]
        DisableAll,
        /// <summary>
        /// 仅禁用遮罩事件
        /// </summary>
        [AliasTooltip("仅禁用遮罩事件")]
        MaskDisable,
        /// <summary>
        /// 仅开启遮罩事件
        /// </summary>
        [AliasTooltip("仅开启遮罩事件")]
        MaskEnable,
        /// <summary>
        /// 开启事件
        /// </summary>
        [AliasTooltip("开启事件")]
        OpenAll,
    }
    #endregion

    #region enGraphicMaskClassify 绘制遮罩分类
    /// <summary>
    /// 绘制遮罩分类
    /// </summary>
    public enum enGraphicMaskClassify
    {
        /// <summary>
        /// 全屏填充
        /// </summary>
        [AliasTooltip("全屏填充")]
        Scene,
        /// <summary>
        /// 仅填充遮罩
        /// </summary>
        [AliasTooltip("仅填充遮罩")]
        Mask,
        /// <summary>
        /// 除遮罩之外都填充
        /// </summary>
        [AliasTooltip("除遮罩之外都填充")]
        ExceptMask,
    }
    #endregion    

    #region enGraphicSpriteFillClassify 绘制精灵填充分类
    /// <summary>
    /// 绘制精灵填充分类
    /// </summary>
    public enum enGraphicSpriteFillClassify
    {
        /// <summary>
        /// 按所占屏幕比例填充
        /// </summary>
        [AliasTooltip("按所占屏幕比例填充")]
        SceneRatioFill,
        /// <summary>
        /// 遮罩单独填充
        /// </summary>
        [AliasTooltip("遮罩单独填充")]
        MaskAloneFill,
    }
    #endregion

    #region IsRaycastLocationValid 是否通过Raycast验证
    /// <summary>
    /// 是否通过Raycast验证
    /// </summary>
    /// <param name="_screenPoint">屏幕坐标</param>
    /// <param name="_eventCamera">检测相机</param>
    /// <returns>True:检测到有效目标【事件停止传递】,False:未检测到有效目标【事件继续传递】</returns>
    public bool IsRaycastLocationValid(Vector2 _screenPoint, Camera _eventCamera)
    {
        bool isStopEvent = false;
        switch (triggerEventClassify)
        {
            case enTriggerEventClassiy.DisableAll:
                isStopEvent = true;
                break;
            case enTriggerEventClassiy.MaskDisable:
                isStopEvent = IsPointInAnyMask(_screenPoint, _eventCamera);
                break;
            case enTriggerEventClassiy.MaskEnable:
                isStopEvent = !IsPointInAnyMask(_screenPoint, _eventCamera);
                break;
            case enTriggerEventClassiy.OpenAll:
                isStopEvent = false;
                break;
        }
        return isStopEvent;
    }

    /// <summary>
    /// Point是否在任意Mask上
    /// </summary>
    /// <param name="_screenPoint">屏幕坐标</param>
    /// <param name="_eventCamera">检测相机</param>
    /// <returns>True:是,False:否</returns>
    bool IsPointInAnyMask(Vector2 _screenPoint, Camera _eventCamera)
    {
        bool isPointInAnyMask = false;
        if (mGraphicMasks != null && mGraphicMasks.Count > 0)
        {
            foreach (Graphic g in mGraphicMasks)
            {
                if (RectTransformUtility.RectangleContainsScreenPoint(g.rectTransform, _screenPoint, _eventCamera))
                {
                    isPointInAnyMask = true;
                    break;
                }
            }
        }
        return isPointInAnyMask;
    }
    #endregion

    #region graphicMasks 当前GraphicMask组
    /// <summary>
    /// 当前GraphicMask组
    /// </summary>
    protected List<Graphic> graphicMasks { get { return mGraphicMasks; } }
    #endregion

    #region AddGraphicMask 添加遮罩Graphic
    /// <summary>
    /// Graphic组
    /// </summary>
    List<Graphic> mGraphicMasks = new List<Graphic>();
    /// <summary>
    /// 添加Graphic遮罩
    /// </summary>
    /// <param name="_mask">遮罩</param>
    public void AddGraphicMask(Graphic _mask)
    {
        if (!mGraphicMasks.Contains(_mask))
        {
            mGraphicMasks.Add(_mask);
            RegisterDirty(_mask);
            SetVerticesDirty();
        }
    }
    #endregion

    #region RemoveGraphicMask 移除Graphic遮罩
    /// <summary>
    /// 移除Graphic遮罩
    /// </summary>
    /// <param name="_mask">遮罩</param>
    public void RemoveGraphicMask(Graphic _mask)
    {
        mGraphicMasks.Remove(_mask);
        UnregisterDirty(_mask);
        SetVerticesDirty();
    }
    #endregion

    #region RegisterDirty 注册Dirty事件
    /// <summary>
    /// 注册Dirty事件
    /// </summary>
    /// <param name="_graphic">绘制</param>
    void RegisterDirty(Graphic _graphic)
    {
        _graphic.RegisterDirtyLayoutCallback(DirtyAction);
        _graphic.RegisterDirtyMaterialCallback(DirtyAction);
        _graphic.RegisterDirtyVerticesCallback(DirtyAction);
    }

    /// <summary>
    /// Dirty动作
    /// </summary>
    void DirtyAction()
    {
        SetVerticesDirty();
    }

    /// <summary>
    /// 注册Dirty事件
    /// </summary>
    /// <param name="_graphic">绘制</param>
    void UnregisterDirty(Graphic _graphic)
    {
        _graphic.UnregisterDirtyLayoutCallback(DirtyAction);
        _graphic.UnregisterDirtyMaterialCallback(DirtyAction);
        _graphic.UnregisterDirtyVerticesCallback(DirtyAction);
    }
    #endregion    

    #region IsChangeGraphicMaskVariable 绘制遮罩变量是否有变更
    /// <summary>
    /// 最后绘制的graphic遮罩
    /// </summary>
    Dictionary<int, GraphicVariable> mLastDrawGraphicMaskMaping = new Dictionary<int, GraphicVariable>();
    /// <summary>
    /// 绘制遮罩变量是否有变更
    /// </summary>
    /// <param name="_masks">绘制遮罩</param>
    /// <returns>True:有变更,False:无变更</returns>
    bool IsChangeGraphicMaskVariable(List<Graphic> _masks)
    {
        bool isAnyMaskChange = mLastDrawGraphicMaskMaping.Count != _masks.Count;
        int gid = 0;
        if (!isAnyMaskChange)
        {
            //如果个数相同,则看是不是与最后保存的是同样的Graphic
            foreach (Graphic g in _masks)
            {
                gid = g.gameObject.GetInstanceID();
                isAnyMaskChange |= !mLastDrawGraphicMaskMaping.ContainsKey(gid);
                if (isAnyMaskChange)
                {
                    break;
                }
            }
        }

        if (!isAnyMaskChange)
        {
            //如果与保存的是同样的Graphic,则看每个Graphic的参数是否有不同
            foreach (Graphic g in _masks)
            {
                isAnyMaskChange |= mLastDrawGraphicMaskMaping[g.gameObject.GetInstanceID()].isDifferent(g);
                if (isAnyMaskChange)
                {
                    break;
                }
            }
        }
        else
        {
            mLastDrawGraphicMaskMaping.Clear();
            foreach (Graphic g in _masks)
            {
                mLastDrawGraphicMaskMaping.Add(g.gameObject.GetInstanceID(), new GraphicVariable(g));
            }
        }
        return isAnyMaskChange;
    }

    /// <summary>
    /// Graphic变量
    /// </summary>
    class GraphicVariable
    {
        /// <summary>
        /// anchoredPosition
        /// </summary>
        Vector2 mAnchoredPosition = Vector2.zero;
        /// <summary>
        /// anchoredPosition3D
        /// </summary>
        Vector3 mAnchoredPosition3D = Vector3.zero;
        /// <summary>
        /// anchorMin
        /// </summary>
        Vector2 mAnchorMin = Vector2.zero;
        /// <summary>
        /// anchorMax
        /// </summary>
        Vector2 mAnchorMax = Vector2.zero;
        /// <summary>
        /// offsetMin
        /// </summary>
        Vector2 mOffsetMin = Vector2.zero;
        /// <summary>
        /// offsetMax
        /// </summary>
        Vector2 mOffsetMax = Vector2.zero;
        /// <summary>
        /// pivot
        /// </summary>
        Vector2 mPivot = Vector2.zero;
        /// <summary>
        /// sizeDelta
        /// </summary>
        Vector2 mSizeDelta = Vector2.zero;

        /// <summary>
        /// localEulerAngles
        /// </summary>
        Vector3 mLocalEulerAngles = Vector3.zero;
        /// <summary>
        /// localScale
        /// </summary>
        Vector3 mLocalScale = Vector3.zero;

        /// <summary>
        /// Graphic变量
        /// </summary>
        /// <param name="_g">Graphic</param>
        public GraphicVariable(Graphic _g)
        {
            SetVariable(_g);
        }

        /// <summary>
        /// 设置变量
        /// </summary>
        /// <param name="_g">Graphic</param>
        void SetVariable(Graphic _g)
        {
            RectTransform rt = (RectTransform)_g.gameObject.transform;
            mAnchoredPosition = rt.anchoredPosition;
            mAnchoredPosition3D = rt.anchoredPosition3D;
            mAnchorMin = rt.anchorMin;
            mAnchorMax = rt.anchorMax;
            mOffsetMin = rt.offsetMin;
            mOffsetMax = rt.offsetMax;
            mPivot = rt.pivot;
            mSizeDelta = rt.sizeDelta;
            mLocalScale = rt.localScale;
            mLocalEulerAngles = rt.localEulerAngles;
        }

        /// <summary>
        /// 是否与指定Graphic不同
        /// </summary>
        /// <param name="_g">Graphic</param>
        /// <returns></returns>
        public bool isDifferent(Graphic _g)
        {
            RectTransform rt = (RectTransform)_g.gameObject.transform;
            bool isNot = mAnchoredPosition != rt.anchoredPosition ||
            mAnchoredPosition3D != rt.anchoredPosition3D ||
            mAnchorMin != rt.anchorMin ||
            mAnchorMax != rt.anchorMax ||
            mOffsetMin != rt.offsetMin ||
            mOffsetMax != rt.offsetMax ||
            mPivot != rt.pivot ||
            mSizeDelta != rt.sizeDelta ||
            mLocalScale != rt.localScale ||
            mLocalEulerAngles != rt.localEulerAngles;
            if (isNot)
            {
                SetVariable(_g);
            }
            return isNot;
        }
    }
    #endregion

    #region LateUpdate
    /// <summary>
    /// LateUpdate
    /// </summary>
    private void LateUpdate()
    {
        if (IsChangeGraphicMaskVariable(mGraphicMasks))
        {
            DirtyAction();
        }
    }
    #endregion    
}

UIGraphicMaskShader.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 绘制遮罩【Shader版】
/// 依赖项
/// 1.UIGraphicMaskShader.shader
/// </summary>
[AddComponentMenu("Game/UI/GraphicMask/Shader")]
public class UIGraphicMaskShader : AbsUIGraphicMask
{
    #region OnPopulateMesh
    /// <summary>
    /// 空图
    /// </summary>
    Texture2D mTextureClear = null;
    /// <summary>
    /// 最大图数量
    /// </summary>
    int mMaxTextureNum = 0;
    /// <summary>
    /// Fill the vertex buffer data.
    /// </summary>
    /// <param name="_vh">顶点Helper</param>
    protected override void OnPopulateMesh(VertexHelper _vh)
    {
        if (graphicMasks != null && graphicMasks.Count > 0)
        {
            Rect sceneRect = this.GraphicLocalRectForVertexHelper();
            switch (graphicClassify)
            {
                case enGraphicMaskClassify.Scene:
                    OnFillScene(sceneRect, graphicMasks);
                    break;
                case enGraphicMaskClassify.Mask:
                    OnFillMask(sceneRect, graphicMasks);
                    break;
                case enGraphicMaskClassify.ExceptMask:
                    OnFillExceptMask(sceneRect, graphicMasks);
                    break;
            }
            mMaxTextureNum = Mathf.Max(mMaxTextureNum, graphicMasks.Count);
        }
        else
        {
            if (mTextureClear == null)
            {
                mTextureClear = new Texture2D(1, 1, TextureFormat.ARGB32, false, false);
                mTextureClear.SetPixel(0, 0, Color.clear);
                mTextureClear.Apply();
            }
            for (int i = 0; i < mMaxTextureNum; i++)
            {
                material.SetTexture("_GraphicTex" + i, mTextureClear);
            }
        }
        base.OnPopulateMesh(_vh);
    }
    #endregion

    #region OnFillScene 全屏填充
    /// <summary>
    /// 全屏填充
    /// </summary>
    /// <param name="_sceneRect">屏幕矩形</param>
    /// <param name="_masks">绘制组</param>
    protected virtual void OnFillScene(Rect _sceneRect,List<Graphic> _masks)
    {
        //_vh.Clear();
        //List<UIVertex> vertexs = new List<UIVertex>();
        //Vector3[] fourCorners = null;
        //Vector4 uv = (sprite != null) ? UnityEngine.Sprites.DataUtility.GetOuterUV(sprite) : Vector4.zero;
        //this.GraphicLocalCornersForVertexHelper(out fourCorners);
        //vertexs.Add(new UIVertex() { position = fourCorners[0], color = color, uv0 = new Vector2(uv.x, uv.y) });
        //vertexs.Add(new UIVertex() { position = fourCorners[1], color = color, uv0 = new Vector2(uv.x, uv.w) });
        //vertexs.Add(new UIVertex() { position = fourCorners[2], color = color, uv0 = new Vector2(uv.z, uv.w) });
        //vertexs.Add(new UIVertex() { position = fourCorners[3], color = color, uv0 = new Vector2(uv.z, uv.y) });
        //_vh.AddUIVertexQuad(vertexs.ToArray());
        OnFillExceptMask(_sceneRect, _masks);
    }
    #endregion

    #region OnFillMask 仅填充遮罩
    //x=>u最小值,z=>u最大值 ,y=>v最小值,y=>v最大值
    //v
    //|     xw---------zw
    //|       |               |
    //|     xy----------zy
    //------------------------u
    static readonly Vector4 uvDefault = new Vector4(0, 0, 1, 1);
    /// <summary>
    /// 仅填充遮罩
    /// </summary>
    /// <param name="_sceneRect">屏幕矩形</param>
    /// <param name="_masks">绘制组</param>
    protected virtual void OnFillMask(Rect _sceneRect, List<Graphic> _masks)
    {
        OnFillExceptMask(_sceneRect,_masks);
    }
    #endregion

    #region OnFillExceptMask 除遮罩之外都填充
    /// <summary>
    /// 除遮罩之外都填充
    /// </summary>
    /// <param name="_sceneRect">屏幕矩形</param>
    /// <param name="_masks">绘制组</param>
    protected virtual void OnFillExceptMask(Rect _sceneRect, List<Graphic> _masks)
    {
        Sprite sprite = null;
        Image image = null;
        Vector4 spriteUV = Vector4.zero;        
        Vector4 spriteUVClip = Vector4.zero;
        Vector4 spriteBorder = Vector4.zero;
        Vector4 rectUV = Vector4.zero;
        Vector4 sceneGraphicWh = Vector4.zero;
        Vector4 graphicRectSlicedBorder = Vector4.zero;
        Vector4 graphicUvSlicedBorderRatio = Vector4.zero;
        Vector2 rectMin = Vector2.zero;
        Vector2 rectMax = Vector2.zero;
        bool isGraphicSliced = false;
        sceneGraphicWh.x = _sceneRect.size.x;
        sceneGraphicWh.y = _sceneRect.size.y;
        Rect maskRect = new Rect();
        
        for (int i = 0; i < _masks.Count; i++)
        {
            spriteUV = uvDefault;
            spriteUVClip = uvDefault;
            spriteBorder = Vector4.zero;
            isGraphicSliced = false;
            maskRect = _masks[i].GraphicLocalRectForVertexHelper();
            sceneGraphicWh.z = maskRect.size.x;
            sceneGraphicWh.w = maskRect.size.y;
            rectMin = Rect.PointToNormalized(_sceneRect, maskRect.min);
            rectMax = Rect.PointToNormalized(_sceneRect, maskRect.max);
            rectUV.Set(rectMin.x, rectMin.y, rectMax.x, rectMax.y);

            if (!Geometry2DUtility.IsPointInRect(maskRect.min, _sceneRect))
            {
                if (rectMin.x == 0)
                {
                    spriteUVClip.x = Mathf.InverseLerp(maskRect.min.x, maskRect.max.x, _sceneRect.min.x);
                }
                if (rectMin.y == 0)
                {
                    spriteUVClip.y = Mathf.InverseLerp(maskRect.min.y, maskRect.max.y, _sceneRect.min.y);
                }
            }

            if (_masks[i] is Image)
            {
                image = (Image)_masks[i];
                sprite = image.overrideSprite;
                if (sprite != null)
                {
                    spriteUV = UnityEngine.Sprites.DataUtility.GetOuterUV(sprite);
                    graphicRectSlicedBorder.x = sprite.border.x / _masks[i].GetPixelAdjustedRect().width * 0.5f;
                    graphicRectSlicedBorder.y = sprite.border.y / _masks[i].GetPixelAdjustedRect().height * 0.5f;
                    graphicRectSlicedBorder.z = sprite.border.z / _masks[i].GetPixelAdjustedRect().width * 0.5f;
                    graphicRectSlicedBorder.w = sprite.border.w / _masks[i].GetPixelAdjustedRect().height * 0.5f;
                    graphicRectSlicedBorder.z = 1 - graphicRectSlicedBorder.z;
                    graphicRectSlicedBorder.w = 1 - graphicRectSlicedBorder.w;

                    graphicUvSlicedBorderRatio.x = sprite.border.x / sprite.textureRect.width;
                    graphicUvSlicedBorderRatio.y = sprite.border.y / sprite.textureRect.height;
                    graphicUvSlicedBorderRatio.z = sprite.border.z / sprite.textureRect.width;
                    graphicUvSlicedBorderRatio.w = sprite.border.w / sprite.textureRect.height;
                }
                switch (image.type)
                {
                    case Image.Type.Sliced:
                    case Image.Type.Tiled:
                        isGraphicSliced = true;
                        break;
                }
            }
            material.SetTexture("_GraphicTex" + i, _masks[i].mainTexture);
            material.SetVector("_GraphicUvSlicedBorderRatio" + i, graphicUvSlicedBorderRatio);
            material.SetVector("_SceneGraphicWh" + i, sceneGraphicWh);
            material.SetVector("_GraphicUvMinMax" + i, spriteUV);
            material.SetVector("_GraphicUvMinMaxClip" + i, spriteUVClip);
            material.SetVector("_GraphicUvMaskForScene" + i, rectUV);
            material.SetInt("_isGraphicSliced" + i, isGraphicSliced ? 1 : 0);
            material.SetVector("_GraphicRectSlicedBorder" + i, graphicRectSlicedBorder);
        }
    }
    #endregion
}

UIGraphicMaskShader.shader

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Game/UI/GraphicMask/Shader"
{
	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

		[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
		{
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

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

			#pragma multi_compile __ UNITY_UI_ALPHACLIP
			
			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color    : COLOR;
				half2 texcoord  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
			};
			
			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;
			v2f vert(appdata_t IN)
			{
				v2f OUT;
				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

				OUT.texcoord = IN.texcoord;
				
				#ifdef UNITY_HALF_TEXEL_OFFSET
				OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
				#endif
				
				OUT.color = IN.color * _Color;
				return OUT;
			}

			sampler2D _MainTex;
            sampler2D _MaskTex;

            //遮罩要支持多少个,这里就添加多少个,建议最多不超过6个
            //遮罩
            sampler2D _GraphicTex0;
            //屏幕与遮罩长宽
            float4 _SceneGraphicWh0;
            //遮罩最小最大uv坐标
            float4 _GraphicUvMinMax0;
            //遮罩最小最大uv坐标裁剪
            float4 _GraphicUvMinMaxClip0;
            //遮罩矩形(0,0)(1,1)相对于屏幕uv坐标
            float4 _GraphicUvMaskForScene0;
            //遮罩Sliced绘制
            int _isGraphicSliced0;
            //遮罩矩形Sliced Border
            float4 _GraphicRectSlicedBorder0;
            //遮罩图Sliced Border缩放比率
            float4 _GraphicUvSlicedBorderRatio0;

            //遮罩
            sampler2D _GraphicTex1;
            //屏幕与遮罩长宽
            float4 _SceneGraphicWh1;
            //遮罩最小最大uv坐标
            float4 _GraphicUvMinMax1;
            //遮罩最小最大uv坐标裁剪
            float4 _GraphicUvMinMaxClip1;
            //遮罩矩形(0,0)(1,1)相对于屏幕uv坐标
            float4 _GraphicUvMaskForScene1;
            //遮罩Sliced绘制
            int _isGraphicSliced1;
            //遮罩矩形Sliced Border
            float4 _GraphicRectSlicedBorder1;
             //遮罩图Sliced Border缩放比率
            float4 _GraphicUvSlicedBorderRatio1;
            /*
            x=>u最小值,z=>u最大值 ,y=>v最小值,y=>v最大值
            v
            |     xw---------zw
            |       |               |
            |     xy----------zy
            ------------------------u
            */
            //遮罩填充
            fixed fragFillMask(half2 _texcoord,sampler2D _graphicTex,float4 _sceneGraphicWh,
            float4 _graphicUvMinMax,float4 _graphicUvMinMaxClip,float4 _graphicUvMaskForScene,
            int _isGraphicSliced,float4 _GraphicRectSlicedBorder,float4 _GraphicUvSlicedBorderRatio)
            {            
                fixed alpha = 1;
                //屏幕点
                half2 texuv  = _texcoord.xy;
                //如果点在要绘制的遮罩框内才进行像素计算
                if(texuv.x >= _graphicUvMaskForScene.x && texuv.x <= _graphicUvMaskForScene.z
                && texuv.y >= _graphicUvMaskForScene.y && texuv.y <= _graphicUvMaskForScene.w)
                {
                    //长宽比
                    float2 ratiouv = float2(_sceneGraphicWh.x/_sceneGraphicWh.z,_sceneGraphicWh.y/_sceneGraphicWh.w);
                    //矩形参数
                    float4 rectParams = float4(_graphicUvMaskForScene.x,_graphicUvMaskForScene.y, _graphicUvMaskForScene.z - _graphicUvMaskForScene.x,_graphicUvMaskForScene.w - _graphicUvMaskForScene.y);
                    //遮罩图参数
                    float4 texParams= float4(_graphicUvMinMax.x,_graphicUvMinMax.y,_graphicUvMinMax.z - _graphicUvMinMax.x,_graphicUvMinMax.w - _graphicUvMinMax.y);
                    //遮罩颜色
                    float4 graphicColor = float4(1,1,1,1);
                    if(_isGraphicSliced == 1)
                    {
                        //_GraphicUvSlicedBorder 九宫uv值,判定当前uv在九宫的哪一格,按当前格子的缩放进行uv读取
                        //转换到遮罩(0,0)坐标
                        texuv -=_graphicUvMaskForScene.xy;
                        //由屏幕坐标系转到遮罩坐标系
                        texuv *=ratiouv;

                        texuv +=_graphicUvMinMaxClip.xy;
                        if(texuv.x > _GraphicRectSlicedBorder.x && texuv.x < _GraphicRectSlicedBorder.z && texuv.y > _GraphicRectSlicedBorder.y && texuv.y < _GraphicRectSlicedBorder.w)
                        {
                            graphicColor.a = 1;
                        }
                        else
                        {                            
                            //左下角               
                            if(texuv.x< _GraphicRectSlicedBorder.x &&
                               texuv.y <_GraphicRectSlicedBorder.y)
                            {
                                //坐标转换到左下角坐标系(0,1)
                                texuv /= _GraphicRectSlicedBorder.xy;
                                //坐标转换到Texture坐标系
                                texuv *= _GraphicUvSlicedBorderRatio.xy;
                                //遮罩坐标系转到Texture坐标系
                                texuv = texuv * texParams.zw + texParams.xy;
                                //遮罩在屏幕的矩形裁剪
                                //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                                graphicColor = tex2D(_graphicTex,texuv);
                            }
                            //左上角
                            else if(texuv.x< _GraphicRectSlicedBorder.x &&
                             texuv.y >_GraphicRectSlicedBorder.w)
                            {                            
                                //相对于左上角为原点的坐标
                                texuv.y =1 - texuv.y;
                                //坐标转换到左上角坐标系(0,1)
                                texuv.x /= _GraphicRectSlicedBorder.x;
                                texuv.y /= (1 - _GraphicRectSlicedBorder.w);
                                //坐标转换到Texture坐标系
                                texuv *= _GraphicUvSlicedBorderRatio.xw;
                                texuv.y = 1 - texuv.y;
                                //遮罩坐标系转到Texture坐标系
                                texuv = texuv * texParams.zw + texParams.xy;
                                //遮罩在屏幕的矩形裁剪
                                //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                                graphicColor = tex2D(_graphicTex,texuv);
                            }
                            //右上角
                            else if(texuv.x > _GraphicRectSlicedBorder.z &&
                             texuv.y >_GraphicRectSlicedBorder.w)
                            {                            
                                //相对于左上角为原点的坐标
                                texuv =1 - texuv;
                                //坐标转换到左上角坐标系(0,1)
                                texuv /= (1 -_GraphicRectSlicedBorder.zw);
                                //坐标转换到Texture坐标系
                                texuv *= _GraphicUvSlicedBorderRatio.zw;
                                texuv = 1 - texuv;
                                //遮罩坐标系转到Texture坐标系
                                texuv = texuv * texParams.zw + texParams.xy;
                                //遮罩在屏幕的矩形裁剪
                                //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                                graphicColor = tex2D(_graphicTex,texuv);
                            }
                             //右下角
                            else if(texuv.x > _GraphicRectSlicedBorder.z &&
                             texuv.y < _GraphicRectSlicedBorder.y)
                            {                            
                                //相对于左上角为原点的坐标
                                texuv.x =1 - texuv.x;
                                //坐标转换到左上角坐标系(0,1)
                                texuv.x /= (1 -_GraphicRectSlicedBorder.z);
                                texuv.y /= _GraphicRectSlicedBorder.y;
                                //坐标转换到Texture坐标系
                                texuv *= _GraphicUvSlicedBorderRatio.zy;
                                texuv.x = 1 - texuv.x;
                                //遮罩坐标系转到Texture坐标系
                                texuv = texuv * texParams.zw + texParams.xy;
                                //遮罩在屏幕的矩形裁剪
                                //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                                graphicColor = tex2D(_graphicTex,texuv);
                            }
                            //左边
                            else if(texuv.x< _GraphicRectSlicedBorder.x &&
                               texuv.y > _GraphicRectSlicedBorder.y && texuv.y < _GraphicRectSlicedBorder.w)
                            {
                                //相对于左边左下角为原点的坐标
                                texuv.y -= _GraphicRectSlicedBorder.y;
                                //坐标转换到左下角坐标系(0,1)
                                texuv.x /= _GraphicRectSlicedBorder.x;
                                texuv.y /= _GraphicRectSlicedBorder.w - _GraphicRectSlicedBorder.y;                                
                                //坐标转换到Texture坐标系
                                texuv.x *= _GraphicUvSlicedBorderRatio.x;
                                texuv.y *= 1 - _GraphicUvSlicedBorderRatio.w - _GraphicUvSlicedBorderRatio.y;
                                //坐标补上左下角的y值
                                texuv.y += _GraphicUvSlicedBorderRatio.y;
                                //遮罩坐标系转到Texture坐标系
                                texuv = texuv * texParams.zw + texParams.xy;
                                //遮罩在屏幕的矩形裁剪
                                //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                                graphicColor = tex2D(_graphicTex,texuv);
                            }
                            //右边
                            else if(texuv.x > _GraphicRectSlicedBorder.z &&
                               texuv.y > _GraphicRectSlicedBorder.y && texuv.y < _GraphicRectSlicedBorder.w)
                            {
                                //相对于右边右上角为原点的坐标
                                texuv = 1 - texuv;
                                texuv.y -= 1-_GraphicRectSlicedBorder.w;
                                //坐标转换到右上角坐标系(0,1)
                                texuv.x /= 1 - _GraphicRectSlicedBorder.z;
                                texuv.y /= _GraphicRectSlicedBorder.w - _GraphicRectSlicedBorder.y;                                
                                //坐标转换到Texture坐标系
                                texuv.x *= _GraphicUvSlicedBorderRatio.z;
                                texuv.y *= 1 - _GraphicUvSlicedBorderRatio.w - _GraphicUvSlicedBorderRatio.y;
                                //坐标补上左下角的y值
                                texuv.y += _GraphicUvSlicedBorderRatio.w;
                                texuv = 1-texuv;
                                //遮罩坐标系转到Texture坐标系
                                texuv = texuv * texParams.zw + texParams.xy;
                                //遮罩在屏幕的矩形裁剪
                                //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                                graphicColor = tex2D(_graphicTex,texuv);
                            }
                            //下边
                            else if(texuv.x > _GraphicRectSlicedBorder.x && texuv.x < _GraphicRectSlicedBorder.z &&
                               texuv.y < _GraphicRectSlicedBorder.y)
                            {
                                //相对于下边左下角为原点的坐标
                                texuv.x -= _GraphicRectSlicedBorder.x;
                                //坐标转换到左下角坐标系(0,1)
                                texuv.x /=_GraphicRectSlicedBorder.z - _GraphicRectSlicedBorder.x;     
                                texuv.y /= _GraphicRectSlicedBorder.y;
                                //坐标转换到Texture坐标系
                                texuv.x *= 1 - _GraphicUvSlicedBorderRatio.z - _GraphicUvSlicedBorderRatio.x;
                                texuv.y *= _GraphicUvSlicedBorderRatio.y;
                                //坐标补上左下角的x值
                                texuv.x += _GraphicUvSlicedBorderRatio.x;
                                //遮罩坐标系转到Texture坐标系
                                texuv = texuv * texParams.zw + texParams.xy;
                                //遮罩在屏幕的矩形裁剪
                                //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                                graphicColor = tex2D(_graphicTex,texuv);                                
                            }
                             //上边
                            else if(texuv.x > _GraphicRectSlicedBorder.x && texuv.x < _GraphicRectSlicedBorder.z &&
                               texuv.y > _GraphicRectSlicedBorder.w)
                            {
                                //相对于上边右上角为原点的坐标
                                texuv = 1 - texuv;
                                texuv.x -= 1- _GraphicRectSlicedBorder.z;
                                texuv.y -= 1-_GraphicRectSlicedBorder.w;
                                //坐标转换到右上角坐标系(0,1)
                                texuv.x /= _GraphicRectSlicedBorder.z - _GraphicRectSlicedBorder.x;
                                texuv.y /= 1 -_GraphicRectSlicedBorder.w;
                                //坐标转换到Texture坐标系
                                texuv.x *= 1 - _GraphicUvSlicedBorderRatio.z - _GraphicUvSlicedBorderRatio.x;
                                texuv.y *= _GraphicUvSlicedBorderRatio.w;
                                //坐标补上右上角的xy值
                                texuv.x += _GraphicUvSlicedBorderRatio.z;
                                texuv.y += _GraphicUvSlicedBorderRatio.w;
                                texuv = 1-texuv;
                                //遮罩坐标系转到Texture坐标系
                                texuv = texuv * texParams.zw + texParams.xy;
                                //遮罩在屏幕的矩形裁剪
                                //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                                graphicColor = tex2D(_graphicTex,texuv);
                            }                            
                        }                        
                        alpha = 1-graphicColor.a;
                    }
                    else
                    {
                        //转换到遮罩(0,0)坐标
                        texuv -=_graphicUvMaskForScene.xy;
                        //由屏幕坐标系转到遮罩坐标系
                        texuv *=ratiouv;
                        //遮罩在屏幕的矩形裁剪
                        texuv +=_graphicUvMinMaxClip.xy;
                        //遮罩坐标系转到Texture坐标系
                        texuv = texuv * texParams.zw + texParams.xy;
                        //遮罩在屏幕的矩形裁剪
                        //texuv +=_graphicUvMinMaxClip.xy * texParams.zw;
                        graphicColor = tex2D(_graphicTex,texuv);
                        alpha = 1-graphicColor.a;
                    }                    
                }              
                return alpha;
            }             
            

            fixed4 frag(v2f IN) : SV_Target
			{
				half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
                //遮罩颜色混合
                fixed alpha = fragFillMask(IN.texcoord,_GraphicTex0,_SceneGraphicWh0,_GraphicUvMinMax0,_GraphicUvMinMaxClip0,_GraphicUvMaskForScene0,_isGraphicSliced0,_GraphicRectSlicedBorder0,_GraphicUvSlicedBorderRatio0);
                alpha *= fragFillMask(IN.texcoord,_GraphicTex1,_SceneGraphicWh1,_GraphicUvMinMax1,_GraphicUvMinMaxClip1,_GraphicUvMaskForScene1,_isGraphicSliced1,_GraphicRectSlicedBorder1,_GraphicUvSlicedBorderRatio1);
                color.a *=alpha;
                //裁剪
				color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
				#ifdef UNITY_UI_ALPHACLIP
				clip (color.a - 0.001);
				#endif

                float4 mask = tex2D(_MaskTex,IN.texcoord);                
				return color;
			}
		ENDCG
		}
	}
}

UIGraphicMaskVertex.cs

using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 绘制遮罩【顶点版】
/// 依赖项
/// 1.UIGraphicMaskVertex.shader
/// </summary>
[AddComponentMenu("Game/UI/GraphicMask/Vertex")]
public class UIGraphicMaskVertex : AbsUIGraphicMask
{
    #region OnPopulateMesh
    /// <summary>
    /// Fill the vertex buffer data.
    /// </summary>
    /// <param name="_vh">顶点Helper</param>
    protected override void OnPopulateMesh(VertexHelper _vh)
    {
        if (graphicMasks != null && graphicMasks.Count > 0)
        {
            switch (graphicClassify)
            {
                case enGraphicMaskClassify.Scene:
                    OnFillScene(_vh, graphicMasks);
                    break;
                case enGraphicMaskClassify.Mask:
                    OnFillMask(_vh, graphicMasks);
                    break;
                case enGraphicMaskClassify.ExceptMask:
                    OnFillExceptMask(_vh, graphicMasks);
                    break;
            }
        }
        else
        {
            base.OnPopulateMesh(_vh);
        }
    }
    #endregion

    #region OnFillScene 全屏填充
    /// <summary>
    /// 全屏填充
    /// </summary>
    /// <param name="_vh">顶点Helper</param>
    /// <param name="_masks">绘制组</param>
    protected virtual void OnFillScene(VertexHelper _vh, List<Graphic> _masks)
    {
        _vh.Clear();
        List<UIVertex> vertexs = new List<UIVertex>();
        Vector3[] fourCorners = null;
        Vector4 uv = (sprite != null) ? UnityEngine.Sprites.DataUtility.GetOuterUV(sprite) : Vector4.zero;
        this.GraphicLocalCornersForVertexHelper(out fourCorners);
        vertexs.Add(new UIVertex() { position = fourCorners[0], color = color, uv0 = new Vector2(uv.x, uv.y) });
        vertexs.Add(new UIVertex() { position = fourCorners[1], color = color, uv0 = new Vector2(uv.x, uv.w) });
        vertexs.Add(new UIVertex() { position = fourCorners[2], color = color, uv0 = new Vector2(uv.z, uv.w) });
        vertexs.Add(new UIVertex() { position = fourCorners[3], color = color, uv0 = new Vector2(uv.z, uv.y) });
        _vh.AddUIVertexQuad(vertexs.ToArray());
    }
    #endregion

    #region OnFillMask 仅填充遮罩
    /// <summary>
    /// 仅填充遮罩
    /// </summary>
    /// <param name="_vh">顶点Helper</param>
    /// <param name="_masks">绘制组</param>
    protected virtual void OnFillMask(VertexHelper _vh, List<Graphic> _masks)
    {
        _vh.Clear();
        List<UIVertex> vertexs = new List<UIVertex>();
        Vector3[] fourCorners = null;
        GraphicRect2D sceneRect = new GraphicRect2D(-1, this);
        Vector4 uv = (sprite != null) ? UnityEngine.Sprites.DataUtility.GetOuterUV(sprite) : Vector4.zero;
        foreach (Graphic g in _masks)
        {
            g.GraphicLocalCornersForVertexHelper(out fourCorners);
            vertexs = new List<UIVertex>();
            switch (graphicSpriteFillClassify)
            {
                case enGraphicSpriteFillClassify.SceneRatioFill:
                    fourCorners[0].x = Mathf.Clamp(fourCorners[0].x, sceneRect.rect.xMin, sceneRect.rect.xMax);
                    fourCorners[0].y = Mathf.Clamp(fourCorners[0].y, sceneRect.rect.yMin, sceneRect.rect.yMax);
                    vertexs.Add(new UIVertex() { position = fourCorners[0], color = color, uv0 = Rect.PointToNormalized(sceneRect.rect, fourCorners[0]) });

                    fourCorners[1].x = Mathf.Clamp(fourCorners[1].x, sceneRect.rect.xMin, sceneRect.rect.xMax);
                    fourCorners[1].y = Mathf.Clamp(fourCorners[1].y, sceneRect.rect.yMin, sceneRect.rect.yMax);
                    vertexs.Add(new UIVertex() { position = fourCorners[1], color = color, uv0 = Rect.PointToNormalized(sceneRect.rect, fourCorners[1]) });

                    fourCorners[2].x = Mathf.Clamp(fourCorners[2].x, sceneRect.rect.xMin, sceneRect.rect.xMax);
                    fourCorners[2].y = Mathf.Clamp(fourCorners[2].y, sceneRect.rect.yMin, sceneRect.rect.yMax);
                    vertexs.Add(new UIVertex() { position = fourCorners[2], color = color, uv0 = Rect.PointToNormalized(sceneRect.rect, fourCorners[2]) });

                    fourCorners[3].x = Mathf.Clamp(fourCorners[3].x, sceneRect.rect.xMin, sceneRect.rect.xMax);
                    fourCorners[3].y = Mathf.Clamp(fourCorners[3].y, sceneRect.rect.yMin, sceneRect.rect.yMax);
                    vertexs.Add(new UIVertex() { position = fourCorners[3], color = color, uv0 = Rect.PointToNormalized(sceneRect.rect, fourCorners[3]) });
                    break;
                case enGraphicSpriteFillClassify.MaskAloneFill:
                    vertexs.Add(new UIVertex() { position = fourCorners[0], color = color, uv0 = new Vector2(uv.x, uv.y) });
                    vertexs.Add(new UIVertex() { position = fourCorners[1], color = color, uv0 = new Vector2(uv.x, uv.w) });
                    vertexs.Add(new UIVertex() { position = fourCorners[2], color = color, uv0 = new Vector2(uv.z, uv.w) });
                    vertexs.Add(new UIVertex() { position = fourCorners[3], color = color, uv0 = new Vector2(uv.z, uv.y) });
                    break;
            }
            _vh.AddUIVertexQuad(vertexs.ToArray());
        }
    }
    #endregion

    #region OnFillExceptMask 除遮罩之外都填充
    /// <summary>
    /// 三角剖分结果
    /// </summary>
    PolygonDelaunayResult2D mDelaunayResult = null;
    /// <summary>
    /// 除遮罩之外都填充
    /// </summary>
    /// <param name="_vh">顶点Helper</param>
    /// <param name="_masks">绘制组</param>
    protected virtual void OnFillExceptMask(VertexHelper _vh, List<Graphic> _masks)
    {
        #region 屏幕矩形
        GraphicRect2D sceneRect = new GraphicRect2D(-1, this);
        #endregion

        #region 遮罩矩形
        List<GraphicRect2D> maskRect = new List<GraphicRect2D>();
        GraphicRect2D rect;
        for (int i = 0; i < _masks.Count; i++)
        {
            rect = new GraphicRect2D(i, _masks[i]);
            maskRect.Add(rect);
        }

        //求两矩形填充点和矩形在屏幕上的填充点
        List<Vector3> useVertexs = new List<Vector3>();
        List<Vector3> tempVertexs = new List<Vector3>();
        List<Vector3> validateVertexs = new List<Vector3>();
        if (maskRect.Count <= 1)
        {
            tempVertexs = maskRect[0].IntersectionFill(sceneRect);
            if (tempVertexs.Count > 0)
            {
                validateVertexs.AddRange(tempVertexs);
            }
        }
        else
        {
            foreach (GraphicRect2D a in maskRect)
            {
                foreach (GraphicRect2D b in maskRect)
                {
                    tempVertexs = a.IntersectionFill(b);
                    if (tempVertexs.Count > 0)
                    {
                        validateVertexs.AddRange(tempVertexs);
                    }
                }
            }
        }
        #endregion

        validateVertexs.InsertRange(0, sceneRect.corners);

        #region 清理重复的点
        List<int> hasPoint = new List<int>();
        int pk = 0;
        Vector3 tp = Vector3.zero;
        foreach (Vector3 cv in validateVertexs)
        {
            //顶点不超过屏幕
            tp.x = cv.x;
            tp.x = Mathf.Max(tp.x, sceneRect.rect.xMin);
            tp.x = Mathf.Min(tp.x, sceneRect.rect.xMax);
            tp.y = cv.y;
            tp.y = Mathf.Max(tp.y, sceneRect.rect.yMin);
            tp.y = Mathf.Min(tp.y, sceneRect.rect.yMax);
            //过滤重复点
            pk = (tp.x.ToString() + tp.y.ToString()).UniqueHashCode();
            if (!hasPoint.Contains(pk))
            {
                hasPoint.Add(pk);
                useVertexs.Add(tp);
            }
        }
        #endregion

        mDelaunayResult = Polygon2DUtility.BuilderDelaunay2D(useVertexs, new List<int>() { 0, 1, 2, 3 });

        #region 绘制三角面
        _vh.Clear();
        Vector4 uv = (sprite != null) ? UnityEngine.Sprites.DataUtility.GetOuterUV(sprite) : Vector4.zero;
        UIVertex[] vertexs = new UIVertex[mDelaunayResult.vertexs.Count];
        List<int> vertexIndexs = new List<int>();
        foreach (PolygonPoint2D p in mDelaunayResult.vertexs)
        {
            vertexs[p.index] = new UIVertex() { position = p.position, color = color, uv0 = Rect.PointToNormalized(sceneRect.rect, p.position) };
        }

        bool isInAnyRect = false;
        foreach (PolygonTriangle2D tri in mDelaunayResult.delaunayTriangle)
        {
            foreach (GraphicRect2D r in maskRect)
            {
                isInAnyRect =
                    Geometry2DUtility.IsPointInRect(tri.vertexs[0].position, r.rect) &&
                    Geometry2DUtility.IsPointInRect(tri.vertexs[1].position, r.rect) &&
                    Geometry2DUtility.IsPointInRect(tri.vertexs[2].position, r.rect);
                if (isInAnyRect)
                {
                    break;
                }
            }
            if (!isInAnyRect)
            {
                vertexIndexs.Add(tri.vertexs[0].index);
                vertexIndexs.Add(tri.vertexs[1].index);
                vertexIndexs.Add(tri.vertexs[2].index);
            }
        }
        _vh.AddUIVertexStream(new List<UIVertex>(vertexs), vertexIndexs);
        #endregion
    }
    /// <summary>
    /// Graphic矩形2D
    /// </summary>
    class GraphicRect2D
    {
        /// <summary>
        /// 索引
        /// </summary>
        public int index { get; private set; }
        /// <summary>
        /// 四角
        /// </summary>
        public Vector3[] corners { get; private set; }
        /// <summary>
        /// 矩形
        /// </summary>
        public Rect rect { get; private set; }
        /// <summary>
        /// 包围盒
        /// </summary>
        public Bounds bounds { get; private set; }
        /// <summary>
        /// 矩形四角
        /// </summary>
        /// <param name="_index">矩形索引</param>
        /// <param name="_graphic">Graphic</param>
        public GraphicRect2D(int _index, Graphic _graphic)
        {
            index = _index;
            Vector3[] cns = null;
            rect = _graphic.GraphicLocalRectForVertexHelper(ref cns);
            bounds = rect.TransRectToBounds();
            corners = cns;
        }

        /// <summary>
        /// 求矩形B在当前矩形上的投影填充点
        /// </summary>
        /// <param name="_b">矩形B</param>
        /// <returns>填充点</returns>
        public List<Vector3> IntersectionFill(GraphicRect2D _b)
        {
            List<Vector3> vertexs = new List<Vector3>();
            if (index != _b.index)
            {
                Vector3 va = Vector3.zero;
                Vector3 vb = Vector3.zero;
                Vector3 vp = Vector3.zero;
                Vector3 ab = Vector3.zero;
                Vector3 ap = Vector3.zero;
                Vector3 pab = Vector3.zero;
                for (int i = 0; i < corners.Length; i++)
                {
                    va = corners[i];
                    vertexs.Add(va);
                    if (i >= corners.Length - 1)
                    {
                        vb = corners[0];
                    }
                    else
                    {
                        vb = corners[i + 1];
                    }
                    for (int p = 0; p < _b.corners.Length; p++)
                    {
                        vp = _b.corners[p];
                        ab = vb - va;
                        ap = vp - va;
                        //P点在AB上的投影点pab
                        pab = Vector3.Project(ap, ab);
                        //如果投影点的方向与ab方向一致,并且ab的长度大于ap的长度,则投影点在ab边上,为矩形交点
                        if (Vector3.Dot(ab, pab) >= 0 && ab.magnitude >= ap.magnitude)
                        {
                            vertexs.Add(va + pab);
                        }
                    }
                }
            }
            return vertexs;
        }
    }
    #endregion

    #region OnDrawGizmos
#if UNITY_EDITOR
    /// <summary>
    /// OnDrawGizmos
    /// </summary>
    void OnDrawGizmos()
    {
        if (mDelaunayResult != null && mDelaunayResult.vertexs != null)
        {
            Gizmos.color = Color.yellow;
            float raudis = Vector3.Distance(mDelaunayResult.bounds.min, mDelaunayResult.bounds.max) * 0.5f;
            foreach (PolygonPoint2D point in mDelaunayResult.vertexs)
            {
                Gizmos.DrawSphere(point.position, raudis / mDelaunayResult.vertexs.Count * 0.3f);
            }
            Gizmos.color = Color.yellow;
            Gizmos.DrawLine(mDelaunayResult.bounds.min, mDelaunayResult.bounds.max);
            Gizmos.color = Color.red;
            Gizmos.DrawWireCube(mDelaunayResult.bounds.center, mDelaunayResult.bounds.size);
            Gizmos.color = Color.grey;
            Gizmos.DrawWireSphere(mDelaunayResult.bounds.center, raudis);
            Gizmos.color = Color.green;
            Gizmos.DrawSphere(mDelaunayResult.supperTriangle.vertexs[0].position, raudis / mDelaunayResult.vertexs.Count * 0.3f);
            Gizmos.DrawSphere(mDelaunayResult.supperTriangle.vertexs[1].position, raudis / mDelaunayResult.vertexs.Count * 0.3f);
            Gizmos.DrawSphere(mDelaunayResult.supperTriangle.vertexs[2].position, raudis / mDelaunayResult.vertexs.Count * 0.3f);

            Gizmos.DrawLine(mDelaunayResult.supperTriangle.vertexs[0].position, mDelaunayResult.supperTriangle.vertexs[1].position);
            Gizmos.DrawLine(mDelaunayResult.supperTriangle.vertexs[1].position, mDelaunayResult.supperTriangle.vertexs[2].position);
            Gizmos.DrawLine(mDelaunayResult.supperTriangle.vertexs[2].position, mDelaunayResult.supperTriangle.vertexs[0].position);

            Light light = FindObjectOfType<Light>();
            if (light != null)
            {
                light.transform.position = mDelaunayResult.bounds.center;
            }

            foreach (PolygonTriangle2D tri in mDelaunayResult.delaunayTriangle)
            {
                Gizmos.color = Color.cyan;
                Gizmos.DrawLine(tri.vertexs[0].position, tri.vertexs[1].position);
                Gizmos.DrawLine(tri.vertexs[1].position, tri.vertexs[2].position);
                Gizmos.DrawLine(tri.vertexs[2].position, tri.vertexs[0].position);
                //Gizmos.color = Color.white;
                //Gizmos.DrawWireSphere(tri.circumCircleCenter, tri.circumCircleRadius);
            }
        }
    }
#endif
    #endregion
}

UIGraphicMaskVertex.shader

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Game/UI/GraphicMask/Vertex"
{
	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

		[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
		{
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

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

			#pragma multi_compile __ UNITY_UI_ALPHACLIP
			
			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color    : COLOR;
				half2 texcoord  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
			};
			
			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;
			v2f vert(appdata_t IN)
			{
				v2f OUT;
				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

				OUT.texcoord = IN.texcoord;
				
				#ifdef UNITY_HALF_TEXEL_OFFSET
				OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
				#endif
				
				OUT.color = IN.color * _Color;
				return OUT;
			}

			sampler2D _MainTex;
            sampler2D _MaskTex;
			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

                float4 mask = tex2D(_MaskTex,IN.texcoord);
				return color;
			}
		ENDCG
		}
	}
}