在这首先回答一个问题就是:为什么我们需要实现一个描边的方案?Unity没有吗?
答:
Unity肯定有自己的代替方案,只是性能看起来并没那么优越。Unity 的方案是挂载一个 OutLine 组件。
Outline
outline inherit Shadow:
【Unity】Source Code:
public class Outline : Shadow
{
protected Outline()
{
}
public override void ModifyMesh(VertexHelper vh)
{
if (!this.IsActive())
return;
List<UIVertex> uiVertexList = ListPool<UIVertex>.Get();
vh.GetUIVertexStream(uiVertexList);
int num = uiVertexList.Count * 5;
if (uiVertexList.Capacity < num)
uiVertexList.Capacity = num;
int start1 = 0;
int count1 = uiVertexList.Count;
this.ApplyShadowZeroAlloc(uiVertexList, (Color32) this.effectColor, start1, uiVertexList.Count, this.effectDistance.x, this.effectDistance.y);
int start2 = count1;
int count2 = uiVertexList.Count;
this.ApplyShadowZeroAlloc(uiVertexList, (Color32) this.effectColor, start2, uiVertexList.Count, this.effectDistance.x, -this.effectDistance.y);
int start3 = count2;
int count3 = uiVertexList.Count;
this.ApplyShadowZeroAlloc(uiVertexList, (Color32) this.effectColor, start3, uiVertexList.Count, -this.effectDistance.x, this.effectDistance.y);
int start4 = count3;
int count4 = uiVertexList.Count;
this.ApplyShadowZeroAlloc(uiVertexList, (Color32) this.effectColor, start4, uiVertexList.Count, -this.effectDistance.x, -this.effectDistance.y);
vh.Clear();
vh.AddUIVertexTriangleStream(uiVertexList);
ListPool<UIVertex>.Release(uiVertexList);
}
}
上面的代码是Unity的源代码,我们可以发现,outline 是继承 Shadow 组件,实现的原理就是向四个方向扩张阴影部分。最主要的是
int num = uiVertexList.Count * 5;
这是不是就明了了?假设我只有一个字符时,那么我需要5倍的性能来完成这份工作。
我们看看使用Unity的方案会带来什么效果,我们上图:
5倍的数据(5 * 6),10个字符就达到了非常恐怖的300数据量。优化时非常有必要的。
下面开始进行优化。前提是先实现一套自己的描边(首先我们要复用左上和右下两个顶点)
优化方案一、网格顶点优化
主要变量:
[SerializeField] private bool m_EnableOutLine = false;
[SerializeField] private float m_OutLineWidth = 1;
......
//ModifyMesh(VertexHelper vh)
private void _ProcessVertices(VertexHelper vh){
if (!IsActive())
{
return;
}
var count = vh.currentVertCount;
if (count == 0)
return;
for (int i = 0; i < count; i++)
{
UIVertex vertex = UIVertex.simpleVert;
vh.PopulateUIVertex(ref vertex, i);
this.iVertices.Add(vertex);
}
vh.Clear();
for (int i = 0; i < this.iVertices.Count; i += 4)
{
UIVertex TL = GeneralUIVertex(this.iVertices[i + 0]);
UIVertex TR = GeneralUIVertex(this.iVertices[i + 1]);
UIVertex BR = GeneralUIVertex(this.iVertices[i + 2]);
UIVertex BL = GeneralUIVertex(this.iVertices[i + 3]);
vh.AddVert(TL);
vh.AddVert(TR);
vh.AddVert(BR);
vh.AddVert(BL);
}
//添加三角面
for (int i = 0; i < vh.currentVertCount; i += 4)
{
vh.AddTriangle(i + 0, i + 1, i + 2);
vh.AddTriangle(i + 2, i + 3, i + 0);
}
}
我们的思路看起来是这样的:
/*
* TL--------TR
* | |^
* | ||
* | ||
* | |v
* BL--------BR
* **/
优化后(顶点下降了10点):
那么,这样的优化就可以了吗?
在用网格的方式来实现的情况下,我认为这应该算是差不多了,不排除有其他的算法。(蚊子再小也是块肉啊。)
那么还有其他方式优化吗?
有!
优化方案二、shader 描边
作者参考了网上许多的方案,觉得shader描边确实可行。参考博客:
CSDN博客链接
该方案确实可行,唯一需要注意的是,它可能会导致你未描边和描边的的对象中断UI合批处理。
最后我们上一下最后Shader的优化后的效果:
我们可以看到在我们之前的优化基础上shader的描边会更加厚重和清晰,数据量也非常少。这不失为一种办法。
最后我们看下实现逻辑。
代码中有这么一些:
var v1 = 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;
}
这里的作用就是把 对应的 UIVertex 里面的对应信息开启,让对应的通道可以通过Shader开启/获取数据,然后通过这样对应的通道,提交一些描边颜色宽度等数据,提交到Shader上面,而shader通过语意来获取如 TEXCOORD0等等。Unity默认好像只开启部分,不一定全开。
shader 接到数据后就开始描边。也不是特别复杂。最后奉献上代码(含有颜色渐变喔):
码云 Unity-TextOutline-and-Color-Gradient