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描边的主要内容,如果未能解决你的问题,请参考以下文章