Unity中实现UI描边

Posted Hello Bug.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity中实现UI描边相关的知识,希望对你有一定的参考价值。

一:效果演示


 二:使用


MaterialPath:材质路径(需要自己创建材质并设置Shader为UI/Outline)
Outline Color:描边颜色
Outline Width:描边宽度


三:为什么尽量避免使用UGUI的Outline和Shadaw组件 

UGUI源码解析——Shadow
UGUI源码解析——Outline
不论是Outline还是Shadow,它的实现原理都是将原网格数据复制一份并向指定方向移动指定像素,然后再填充到顶点数据中,所以顶点数和三角面数Shadow会增加一倍,Outline会增加四倍


四:代码实现

Shadar:

Shader "UI/Outline" 

    Properties
    
        _MainTex ("Main Texture", 2D) = "white" 
        _Color ("Tint", Color) = (1, 1, 1, 1)
        //_OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
        //_OutlineWidth ("Outline Width", Int) = 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
        
            Name "OUTLINE"

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //Add for RectMask2D  
            #include "UnityUI.cginc"
            //End for RectMask2D  

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _MainTex_TexelSize;

            //float4 _OutlineColor;
            //int _OutlineWidth;

            //Add for RectMask2D  
            float4 _ClipRect;
            //End for RectMask2D

			struct appdata
			
				float4 vertex : POSITION;
				float4 tangent : TANGENT;
				float4 normal : NORMAL;
				float2 texcoord : TEXCOORD0;
				float2 uv1 : TEXCOORD1;
				float2 uv2 : TEXCOORD2;
				float2 uv3 : TEXCOORD3;
				fixed4 color : COLOR;
			;


            struct v2f
            
                float4 vertex : SV_POSITION;
				float4 tangent : TANGENT;
				float4 normal : NORMAL;
                float2 texcoord : TEXCOORD0;
                float2 uv1 : TEXCOORD1;
				float2 uv2 : TEXCOORD2;
				float2 uv3 : TEXCOORD3;
                //Add for RectMask2D  
                float4 worldPosition : TEXCOORD4;
                //End for RectMask2D
                fixed4 color : COLOR;
            ;

            v2f vert(appdata IN)
            
                v2f o;

                //Add for RectMask2D  
                o.worldPosition = IN.vertex;
                //End for RectMask2D 

                o.vertex = UnityObjectToClipPos(IN.vertex);
                o.tangent = IN.tangent;
                o.texcoord = IN.texcoord;
                o.color = IN.color * _Color;
				o.uv1 = IN.uv1;
				o.uv2 = IN.uv2;
				o.uv3 = IN.uv3;
				o.normal = IN.normal;

                return o;
            
			/*
            fixed IsInRect(float2 pPos, float4 pClipRect)
            
                pPos = step(pClipRect.xy, pPos) * step(pPos, pClipRect.zw);
                return pPos.x * pPos.y;
            
			*/
			fixed IsInRect(float2 pPos, float2 pClipRectMin, float2 pClipRectMax)
			
				pPos = step(pClipRectMin, pPos) * step(pPos, pClipRectMax);
				return pPos.x * pPos.y;
			

			fixed SampleAlpha(int pIndex, v2f IN)
			
				const fixed sinArray[12] =  0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5 ;
				const fixed cosArray[12] =  1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866 ;
				float2 pos = IN.texcoord + _MainTex_TexelSize.xy * float2(cosArray[pIndex], sinArray[pIndex]) * IN.normal.z;	//normal.z 存放 _OutlineWidth
				return IsInRect(pos, IN.uv1, IN.uv2) * (tex2D(_MainTex, pos) + _TextureSampleAdd).w * IN.tangent.w;		//tangent.w 存放 _OutlineColor.w
			

			fixed4 frag(v2f IN) : SV_Target
			
				fixed4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
				if (IN.normal.z > 0)	//normal.z 存放 _OutlineWidth
				
					color.w *= IsInRect(IN.texcoord, IN.uv1, IN.uv2);	//uv1 uv2 存着原始字的uv长方形区域大小
					half4 val = half4(IN.uv3.x, IN.uv3.y, IN.tangent.z, 0);		//uv3.xy tangent.z 分别存放着 _OutlineColor的rgb

					val.w += SampleAlpha(0, IN);
					val.w += SampleAlpha(1, IN);
					val.w += SampleAlpha(2, IN);
					val.w += SampleAlpha(3, IN);
					val.w += SampleAlpha(4, IN);
					val.w += SampleAlpha(5, IN);
					val.w += SampleAlpha(6, IN);
					val.w += SampleAlpha(7, IN);
					val.w += SampleAlpha(8, IN);
					val.w += SampleAlpha(9, IN);
					val.w += SampleAlpha(10, IN);
					val.w += SampleAlpha(11, IN);

					color = (val * (1.0 - color.a)) + (color * color.a);
                    color.a = saturate(color.a);
                    color.a *= IN.color.a*IN.color.a*IN.color.a;	//字逐渐隐藏时,描边也要隐藏

				

                //Add for RectMask2D 
                color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
