UGUI Text描边实现

参考链接

当文字贴图发生变化时 描边计算错误的问题 描边显示会有异常 粗细不一

 修改后的代码

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

/// <summary>
/// 顶点外扩
/// </summary>
public class OutlineEx : BaseMeshEffect
{
    private const string FONT_TEX_SIZE_NAME = "_FontTexSize";
    private const string OUTLINE_COLOR_NAME = "_OutlineColor";
    private const string OUTLINE_WIDTH_NAME = "_OutlineWidth";
    private const string SHADER_PATH = "UI/OutlineEx";

    [Range(0, 3)] public float outlineWidth = 1.5f;
    public float width = 512;
    public float height = 512;

    [Header("默认使用Base/Scripts/Outline.mat, 如果需要修改描边颜色这个为null")] 
    public Material material;
    private Text text;

    public Color outlineColor = Color.black;
    private static List<UIVertex> vetexList = new List<UIVertex>();

    protected override void Awake()
    {
        base.Awake();
        text = GetComponent<Text>();
        UpdateMaterial();
    }

    protected override void Start()
    {
        base.Start();
        UpdateFontMainTexTexelSize(text.font);
        UpdateAdditionalShaderChannels();
        Font.textureRebuilt += TextTextureRebuild;
    }

    private void UpdateAdditionalShaderChannels()
    {
        if (graphic == null || graphic.canvas == null)
            return;

        var v1 = graphic.canvas.additionalShaderChannels;
        var v2 = AdditionalCanvasShaderChannels.TexCoord1;

        if ((v1 & v2) != v2)
        {
            graphic.canvas.additionalShaderChannels |= v2;
        }

        v2 = AdditionalCanvasShaderChannels.TexCoord2;
        if ((v1 & v2) != v2)
        {
            graphic.canvas.additionalShaderChannels |= v2;
        }
    }

    private void TextTextureRebuild(Font font)
    {
        if (this == null)
            return;

        if (text == null)
            text = GetComponent<Text>();

        if (text == null)
        {
            Logger.Error("no find text com, name: ", gameObject.name);
            return;
        }

        if (text.font == font)
        {
            UpdateFontMainTexTexelSize(font);
        }
    }

    private void UpdateFontMainTexTexelSize(Font font)
    {
        if (material == null)
            return;

        if (font == null || font.material == null || font.material.mainTexture == null)
            return;

        width = font.material.mainTexture.width;
        height = font.material.mainTexture.height;
        Vector4 vector = new Vector4(1.0f / width, 1.0f / height, width, height);
        material.SetVector(FONT_TEX_SIZE_NAME, vector);
    }

    private void SetParam()
    {
        material.SetColor(OUTLINE_COLOR_NAME, outlineColor);
        material.SetFloat(OUTLINE_WIDTH_NAME, outlineWidth);
    }

    private void UpdateMaterial()
    {
        if (graphic == null)
        {
            Logger.Error("base.graphic == null");
            return;
        }

        if (material == null)
            material = new Material(Shader.Find(SHADER_PATH));

        graphic.material = material;
    }

    public override void ModifyMesh(VertexHelper vh)
    {
        SetParam();
        vh.GetUIVertexStream(vetexList);

        ProcessVertices();

        vh.Clear();
        vh.AddUIVertexTriangleStream(vetexList);
    }

