基础知识与源码简析
基础知识
在UGUI中,每个UI都是Mesh组成的。将scene窗口的Shading Mode改为“WireFrame”,就能查看到UI的网格。
每个UI都开启了模板测试,都在透明物体的渲染队列中渲染。可以把Shading Mode改为“Overdraw”,查看UI的“是否有较多的overdraw”
Drawcall:CPU准备好了所有的顶点、贴图等信息,把这些信息放在GPU可以读取的地方,并向GPU发送一次绘制请求的过程叫做一次Drawcall。
填充率(Fill Rate):显卡每帧或者说每秒能够渲染的像素数。如果overdraw过多,显卡压力就会过大。
批处理(Batching):UGUI做一次Mesh合并的过程。在UGUI中材质和贴图相同并且相邻的UI元素才可以合批。
基础源码简析
Canvas类
UI绘制的底层类,是一个封装在Unity底层的c++类,在Canvas
类中有一个很重要的事件willRenderCanvases
,这是所有UI在进行过滤、合批后最终要注册的事件。
1 | namespace UnityEngine |
每一个需要显示的UI组件,都会挂载一个Canvas Renderer组件,这些组件就是用来告诉Canvas,哪些UI需要进行绘制,并且向Canvas提供这些UI的Mesh
Graphic类
所有需要显示的组件,比如Image、Text等,都间接继承自Graphic
类
1 | namespace UnityEngine.UI |
ICanvasElement
也是一个非常基础的接口,表示继承了Graphic
的组件需要在Canvas下显示。如果没有Canvas,就不能进行正常的Mesh重建。在ICanvasElement
中,声明了一个非常重要的方法,即ReBubuild
方法:
ICanvasElement
的接口声明在CanvasUpdateRegistry
文件里
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 /// <summary>
/// This is an element that can live on a Canvas.
/// </summary>
public interface ICanvasElement
{
/// <summary>
/// Rebuild the element for the given stage.
/// </summary>
/// <param name="executing">The current CanvasUpdate stage being rebuild.</param>
void Rebuild(CanvasUpdate executing);
/// <summary>
/// Get the transform associated with the ICanvasElement.
/// </summary>
Transform transform { get; }
/// <summary>
/// Callback sent when this ICanvasElement has completed layout.
/// </summary>
void LayoutComplete();
/// <summary>
/// Callback sent when this ICanvasElement has completed Graphic rebuild.
/// </summary>
void GraphicUpdateComplete();
/// <summary>
/// Used if the native representation has been destroyed.
/// </summary>
/// <returns>Return true if the element is considered destroyed.</returns>
bool IsDestroyed();
}在对UI进行批处理(预先处理)之后,所有带有“脏标记”的UI组件将会进行重建(
ReBuild
)在
Graphic
类里面进行预处理时,将所有的需要重建的顶点、材质或者Layout设为“脏”,对应的bool设为true。
MaskableGraphic类
MaskableGraphic
类是Image
、Text
组件直接继承的类,表示这个组件可以被Mask遮挡。
1 | namespace UnityEngine.UI |
IMaskable
接口表示此图形可以被Mask组件剔除。
LayoutRebuilder类
LayoutRebuilder
类是管理各种Layout组件的Rebuild的类。
1 | namespace UnityEngine.UI |
使用
MarkLayoutForRebuild
方法,将标记的rect是否需要进行rebuild进行注册CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild(rebuilder)
CanvasUpdateRegistry
文件中对应的方法:
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 /// <summary>
/// Try and add the given element to the layout rebuild list.
/// </summary>
/// <param name="element">The element that is needing rebuilt.</param>
/// <returns>
/// True if the element was successfully added to the rebuilt list.
/// False if either already inside a Graphic Update loop OR has already been added to the list.
/// </returns>
public static bool TryRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
return instance.InternalRegisterCanvasElementForLayoutRebuild(element);
}
private bool InternalRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
if (m_LayoutRebuildQueue.Contains(element))
return false;
/* TODO: this likely should be here but causes the error to show just resizing the game view (case 739376)
if (m_PerformingLayoutUpdate)
{
Debug.LogError(string.Format("Trying to add {0} for layout rebuild while we are already inside a layout rebuild loop. This is not supported.", element));
return false;
}*/
return m_LayoutRebuildQueue.AddUnique(element);
}可以看到,在内部所有需要Rebuild的Layout都放在
m_LayoutRebuildQueue
中。也就是设置成脏标记。
所有可以被Layout影响的组件都继承ILayoutElement
接口,Image或Text组件都继承了这个接口
CanvasUpdateRegistry类
这个类负责Canvas更新的注册。不管是Layout的ReBuild还是Graphic的ReBuild都要在这里注册进去。
1 | namespace UnityEngine.UI |
Mask源码简析
RectMask2D类
1 | namespace UnityEngine.UI |
这个类使用IClipper
接口来实现核心的遮罩逻辑,核心实现在PerformClipping
方法里。
Mask类
1 | namespace UnityEngine.UI |
Mask
类实现遮罩逻辑并不是通过裁剪,而是使用模板缓存(Stencil Buffer),核心实现在GetModifiedMaterial
里
拓展:深度测试
在Shader中打开深度测试和深度写入,就能根据物体和摄像机之间的距离来覆盖对应区域的颜色。
比如说在世界空间中,一个黑色物体在白色物体前方,摄像机视野下黑色物体遮挡白色物体,如果我们给白色物体的材质挂载下面的Shader
1 | Shader "MyShader/AlphaShadera" |
这样在摄像机视野下,白色物体虽然在黑色物体后面,但是被遮挡部分会渲染成白色。
在Unity中透明物体有自己的渲染队列,透明的物体不会写深度,因为所有的透明物体写深度没有意义,必须要全都渲染出来。渲染透明物体时,一般都是从后向前渲染。
所有的UI都是在透明队列中绘制,UI系统中的需要渲染的组件都带有Alpha Blend,所以UI渲染存在overdraw,填充率过大的问题,需要进行优化。