#ifdef UNITY_UI_ALPHACLIP
                clip(color.a - 0.001);
#endif
                //End for RectMask2D 

				return color;
			

            ENDCG
        
    

CS:

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

/// <summary>
/// 描边
/// </summary>
[DisallowMultipleComponent]
[AddComponentMenu("LFramework/UI/Effects/Outline", 2)]
public class Outline : BaseMeshEffect

    protected Outline()
    

    

    //描边颜色
    [SerializeField]
    Color m_OutlineColor = Color.white;
    public Color OutlineColor
    
        get
        
            return m_OutlineColor;
        
        set
        
            m_OutlineColor = value;
            graphic.SetVerticesDirty();
        
    

    //描边宽度
    [SerializeField]
    [Range(0, 5)]
    int m_OutlineWidth = 1;
    public int OutlineWidth
    
        get
        
            return m_OutlineWidth;
        
        set
        
            m_OutlineWidth = value;
            graphic.SetVerticesDirty();
        
    

    //顶点缓存
    List<UIVertex> m_VertexCache = new List<UIVertex>();

    //材质路径
    const string MaterialPath = "Assets/Materials/UIOutline.mat";

    protected override void Awake()
    
        base.Awake();

        if (graphic != null)
        
            if (graphic.material == null
                || graphic.material.shader.name != "UI/Outline")
            
                LoadOutlineMat();
            
        

        RefreshOutline();
    

    void LoadOutlineMat()
    
#if UNITY_EDITOR
        var mat = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(MaterialPath);
        if (mat != null)
        
            graphic.material = mat;
        
        else
        
            Debug.LogError("没有找到材质Outline.mat");
        
#else
        var shader = Shader.Find("UI/Outline");
        base.graphic.material = new Material(shader);
#endif
    

#if UNITY_EDITOR
    protected override void OnValidate()
    
        base.OnValidate();

        if (graphic != null)
        
            if (graphic.material == null
                || graphic.material.shader.name != "UI/Outline")
            
                LoadOutlineMat();
            
        
    