    private void ProcessVertices()
    {
        for (int i = 0, count = vetexList.Count - 3; i <= count; i += 3)
        {
            var v1 = vetexList[i];
            var v2 = vetexList[i + 1];
            var v3 = vetexList[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);
            var uvOrigin = new Vector4(uvMin.x, uvMin.y, uvMax.x, uvMax.y);
            // 为每个顶点设置新的Position和UV,并传入原始UV框
            //
            v1 = SetNewPosAndUV(v1, outlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
            v2 = SetNewPosAndUV(v2, outlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
            v3 = SetNewPosAndUV(v3, outlineWidth, posCenter, triX, triY, uvX, uvY, uvOrigin);
            // 应用设置后的UIVertex
            //
            vetexList[i] = v1;
            vetexList[i + 1] = v2;
            vetexList[i + 2] = v3;
        }
    }

    private static UIVertex SetNewPosAndUV(UIVertex pVertex, float pOutLineWidth,
        Vector2 pPosCenter,
        Vector2 pTriangleX, Vector2 pTriangleY,
        Vector2 pUVX, Vector2 pUVY,
        Vector4 pUVOrigin)
    {
        // Position
        var pos = pVertex.position;
        var posXOffset = pos.x > pPosCenter.x ? pOutLineWidth : -pOutLineWidth;
        var posYOffset = pos.y > pPosCenter.y ? pOutLineWidth : -pOutLineWidth;
        pos.x += posXOffset;
        pos.y += posYOffset;
        pVertex.position = pos;

        // UV
        var uv = pVertex.uv0;
        uv += new Vector4(
            (pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1)).x,
            (pUVX / pTriangleX.magnitude * posXOffset * (Vector2.Dot(pTriangleX, Vector2.right) > 0 ? 1 : -1)).y,
            0, 0);

        uv += new Vector4(
            (pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1)).x,
            (pUVY / pTriangleY.magnitude * posYOffset * (Vector2.Dot(pTriangleY, Vector2.up) > 0 ? 1 : -1)).y,
            0, 0);

        pVertex.uv0 = uv;

        // 原始UV框
        pVertex.uv1 = new Vector2(pUVOrigin.x, pUVOrigin.y);
        pVertex.uv2 = new Vector2(pUVOrigin.z, pUVOrigin.w);

        return pVertex;
    }

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

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

    private 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));
    }

    private 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));
    }

    protected override void OnDestroy()
    {
        base.OnDestroy();
        vetexList?.Clear();
        Font.textureRebuilt -= TextTextureRebuild;
    }
}

Shader "UI/OutlineEx"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "white" {}
        _Color("Tint", Color) = (1, 1, 1, 1)
        _OutlineColor("Outline Color", Color) = (1, 1, 1, 1)
        _OutlineWidth("Outline Width", Float) = 1
        _FontTexSize("Font Tex Size", Vector) = (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
            {
                Name "OUTLINE"

                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag

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

                sampler2D _MainTex;
                float4 _Color;
                float4 _TextureSampleAdd;
                float4 _FontTexSize;

                float4 _OutlineColor;
                float _OutlineWidth;
                //Add for RectMask2D  
                float4 _ClipRect;
                //End for RectMask2D

                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 texcoord : TEXCOORD0;
                    float2 texcoord1 : TEXCOORD1;
                    float2 texcoord2 : TEXCOORD2;
                    float4 color : COLOR;
                };

                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    float2 texcoord : TEXCOORD0;
                    float2 uvOriginXY : TEXCOORD1;
                    float2 uvOriginZW : TEXCOORD2;
                    //Add for RectMask2D  
                    float4 worldPosition : TEXCOORD4;
                    //End for RectMask2D

                    float4 color : COLOR;
                };

                v2f vert(appdata IN)
                {
                    v2f o;
                    //Add for RectMask2D  
                    o.worldPosition = IN.vertex;
                    //End for RectMask2D

                    o.vertex = UnityObjectToClipPos(IN.vertex);
                    o.texcoord = IN.texcoord;
                    o.uvOriginXY = IN.texcoord1;
                    o.uvOriginZW = IN.texcoord2;
                    o.color = IN.color * _Color;

                    return o;
                }

                float IsInRect(float2 pPos, float2 pClipRectXY, float2 pClipRectZW)
                {
                    pPos = step(pClipRectXY, pPos) * step(pPos, pClipRectZW);
                    return pPos.x * pPos.y;
                }

                float SampleAlpha(int pIndex, v2f IN)
                {
                    const float sinArray[12] = { 0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -0.5 };
                    const float 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 + _FontTexSize.xy * float2(cosArray[pIndex], sinArray[pIndex]) * _OutlineWidth;
                    return IsInRect(pos, IN.uvOriginXY, IN.uvOriginZW) * (tex2D(_MainTex, pos) + _TextureSampleAdd).w * _OutlineColor.w;
                }

                float4 frag(v2f IN) : SV_Target
                {
                    float4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
                    if (_OutlineWidth > 0)
                    {
                        color.w *= IsInRect(IN.texcoord, IN.uvOriginXY, IN.uvOriginZW);
                        float4 val = float4(_OutlineColor.x, _OutlineColor.y, _OutlineColor.z, 0);

                        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);

                        val.w = clamp(val.w, 0, 1);
                        color = (val * (1.0 - color.a)) + (color * 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
            }
        }
}

效果图

猜你喜欢

转载自blog.csdn.net/qq_25670983/article/details/132581691