美文网首页
UGUI笔记——UI Effect

UGUI笔记——UI Effect

作者: 莫忘初心_倒霉熊 | 来源:发表于2021-06-22 21:38 被阅读0次

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);
            }
        }
    }

相关文章

网友评论

      本文标题:UGUI笔记——UI Effect

      本文链接:https://www.haomeiwen.com/subject/xlimyltx.html