#endif

    public void RefreshOutline()
    
        if (base.graphic.canvas)
        
            var v1 = base.graphic.canvas.additionalShaderChannels;
            var v2 = AdditionalCanvasShaderChannels.TexCoord1;
            if ((v1 & v2) != v2)
            
                base.graphic.canvas.additionalShaderChannels |= v2;
            
            v2 = AdditionalCanvasShaderChannels.TexCoord2;
            if ((v1 & v2) != v2)
            
                base.graphic.canvas.additionalShaderChannels |= v2;
            
            v2 = AdditionalCanvasShaderChannels.TexCoord3;
            if ((v1 & v2) != v2)
            
                base.graphic.canvas.additionalShaderChannels |= v2;
            
            v2 = AdditionalCanvasShaderChannels.Tangent;
            if ((v1 & v2) != v2)
            
                base.graphic.canvas.additionalShaderChannels |= v2;
            
            v2 = AdditionalCanvasShaderChannels.Normal;
            if ((v1 & v2) != v2)
            
                base.graphic.canvas.additionalShaderChannels |= v2;
            
        
    

    public override void ModifyMesh(VertexHelper vh)
    
        vh.GetUIVertexStream(m_VertexCache);

        ApplyOutline();

        vh.Clear();
        vh.AddUIVertexTriangleStream(m_VertexCache);
        m_VertexCache.Clear();
    

    void ApplyOutline()
    
        for (int i = 0, count = m_VertexCache.Count - 3; i <= count; i += 3)
        
            var v1 = m_VertexCache[i];
            var v2 = m_VertexCache[i + 1];
            var v3 = m_VertexCache[i + 2];
            //计算原顶点坐标中心点
            var minX = Min(v1.position.x, v2.position.x, v3.position.x);
            var minY = Min(v1.position.y, v2.position.y, v3.position.y);
            var maxX = Max(v1.position.x, v2.position.x, v3.position.x);
            var maxY = Max(v1.position.y, v2.position.y, v3.position.y);
            var posCenter = new Vector2(minX + maxX, minY + maxY) * 0.5f;
            //计算原始顶点坐标和UV的方向
            Vector2 triX, triY, uvX, uvY;
            Vector2 pos1 = v1.position;
            Vector2 pos2 = v2.position;
            Vector2 pos3 = v3.position;
            if (Mathf.Abs(Vector2.Dot((pos2 - pos1).normalized, Vector2.right))
                > Mathf.Abs(Vector2.Dot((pos3 - pos2).normalized, Vector2.right)))
            
                triX = pos2 - pos1;
                triY = pos3 - pos2;
                uvX = v2.uv0 - v1.uv0;
                uvY = v3.uv0 - v2.uv0;
            
            else
            
                triX = pos3 - pos2;
                triY = pos2 - pos1;
                uvX = v3.uv0 - v2.uv0;
                uvY = v2.uv0 - v1.uv0;
            
            //计算原始UV框
            var uvMin = Min(v1.uv0, v2.uv0, v3.uv0);
            var uvMax = Max(v1.uv0, v2.uv0, v3.uv0);
            //OutlineColor和OutlineWidth也传入,避免出现不同的材质球
            var col_rg = new Vector2(m_OutlineColor.r, m_OutlineColor.g);
            var col_ba = new Vector4(0, 0, m_OutlineColor.b, m_OutlineColor.a);
            var normal = new Vector3(0, 0, m_OutlineWidth);
            //为每个顶点设置新的Position和UV,并传入原始UV框
            v1 = SetNewPosAndUV(v1, m_OutlineWidth, posCenter, triX, triY, uvX, uvY, uvMin, uvMax);
            v1.uv3 = col_rg;
            v1.tangent = col_ba;
            v1.normal = normal;
            v2 = SetNewPosAndUV(v2, m_OutlineWidth, posCenter, triX, triY, uvX, uvY, uvMin, uvMax);
            v2.uv3 = col_rg;
            v2.tangent = col_ba;
            v2.normal = normal;
            v3 = SetNewPosAndUV(v3, m_OutlineWidth, posCenter, triX, triY, uvX, uvY, uvMin, uvMax);
            v3.uv3 = col_rg;
            v3.tangent = col_ba;
            v3.normal = normal;
            //应用设置后的UIVertex
            m_VertexCache[i] = v1;
            m_VertexCache[i + 1] = v2;
            m_VertexCache[i + 2] = v3;
        
    

    static UIVertex SetNewPosAndUV(UIVertex vertex, int width,
       Vector2 pPosCenter,
       Vector2 triangleX, Vector2 triangleY,
       Vector2 uvX, Vector2 uvY,
       Vector2 uvOriginMin, Vector2 uvOriginMax)
    
        //Position
        var pos = vertex.position;
        var posXOffset = pos.x > pPosCenter.x ? width : -width;
        var posYOffset = pos.y > pPosCenter.y ? width : -width;
        pos.x += posXOffset;
        pos.y += posYOffset;
        vertex.position = pos;
        //UV
        var uv = vertex.uv0;
        uv += uvX / triangleX.magnitude * posXOffset * (Vector2.Dot(triangleX, Vector2.right) > 0 ? 1 : -1);
        uv += uvY / triangleY.magnitude * posYOffset * (Vector2.Dot(triangleY, Vector2.up) > 0 ? 1 : -1);
        vertex.uv0 = uv;

        vertex.uv1 = uvOriginMin;
        vertex.uv2 = uvOriginMax;

        return vertex;
    

    static float Min(float pA, float pB, float pC)
    
        return Mathf.Min(Mathf.Min(pA, pB), pC);
    

    static float Max(float pA, float pB, float pC)
    
        return Mathf.Max(Mathf.Max(pA, pB), pC);
    

    static Vector2 Min(Vector2 pA, Vector2 pB, Vector2 pC)
    
        return new Vector2(Min(pA.x, pB.x, pC.x), Min(pA.y, pB.y, pC.y));
    

    static Vector2 Max(Vector2 pA, Vector2 pB, Vector2 pC)
    
        return new Vector2(Max(pA.x, pB.x, pC.x), Max(pA.y, pB.y, pC.y));
    

以上是关于Unity中实现UI描边的主要内容,如果未能解决你的问题,请参考以下文章

Unity中实现UI渐变

Unity中实现雷达图

Unity中实现雷达图

Unity中实现UI翻转

Unity中实现UI翻转

Unity中实现UI元素的响应区域精确判定