UGUI--进阶_优化_上

目录

[TOC]

一、写在前面

1.UnityUI版本

版本:2018.3.of2

二、UGUI优化主要问题

1.网格重建

  • 合批部分的网格重建,Canvas 下所有子物体重新绘制:Rebatch()

    Canvas 在重新合批时造成的消耗。

    Canvas 下的任意子物体发生改变的时候 Canvas 会重新绘制所有子物体,执行重新合批的操作。(消耗最大)

  • 单个 UI 物体的重建:Rebuild()

    Canvas 中执行Buildbatch()方法。

(1)网格重建的主逻辑

CanvasUpdateRegistry 中的方法PerformUpdate()

        private void PerformUpdate()
        {
            //UISystemProfilerApi 开启采样
            //1.m_LayoutRebuildQueue:Layout 部分进行重建
            rebuild.Rebuild((CanvasUpdate)i);
            //2.ClipperRegistry 进行剔除操作(Mask2D)
            ClipperRegistry.instance.Cull();
            //3.m_GraphicRebuildQueue:Graphic 部分进行重建
            element.Rebuild((CanvasUpdate)i);
            //UISystemProfilerApi 结束采样
        }

该方法主要负责重建、剔除操作。

该方法被添加到 Canvas 中的 willRenderCanvases 事件下

        protected CanvasUpdateRegistry()
        {
            Canvas.willRenderCanvases += PerformUpdate;
        }
        //Canvas 下的事件(每帧执行)        
        public static event WillRenderCanvases willRenderCanvases;

(2)Layout 部分的重建

优化建议:尽可能少使用自动排列组件。

原因:

  • Layout 重建部分涉及到很多计算,尤其是嵌套型的 Layout 。

代码:LayoutRebuilder继承自ICanvasElement

public void Rebuild(CanvasUpdate executing)
{
    switch (executing)
    {
        case CanvasUpdate.Layout:
            // 不幸的是,我们将对树执行相同的GetComponents查询2次,
            // 但是在执行下一个操作之前,必须对每棵树进行完整的迭代,
            // 因此,重用结果需要将结果存储在字典或类似的东西中,
            // 这可能比多次执行GetComponents的开销更大.
            PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputHorizontal());
            PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutHorizontal());
            PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputVertical());
            PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutVertical());
            break;
    }
}

(3)Graphic 部分的重建

优化建议:不要使用SetActive()方法来显示隐藏UI组件,这样会导致大量的重建

原因:

  • 大部分操作都会导致顶点设置脏标记,从而引发重建
  • 只有当材质发生改变的时候,才会将材质设置脏标记
  • GraphicOnEnable()(设置显示)时会设置图形布局、顶点和材质的脏标记,并表示需要重建
  • Text 文本每个字都为一个单独的面片,当文本中字数过多的时候,顶点数是很大的,此时的重建会带来巨大性能消耗。

代码:

Graphic中的重建:

/// <summary>
/// 在预渲染循环中重新构建图形几何图形及其材料.
/// </summary>
/// <param name="update">渲染CanvasUpdate循环的当前步骤.</param>
/// <remarks>
/// 有关画布更新周期的更多细节,请参见CanvasUpdateRegistry.
/// </remarks>
public virtual void Rebuild(CanvasUpdate update)
{
    if (canvasRenderer.cull)
        return;

    switch (update)
    {
        case CanvasUpdate.PreRender:
            if (m_VertsDirty)//顶点被设置脏标记
            {
                UpdateGeometry();
                m_VertsDirty = false;
            }
            if (m_MaterialDirty)//材质被设置脏标记
            {
                UpdateMaterial();
                m_MaterialDirty = false;
            }
            break;
    }
}

Graphic中的OnEnable()

/// <summary>
/// 将图形和画布标记为已更改.
/// </summary>
protected override void OnEnable()
{
    base.OnEnable();
    CacheCanvas();
    GraphicRegistry.RegisterGraphicForCanvas(canvas, this);

    #if UNITY_EDITOR
        GraphicRebuildTracker.TrackGraphic(this);
    #endif
        if (s_WhiteTexture == null)
            s_WhiteTexture = Texture2D.whiteTexture;

    SetAllDirty();//设置图形布局、顶点和材质的脏标记,并表示需要重建
}

Graphic中的SetAllDirty()

/// <summary>
/// 设置图形所有属性的脏标记,并表示需要重建.
/// 布局、顶点和材质.
/// </summary>
public virtual void SetAllDirty()
{
    SetLayoutDirty();
    SetVerticesDirty();
    SetMaterialDirty();
}

2.UI射线

GraphicRaycaster中有Raycast()方法来处理射线检测:

/// <summary>
/// 对与画布关联的图形列表执行raycast.
/// </summary>
/// <param name="eventData">当前事件数据</param>
/// <param name="resultAppendList">命中对象列表.</param>
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
    //获取指定 canvas 下所有 Graphic 组件
    var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
    //获取响应点击事件的相机
    var currentEventCamera = eventCamera;
    //获取当前显示的显示界面Id
    displayIndex
    //获取响应事件的位置
    var eventPosition = Display.RelativeMouseAt(eventData.position);
    //通过显示界面Id获取显示界面Size,从而将响应事件的位置转换到view space
    // Convert to view space
    Vector2 pos;

    //剔除超出摄像机视域部分的响应事件
    if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
        return;
    //计算对应于 BlockingObjects 下的射线响应的距离
    float hitDistance;
    //筛选graphic
    //筛选,只选取由 ICanvasRaycastFilter 接口实现的对象
    Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
    //剔除 graphic 反面
    //剔除在摄像机背后的 graphic
    //距离判断(详细计算逻辑见:http://geomalgorithms.com/a06-_intersect-2.html)
    //添加到命中列表
    resultAppendList.Add(castResult);
}