当前位置:   article > 正文

Unity uGUI原理解析-Graphic_unity graphic

unity graphic

Unity uGUI原理解析-Graphic

Graphic 在 UGUI 中主要负责显示部分,从Unity uGUI原理解析中我们可以知道如下关系:
UGUI Graphic 关系
可以看到我们熟悉的RawImag、Image和Text正是继承自Graphic。

一、绘制原理

那么 Graphic 是如何渲染图像到界面上的呢? 我们可以在类头部的特性看到CanvasRenderer,Graphic 收集完数据后调用 CanvasRenderer 设置顶点、材质等信息由 CanvasRenderer 来进行渲染。

[RequireComponent(typeof(CanvasRenderer))]
  • 1

为了将图像显示出来我们需要给CanvasRenderer设置 材质(Material)、贴图(Texture)、网格(Mesh)。
一个基本流程如下面的代码:



/// <summary>
/// Base class for all UI components that should be derived from when creating new Graphic types.
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(CanvasRenderer))]
[RequireComponent(typeof(RectTransform))]
[ExecuteAlways]
public abstract class Graphic : UIBehaviour, ICanvasElement
{
    //...

    /// <summary>
    /// Set all properties of the Graphic dirty and needing rebuilt.
    /// Dirties Layout, Vertices, and Materials.
    /// </summary>
    public virtual void SetAllDirty()
    {
        SetLayoutDirty(); // 忽略掉各种判断代码
        SetMaterialDirty(); // 忽略掉各种判断代码
        SetVerticesDirty();
    }

    /// <summary>
    /// Mark the layout as dirty and needing rebuilt.
    /// </summary>
    /// <remarks>
    /// Send a OnDirtyLayoutCallback notification if any elements are registered. See RegisterDirtyLayoutCallback
    /// </remarks>
    public virtual void SetLayoutDirty()
    {
        if (!IsActive())
            return;

        LayoutRebuilder.MarkLayoutForRebuild(rectTransform);

        if (m_OnDirtyLayoutCallback != null)
            m_OnDirtyLayoutCallback();
    }

    /// <summary>
    /// Mark the vertices as dirty and needing rebuilt.
    /// </summary>
    /// <remarks>
    /// Send a OnDirtyVertsCallback notification if any elements are registered. See RegisterDirtyVerticesCallback
    /// </remarks>
    public virtual void SetVerticesDirty()
    {
        if (!IsActive())
            return;

        m_VertsDirty = true;
        CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);

