UGUI文本描边

UGUI的文本描边

如果当前渲染的像素Alpha>0,那么这个像素肯定是文字本身的像素。
如果当前渲染的像素Alpha<=0,那么这个像素肯定不是文字本身的像素。
在这里插入图片描述

当渲染像素 2 的时候,会采样到像素 1

//如果想要描边效果更佳平滑的话,升采样的像素点可以扩大到12或者更高,但是会带来更高的性能消耗
static const half2 UpSamplePixelCoord[8] =
{
	half2 (-1, 1),  half2 (0, 1),  half2 (1, 1),
	half2 (-1, 0),                 half2 (1, 0),
	half2 (-1, -1), half2 (0, -1), half2 (1, -1)
};

//升采样,每个像素根据周边8个像素的透明度来确定是否显示描边颜色
fixed UpSamplePixel(int index, v2f IN)
{
	half2 realOutlineWidth = _MainTex_TexelSize.xy * UpSamplePixelCoord[index] * _OutlineWidth;
	half2 pixelUV = IN.texcoord + realOutlineWidth;
	half4 pixelAlpha = (tex2D(_MainTex, pixelUV) + _TextureSampleAdd).w;
	return pixelAlpha;
}

fixed4 frag(v2f IN) : SV_Target
{
	//当前像素中心点的颜色
	fixed4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
	half4 outlineColor = half4(_OutlineColor.xyz, 0);
	int index = 0;
	for (; index < 8; ++index)
	{
		outlineColor.w += UpSamplePixel(index, IN);
	}
	outlineColor.w = clamp(outlineColor.w, 0, 1);
    //把文字本身的颜色和描边的颜色做一个过渡。
	color = lerp(outlineColor, color, color.a);
	return color;
}

在这里插入图片描述


可以看到描边的效果有了,但是在字体的边缘都被截掉了。这是因为文字的顶点组成的矩形区域是确定的(每个文字是由两个三角形组成的矩形,UGUI处理之后到着色器中是Mesh数据),我们增加的描边相当于加宽了文字,但是原本文字的区域我们没有加大,自然描边超出的部分就会被截掉。所以接下来我们要处理的就是把组成单个文字的两个三角形加宽,同时我们得让文字本身的大小保持不变,这样文字四周的宽度留出来给描边。类似于这样:
在这里插入图片描述

public override void ModifyMesh(VertexHelper vh)
{
    //...省略很少一部分代码
    List<UIVertex> vertexStream = ListPool<UIVertex>.Get();
    vh.GetUIVertexStream(vertexStream);
    float expandWidth = Mathf.Abs(effectDistance.x * 0.5f);
    float expandHeight = Mathf.Abs(effectDistance.y * 0.5f);
    //   v1----v2
    //   | \    |
    //   |   \  |  
    //   |     \|
    //   v4----v3
    //  顺序按照vertex来确定的,一个文字由两个triangle组成,并且任意文字的vertex顺序都是相同的
    //  但是不同文字的uv的顺序不一样
    int length = vertexStream.Count;
    for (int i = 0; i < length; i += 6)
    {
        Vector2 expandPositionSize = GetExpandPositionSize(expandWidth, expandHeight);
        Vector2 shrinkUvSize = GetShrinkUvSize(vertexStream, expandWidth, expandHeight, i);
        RePackTextVertex(vertexStream, i, expandPositionSize, shrinkUvSize);
    }
    vh.Clear();
    vh.AddUIVertexTriangleStream(vertexStream);
    ListPool<UIVertex>.Release(vertexStream);
        //...省略很少一部分代码
}

代码中的 GetExpandPositionSize 比较好处理,因为文字的顶点顺序是确定的,按照固定的顺序去增加或者减去expand值,保证每个顶点的坐标向外扩即可。GetShrinkUvSize这个处理起来要稍微注意下,因为文字的UV的坐标不确定,我没有法线任何规律,也就是说两个文字的同一个顶点处,uv坐标顺序可能会不一样。这就需要自己根据每个uv的值来判断了,保证每个顶点的uv是向内缩即可,实现的原理如图(图中假设内缩大小为(0.2, 0.2)):

在这里插入图片描述

private Vector2 ShrinkUvSize(Vector2 uv, Vector2 leftVertexUv, Vector2 rightVertexUv, Vector2 shrinkSize)
{
    float x = ShrinkValue(uv.x, leftVertexUv.x, rightVertexUv.x, shrinkSize.x);
    float y = ShrinkValue(uv.y, leftVertexUv.y, rightVertexUv.y, shrinkSize.y);
    return new Vector2(x, y);
}
/// <summary>
///   left  三个uv点关系。value:当前uv点,left:value的左边uv点,right:value的右边UV点
///   |
///   |
///  value --- right
/// </summary>
private float ShrinkValue(float value, float left, float right, float shrinkValue)
{
    if (value < left)
    {
        value -= shrinkValue;
    }
    else if (value == left)
    {
        value = value < right ? value-shrinkValue : value +shrinkValue;
    }
    else
    {
        value += shrinkValue;
    }
    return value;
}

处理了文字描边被裁减的问题之后:

在这里插入图片描述

每个文字的边缘出现了不需要的颜色像素,因为我们修改了uv,把文字的顶点的uv值放大了,所以放大的那部分uv对应的像素原本是其他文字的像素现在被采样近来了。所以我们需要剔除掉这部分本不是当前文字的像素。这个可以直接在着色器中处理:

//判断像素是否在三角形中:像素点依次和三角形的两个顶点的叉积的方向同向。
fixed IsPixelInTriangle(float3 pixelPos, float3 a, float3 b, float3 c)
{
	float z1 = cross(pixelPos - a, a - b).z;
	float z2 = cross(pixelPos - b, b - c).z;
	float z3 = cross(pixelPos - c, c - a).z;
	return z1 * z2 > 0 && z2 * z3 > 0;
}

//判断像素是否在一个Rect中,由于Rect是由两个Triangle组成,所以只需要判断顶点是否在任意一个Triangle中即可。
fixed IsPixelInRect(float3 pixelPos, float3 a, float3 b, float3 c, float3 d)
{
	return IsPixelInTriangle(pixelPos, a, b, c) || IsPixelInTriangle(pixelPos, c, d, a);
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值