Unity uGUI原理解析-BaseMeshEffect
BaseMeshEffect是用于实现网格效果的抽象类实现IMeshModifier 接口,是Shadow、Outline等效果的基类。 从Unity uGUI原理解析-Graphic可以知道,Graphic 在执行 DoMeshGeneration 时会获取到当前GameObject上的所有实现 IMeshModifier 的组件。 并且会调用 ModifyMesh 方法来修改当前Graphic 的Mesh数据。BaseMeshEffect 的类结构图如下:
BaseMeshEffect 源码解析
BaseMeshEffect 源码特别简单大致分为三个部分:
- 获取当前 GameObject 上的 Graphic 组件。
- 在
OnEnable OnDisable OnDidApplyAnimationProperties 等时候使用 SetVerticesDirty 方法标记。 - 实现
IMeshModifier 的ModifyMesh 方法。当然这里是使用抽象方法进行实现。后续会有 Shadow Outline PositionAsUV1 等子类来实现
[ExecuteAlways]
public abstract class BaseMeshEffect : UIBehaviour, IMeshModifier
{
[NonSerialized]
private Graphic m_Graphic;
protected Graphic graphic
{
get
{
if (m_Graphic == null)
m_Graphic = GetComponent<Graphic>();
return m_Graphic;
}
}
protected override void OnEnable()
{
base.OnEnable();
if (graphic != null)
graphic.SetVerticesDirty();
}
protected override void OnDisable()
{
if (graphic != null)
graphic.SetVerticesDirty();
base.OnDisable();
}
protected override void OnDidApplyAnimationProperties()
{
if (graphic != null)
graphic.SetVerticesDirty();
base.OnDidApplyAnimationProperties();
}
public abstract void ModifyMesh(VertexHelper vh);
}
Shadow源码解析
通过源码可以看到 Shadow 继承抽象类 BaseMeshEffect 。 另外在设置 effectColor 和 effectDistance 以及 useGraphicAlpha 时调用 graphic.SetVerticesDirty 方法来标记需要更新。
那么是如何绘制这个阴影的呢? 可以看下是是如何实现 ModifyMesh 方法的。 部分代码如下:
public class Shadow : BaseMeshEffect
{
protected void ApplyShadowZeroAlloc(List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
{
UIVertex vt;
var neededCapacity = verts.Count + end - start;
if (verts.Capacity < neededCapacity)
verts.Capacity = neededCapacity;
for (int i = start; i < end; ++i)
{
vt = verts[i];
verts.Add(vt);
Vector3 v = vt.position;
v.x += x;
v.y += y;
vt.position = v;
var newColor = color;
if (m_UseGraphicAlpha)
newColor.a = (byte)((newColor.a * verts[i].color.a) / 255);
vt.color = newColor;
verts[i] = vt;
}
}
protected void ApplyShadow(List<UIVertex> verts, Color32 color, int start, int end, float x, float y)
{
ApplyShadowZeroAlloc(verts, color, start, end, x, y);
}
public override void ModifyMesh(VertexHelper vh)
{
var output = ListPool<UIVertex>.Get();
vh.GetUIVertexStream(output);
ApplyShadow(output, effectColor, 0, output.Count, effectDistance.x, effectDistance.y);
vh.Clear();
vh.AddUIVertexTriangleStream(output);
ListPool<UIVertex>.Release(output);
}
}
可以看到 ApplyShadowZeroAlloc 这个方法是具体的实现地方。 实际的操作也很简单:
- 遍历所有顶点数据
- 拷贝一份顶点数据
- 修改顶点的偏移,对应 EffectDistance 这个参数
- 修改颜色信息,对应 Effect Color 以及 UseGraphicAlpha这两个参数
- 更新旧顶点数据
Outline源码解析
从Outline源码中可以看到是继承自Shadow这个类。Outline的实现简单来说就是在四周在绘制四遍。不用说这个方案顶点数量变成了5倍,优化方案可以看这篇文章。
public class Outline : Shadow
{
public override void ModifyMesh(VertexHelper vh)
{
var verts = ListPool<UIVertex>.Get();
vh.GetUIVertexStream(verts);
var neededCpacity = verts.Count * 5;
if (verts.Capacity < neededCpacity)
verts.Capacity = neededCpacity;
var start = 0;
var end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, effectDistance.x, -effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, effectDistance.y);
start = end;
end = verts.Count;
ApplyShadowZeroAlloc(verts, effectColor, start, verts.Count, -effectDistance.x, -effectDistance.y);
vh.Clear();
vh.AddUIVertexTriangleStream(verts);
ListPool<UIVertex>.Release(verts);
}
}
PositionAsUV1源码解析
将原始顶点位置设置为生成的顶点的uv1。主要是给shader 提供数据的(法线贴图坐标),没什么好看的。
public class PositionAsUV1 : BaseMeshEffect
{
public override void ModifyMesh(VertexHelper vh)
{
UIVertex vert = new UIVertex();
for (int i = 0; i < vh.currentVertCount; i++)
{
vh.PopulateUIVertex(ref vert, i);
vert.uv1 = new Vector2(vert.position.x, vert.position.y);
vh.SetUIVertex(vert, i);
}
}
}
案例参考
Unity3D UGUI优化:制作镜像图片(1) Unity3D UGUI优化:制作镜像图片(2) Unity3D UGUI优化:制作镜像图片(2) UGUI Text渐变、弯曲、文字跳动(BaseMeshEffect拓展)
|