目录
[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组件,这样会导致大量的重建
原因:
- 大部分操作都会导致顶点设置脏标记,从而引发重建
- 只有当材质发生改变的时候,才会将材质设置脏标记
Graphic
在OnEnable()
(设置显示)时会设置图形布局、顶点和材质的脏标记,并表示需要重建- 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);
}