1.0 UGUI Effect组件类图
UGUI为了方便开发者使用,给UI提供了默认的三种UI Effect,分别是Shadow,Outline和PositionAsUV1组件来实现阴影,描边和根据对象顶点坐标设置UV1坐标功能。
如下是UI Effect相关的类图。可以看出BaseMeshEffecft实现了IMeshModifier接口,然后Effect组件从BaseMeshEffecft派生出自己的类。
UI Effect类图
2.0 UGUI Effect实现原理
我们在之前的文章中,有讲过,Graphic在DoMeshGeneration()方法中通过OnPopulateMesh()方法生成初始的Mesh数据保存到s_VertexHelper对象中,会查找对象上IMeshModifier类型的组件,调用它们的ModifyMesh方法,将s_VertexHelper传个具体的Effect组件,处理完之后,再正式生成新的Mesh数据,填充给CanvasRenderer组件。
Graphic.cs部分源码如下:
private void DoMeshGeneration()
{
if (rectTransform != null && rectTransform.rect.width >= 0 && rectTransform.rect.height >= 0)
OnPopulateMesh(s_VertexHelper);
else
s_VertexHelper.Clear(); // clear the vertex helper so invalid graphics dont draw.
var components = ListPool<Component>.Get();
GetComponents(typeof(IMeshModifier), components);
for (var i = 0; i < components.Count; i++)
((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);
ListPool<Component>.Release(components);
s_VertexHelper.FillMesh(workerMesh);
canvasRenderer.SetMesh(workerMesh);
}
BaseMeshEffect是一个抽象类,而ModifyMesh是一个抽象方法,具体在其子类里实现。它的OnEnable、OnDisable和OnDidApplyAnimationProperties(当应用动画属性后),会调用Graphic组件(Image、RawImage或Text)的SetVerticesDirty方法,设置顶点数据为脏的,等待Mesh数据重建。
BaseMeshEffect.cs部分源码如下:
public abstract class BaseMeshEffect : UIBehaviour, IMeshModifier
{
[NonSerialized]
private Graphic m_Graphic;
/// <summary>
/// The graphic component that the Mesh Effect will aplly to.
/// </summary>
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();
}
/// <summary>
/// Called from the native side any time a animation property is changed.
/// </summary>
protected override void OnDidApplyAnimationProperties()
{
if (graphic != null)
graphic.SetVerticesDirty();
base.OnDidApplyAnimationProperties();
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (graphic != null)
graphic.SetVerticesDirty();
}
#endif
/// <summary>
/// Function that is called when the Graphic is populating the mesh.
/// </summary>
/// <param name="mesh">The generated mesh of the Graphic element that needs modification.</param>
public virtual void ModifyMesh(Mesh mesh)
{
using (var vh = new VertexHelper(mesh))
{
ModifyMesh(vh);
vh.FillMesh(mesh);
}
}
public abstract void ModifyMesh(VertexHelper vh);
}
因此,如果我们需要实现一个自定义的Effect,可以继承自BaseMeshEffect,然后在ModifyMesh()方法中实现我们自己的逻辑即可。
接下来我们来分别看下UGUI为我们提供的UI Effect组件。
3.0 Shadow组件
在ModifyMesh()方法中获取VertexHelper(暂存的顶点数据,用于生成Mesh)里的顶点数据到output对象。然后根据effectColor和effectDistance调整顶点。ApplyShadow会将顶点数乘以2,然后调用ApplyShadowZeroAlloc方法。遍历output的顶点,根据顶点生成一个新的顶点vt,添加到顶点列表里,并将vt的位置加上偏移量,并设置颜色。最后将output重新添加到VertexHelper里面(AddUIVertexTriangleStream会自行生成三角形)。
Shadow.cs部分源码如下:
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;
}
}
/// <summary>
/// Duplicate vertices from start to end and turn them into shadows with the given offset.
/// </summary>
/// <param name="verts">Vert list to copy</param>
/// <param name="color">Shadow color</param>
/// <param name="start">The start index in the verts list</param>
/// <param name="end">The end index in the vers list</param>
/// <param name="x">The shadows x offset</param>
/// <param name="y">The shadows y offset</param>
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)
{
if (!IsActive())
return;
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);
}
如图一个添加了Shadow效果的Text。为了便于查看,将偏移Effect Distance设置为(10,-10)。
可以发现,Shadow组件会将UI会多绘制1次。
Text Shadow
Shadow参数
4.0 Outline组件
Outline继承自Shadow,在ModifyMesh方法中调用ApplyShadowZeroAlloc()方法,将网格重新绘制了4遍,然后设置对应的偏移量,实现描边效果的。
Outline.cs部分源码如下:
using System.Collections.Generic;
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Effects/Outline", 15)]
/// <summary>
/// Adds an outline to a graphic using IVertexModifier.
/// </summary>
public class Outline : Shadow
{
protected Outline()
{}
public override void ModifyMesh(VertexHelper vh)
{
if (!IsActive())
return;
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);
}
}
}
如图一个添加了Outline效果的Text。为了便于查看,将偏移Effect Distance设置为(30,-30)。
可以发现,Outline组件会将UI会多绘制4次。
Text Outline.png
Outline参数
单独的Shadow组件会多绘制1次
单独的Outline组件会多绘制4次
Shadow组件+Outline组件会多绘制9次,一共绘制了10次
Text Outline Shadow
Outline参数 Shadow参数
5.0 PositionAsUV1组件
PositionAsUV1组件代码相对简单一些,使用每个顶点的位置数据当做该顶点的UV坐标,对贴图进行采样。
PositionAsUV1.cs部分源码如下:
/// <summary>
/// An IVertexModifier which sets the raw vertex position into UV1 of the generated verts.
/// </summary>
public class PositionAsUV1 : BaseMeshEffect
{
protected PositionAsUV1()
{}
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);
}
}
}
网友评论