参考链接
当文字贴图发生变化时 描边计算错误的问题 描边显示会有异常 粗细不一
修改后的代码
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Collections;
///
/// 顶点外扩
///
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 vetexList = new List();
protected override void Awake()
{
base.Awake();
text = GetComponent();
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();
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
}
}
}
效果图