        if (m_OnDirtyVertsCallback != null)
            m_OnDirtyVertsCallback();
    }

    /// <summary>
    /// Mark the material as dirty and needing rebuilt.
    /// </summary>
    /// <remarks>
    /// Send a OnDirtyMaterialCallback notification if any elements are registered. See RegisterDirtyMaterialCallback
    /// </remarks>
    public virtual void SetMaterialDirty()
    {
        if (!IsActive())
            return;

        m_MaterialDirty = true;
        CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);

        if (m_OnDirtyMaterialCallback != null)
            m_OnDirtyMaterialCallback();
    }

    /// <summary>
    /// Rebuilds the graphic geometry and its material on the PreRender cycle.
    /// </summary>
    /// <param name="update">The current step of the rendering CanvasUpdate cycle.</param>
    /// <remarks>
    /// See CanvasUpdateRegistry for more details on the canvas update cycle.
    /// </remarks>
    public virtual void Rebuild(CanvasUpdate update)
    {
        if (canvasRenderer == null || canvasRenderer.cull)
            return;

        switch (update)
        {
            case CanvasUpdate.PreRender:
                if (m_VertsDirty)
                {
                    UpdateGeometry();
                    m_VertsDirty = false;
                }
                if (m_MaterialDirty)
                {
                    UpdateMaterial();
                    m_MaterialDirty = false;
                }
                break;
        }
    }

    /// <summary>
    /// Call to update the Material of the graphic onto the CanvasRenderer.
    /// </summary>
    protected virtual void UpdateMaterial()
    {
        if (!IsActive())
            return;

        canvasRenderer.materialCount = 1;
        canvasRenderer.SetMaterial(materialForRendering, 0);
        canvasRenderer.SetTexture(mainTexture);
    }

    /// <summary>
    /// Call to update the geometry of the Graphic onto the CanvasRenderer.
    /// </summary>
    protected virtual void UpdateGeometry()
    {
    	//...
        DoMeshGeneration();
    }

    private void DoMeshGeneration()
    {
    	//...
          OnPopulateMesh(s_VertexHelper);
 
        var components = ListPool<Component>.Get();
        GetComponents(typeof(IMeshModifier), components);

        for (var i = 0; i < components.Count; i++)
            ((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);

        canvasRenderer.SetMesh(workerMesh);
    }

    /// <summary>
    /// Callback function when a UI element needs to generate vertices. Fills the vertex buffer data.
    /// </summary>
    /// <param name="vh">VertexHelper utility.</param>
    /// <remarks>
    /// Used by Text, UI.Image, and RawImage for example to generate vertices specific to their use case.
    /// </remarks>
    protected virtual void OnPopulateMesh(VertexHelper vh)
    {
        var r = GetPixelAdjustedRect();
        var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height);

        Color32 color32 = color;
        vh.Clear();
        vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(0f, 0f));
        vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(0f, 1f));
        vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(1f, 1f));
        vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(1f, 0f));

        vh.AddTriangle(0, 1, 2);
        vh.AddTriangle(2, 3, 0);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  1. SetAllDirty。 在 OnEnable、Reset、OnDidApplyAnimationProperties、OnValidate、OnTransformParentChanged等等事件中,需要更新表现时都会调用SetAllDirty,发送对应的事件。给 CanvasUpdateRegistry 标记需要被Rebuild
  2. 触发 Rebuild, 执行 Rebuild 函数。 这时候会根据Dirty的标识(SetAllDirty里面将标识设置为true,这里设置为false)来调用 UpdateGeometry、UpdateMaterial。
  3. 更新材质 UpdateMaterial. (直接给CanvasRenderer设置值)
  4. 更新顶点数据 UpdateGeometry。
    1. 根据代码我们可以看到首先调用 OnPopulateMesh 来生成Mesh数据。 Image、RawImage以及Text都对这个函数进行重写来生成特定的顶点数据。
    2. 获取当前GameObject上的所有实现了 IMeshModifier 接口的组件并调用 ModifyMesh 方法来修改Mesh数据
    3. 给 CanvasRenderer 设置更新后的数据

二、事件原理

Graphic 组件负责在界面上的展示内容,需要知道自己是否被操作了。
在 OnEnable 或其他时候在 GraphicRegistry 中注册自身。
在 OnDisable 或其他时候在 GraphicRegistry 中取消注册。

查阅 GraphicRegistry 源码我们可以知道这是一个单例, 里面实现的功能也很简单。 就是注册、取消注册以及获取。

public class GraphicRegistry
{
    private static GraphicRegistry s_Instance;
    //...
    public static GraphicRegistry instance
    {
        get
        {
            if (s_Instance == null) s_Instance = new GraphicRegistry();
            return s_Instance;
        }
    }
    // 注册Graphic
    public static void RegisterGraphicForCanvas(Canvas c, Graphic graphic)
    {
    	//...
        graphics.Add(graphic);
        instance.m_Graphics.Add(c, graphics);
    }

    public static void UnregisterGraphicForCanvas(Canvas c, Graphic graphic)
    {
    	//...
        graphics.Remove(graphic);
        if (graphics.Count == 0)
            instance.m_Graphics.Remove(c);
    }

    public static IList<Graphic> GetGraphicsForCanvas(Canvas canvas)
    {
    	//...
        IndexedSet<Graphic> graphics;
        if (instance.m_Graphics.TryGetValue(canvas, out graphics))
            return graphics;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

通过 Graphic 给 GraphicRegistry 注册完成后,在 GraphicRaycaster 中的 Raycast函数中会进行获取的操作。

  1. GraphicRaycaster 获取 GraphicRegistry 中所注册的 Graphic 并调用 Graphic 中的 Raycast 方法。
  2. 获取Graphic所挂载的transform上的所有Components检测是否实现了ICanvasRaycastFilter, 对所有实现了 ICanvasRaycastFilter 接口的调用 IsRaycastLocationValid
  3. IsRaycastLocationValid 按照对应实现来进行检测。
public class GraphicRaycaster : BaseRaycaster
{
	private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
	{
	    // Necessary for the event system
	    int totalCount = foundGraphics.Count; // 这个就是从 GraphicRegistry 中获取的Graphics
	    for (int i = 0; i < totalCount; ++i)
	    {
	        Graphic graphic = foundGraphics[i];
	
	        // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
	        if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)
	            continue;
	
	        if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera))
	            continue;
	
	        if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
	            continue;
	
	        if (graphic.Raycast(pointerPosition, eventCamera))
	        {
	            s_SortedGraphics.Add(graphic);
	        }
	    }
	
	    s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
	    totalCount = s_SortedGraphics.Count;
	    for (int i = 0; i < totalCount; ++i)
	        results.Add(s_SortedGraphics[i]);
	
	    s_SortedGraphics.Clear();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

三、实际应用

1. 自定义显示内容

在UI上我们可能希望有个空白图片能让我们进行上色缩放等操作。 但是我们又不想要添加一个图片以及Image组件打断合批。那么我们可以编写一下脚本来实现。因为要响应事件所以还需要实现
IsRaycastLocationValid 方法。代码如下

public class EmptyImg :
	MaskableGraphic, // 继承它用来显示图像
	ICanvasRaycastFilter // 实现这个接口来接收事件
{
    protected override void Awake()
    {
        base.Awake();
        color = new Color(1.0f, 1.0f, 1.0f, 0.0f);
    }
    protected override void OnPopulateMesh(VertexHelper toFill)
    {
        toFill.Clear();
    }
    public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
    {
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2. 翻转图像

翻转图像可以通过设置 Scale 的方式来实现翻转,但是我们偏偏就是不用。 通过对代码的阅读我们可以知道Mesh信息在两个地方可以进行修改。 一个是在 OnPopulateMesh 的时候直接生成对应的数据, 另外一个是实现IMeshModifier接口(可以继承 BaseMeshEffect 方便开发)来修改生成的Mesh信息。 注意:下面代码只是测试用,有很多情况不进行考虑。

OnPopulateMesh 实现

修改 OnPopulateMesh 方法我们可以通过继承对应的类并重写虚方法来实现。 参考代码如下:

public class FlipImage : Image
{
    public bool FlipHorizontal
    {
        get { return flipHor;}
        set
        {
            flipHor = value;
            UpdateGeometry();
        }
    }
    public bool FlipVertical
    {
        get { return flipVer; }
        set
        {
            flipVer = value;
            UpdateGeometry();
        }
    }

    [SerializeField]
    protected bool flipHor;
    [SerializeField]
    protected bool flipVer;

    protected override void OnPopulateMesh(VertexHelper toFill)
    {
        base.OnPopulateMesh(toFill);

        if (flipHor || flipVer)
        {
            Vector2 rectCenter = rectTransform.rect.center;
            int vertCount = toFill.currentVertCount;
            for (int i = 0; i < vertCount; i++)
            {
                UIVertex uiVertex = new UIVertex();
                toFill.PopulateUIVertex(ref uiVertex, i);

                Vector3 pos = uiVertex.position;
                uiVertex.position = new Vector3(
                    flipHor ? (pos.x + (rectCenter.x - pos.x) * 2) : pos.x,
                    flipVer ? (pos.y + (rectCenter.y - pos.y) * 2) : pos.y,
                    pos.z);
                toFill.SetUIVertex(uiVertex, i);
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
BaseMeshEffect实现

通过 BaseMeshEffect 方法实现我们需要在挂载了 Graphic 类(也就是Image、RawImage、Text)的GameObject上挂载继承BaseMeshEffect这个类的组件。 代码如下:

public class FlipImgMeshEffect : BaseMeshEffect
{
    public bool FilpHorizontal
    {
        get { return flipHor; }
        set
        {
            flipHor = value;
            graphic?.SetAllDirty();
        }
    }
    public bool FlipVertical
    {
        get { return flipVer; }
        set
        {
            flipVer = value;
            graphic?.SetAllDirty();
        }
    }

    [SerializeField]
    protected bool flipHor;
    [SerializeField]
    protected bool flipVer;

    readonly List<UIVertex> stream = new List<UIVertex>();
    public override void ModifyMesh(VertexHelper vh)
    {
        vh.GetUIVertexStream(stream);

        if (flipHor || flipVer)
        {
            Vector2 rectCenter = graphic.rectTransform.rect.center;

            for (int i = 0; i < vh.currentVertCount; i++)
            {
                UIVertex uiVertex = new UIVertex();
                vh.PopulateUIVertex(ref uiVertex, i);

                Vector3 pos = uiVertex.position;
                uiVertex.position = new Vector3(
                    flipHor ? (pos.x + (rectCenter.x - pos.x) * 2) : pos.x,
                    flipVer ? (pos.y + (rectCenter.y - pos.y) * 2) : pos.y,
                    pos.z);
                vh.SetUIVertex(uiVertex, i);
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/111173
推荐阅读
相关标签
  

闽ICP备14008679号