1.概要说明涉及的类以及主要的方法简介
1.1 改变颜色时设置脏标记并且将要改变的元素放入队列中供1.2去触发重绘
Graphic类,这个类是UGUI中主要的视图表现的类,主要涉及方法有SetVerticesDirty(),主要作用是设置脏标记以及向重绘队列注册元素用于重绘
SetPropertyUtility类,工具静态类,主要作用是设置一些东西,比如Struct,Class,Color,这里主要涉及其中的SetColor,只是判断是不是改变颜色了
CanvasUpdateRegistry类,也就是更新注册,主要用于注册需要重绘的元素主要语句是:m_GraphicRebuildQueue.AddUnique(element);向队列中加入了需要重绘的元素
所以这里第一步主要就是设置脏标记以及注册需要重绘元素到队列中
首先在设置Image的color时会先判断是否更改了颜色,如果更改了的话就会调用SetVerticesDirty()方法进行脏标记并且调用CanvasUpdateRegistry中的方法并且执行到关键语句m_GraphicRebuildQueue.AddUnique(element)向队列中添加元素。然后就是第二步骤了
1.2 触发重绘
第二步骤中最初始的Canvas类中并没有给到源码,这里直接跳转到了CanvasScaler中,所以首先涉及的类有
①CanvasScaler类,这里主要涉及方法有Canvas_preWillRenderCanvases(),至于为什么会调用到这个函数不清楚,网上许多文章说每帧都会检测,自然就会调用
②CanvasUpdateRegistry类,这里涉及到主要方法,PerformUpdaer()方法,也是在这里调用了Rebuild()方法
③Graphic类,这里就是Rebuild()方法的类了,其中这个方法主要又调用了UpdateGeometry()和UpdateMaterial()方法,就是更新几何和更新材质,其中UpdateGeometry()方法又会调用DoMeshGeneration()方法,也就是网格生成,这个网格生成又会调用OmPopulateMesh()方法,这里由于是virtual,所以会调用到子类中的override,这里到了Image类中的方法
④Image类,这个是最终要真正去绘制图形的类,主要方法就是OnPopulateMesh()方法,如果这里没有给新的sprite,它会默认调用父类的OnPopulaetMesh()方法,而重绘的原理就和Shader类似了,首先就是添加顶点信息,然后添加片面,也就是三角形,这里借助了VertesHelper结构体来存储数据,数据得到后,就会把这些信息传给渲染管线去渲染了,主要涉及的语句有:
s_VertexHelper.FillMesh(workerMesh);
canvasRenderer.SetMesh(workerMesh);
⑤VertexHelper类,辅助类,存储顶点片面数据,并且根据数据进行填充,主要涉及方法有FillMesh(Mesh mesh)
2. 重绘的具体内容
1.1设置脏标记以及注册元素到重绘队列
public virtual void SetVerticesDirty()
{
if (!IsActive())
return;
m_VertsDirty = true;
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
if (m_OnDirtyVertsCallback != null)
m_OnDirtyVertsCallback();
}
最终会在CanvasUpdateRegistry类中调用这个方法,并且将Iamge元素加入到队列中,其中用到了UGUI中自己实现的一个容器类IndexedSet<T>,它能实现快速的移除但无序,它使用了一个List和一个Dictionary,这里就能实现o(1)查找(哈希表)以及o(1)删除,添加(List只添加最后一个和删除最后一个)
private bool InternalRegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
if (m_PerformingGraphicUpdate)
{
Debug.LogError(string.Format("Trying to add {0} for graphic rebuild while we are already inside a graphic rebuild loop. This is not supported.", element));
return false;
}
return m_GraphicRebuildQueue.AddUnique(element);
}
1.2 Canvas开始重绘队列元素
最终会调用到Rubuild()方法以及最终的OnPopulateMesh()方法
再设置完CanvasScaler之后就会调用CanvasUpdateRegiistry()方法,这里时Canvas中的SendWillRenderCanvases()方法调用的,这个方法主要就是检测几个脏标记队列,如果队列中有元素,那么就会调用对应的Rebuild方法。
其中首先会调用CleanInvalidItems()方法,这个方法会清楚无效的元素,主要是判断是不是被删除了,判断方法是看是否为null
然后就是布局更新以及Render更新,这里会进入到具体元素也就是我们的Image的重绘方法
private static readonly Comparison<ICanvasElement> s_SortLayoutFunction = SortLayoutList;
private void PerformUpdate()
{
UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);
CleanInvalidItems();
m_PerformingLayoutUpdate = true;
m_LayoutRebuildQueue.Sort(s_SortLayoutFunction);
for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++)
{
UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]);
for (int j = 0; j < m_LayoutRebuildQueue.Count; j++)
{
var rebuild = m_LayoutRebuildQueue[j];
try
{
if (ObjectValidForUpdate(rebuild))
rebuild.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, rebuild.transform);
}
}
UnityEngine.Profiling.Profiler.EndSample();
}
for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)
m_LayoutRebuildQueue[i].LayoutComplete();
m_LayoutRebuildQueue.Clear();
m_PerformingLayoutUpdate = false;
UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);
UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Render);
// now layout is complete do culling...
UnityEngine.Profiling.Profiler.BeginSample(m_CullingUpdateProfilerString);
ClipperRegistry.instance.Cull();
UnityEngine.Profiling.Profiler.EndSample();
m_PerformingGraphicUpdate = true;
for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
{
UnityEngine.Profiling.Profiler.BeginSample(m_CanvasUpdateProfilerStrings[i]);
for (var k = 0; k < m_GraphicRebuildQueue.Count; k++)
{
try
{
var element = m_GraphicRebuildQueue[k];
if (ObjectValidForUpdate(element))
element.Rebuild((CanvasUpdate)i);
}
catch (Exception e)
{
Debug.LogException(e, m_GraphicRebuildQueue[k].transform);
}
}
UnityEngine.Profiling.Profiler.EndSample();
}
for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)
m_GraphicRebuildQueue[i].GraphicUpdateComplete();
m_GraphicRebuildQueue.Clear();
m_PerformingGraphicUpdate = false;
UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Render);
}
然后就是进入到Graphic中的Rebuild()方法了,这里会调用UpdateGeometry()或者UpdateMaterial()方法,这里再上面1.1脏标记时已经将m_VertsDirty设置为true了,这里就会调用UpdateGeometry();
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;
}
}
protected virtual void UpdateGeometry()
{
if (useLegacyMeshGeneration)
{
DoLegacyMeshGeneration();
}
else
{
DoMeshGeneration();
}
}
这里就会调用真正去绘制网格顶点片元的代码,主要方法是OnPopulateMesh(s_VertexHelper);以及s_VertexHelper.FillMesh()以及canvasRenderer.SetMesh()其中OnPopulateMesh其实就是给s_VertexHelper赋值,主要内容其实是顶点信息以及uv值等等,后面s_VertexHelper.FillMesh()才是填充网格的以及后边的canvasRenderer.SetMesh()才会真正渲染到Canvas中
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);
}
结束:Image颜色改变主要就是两步骤,首先脏标记并且将要改变的元素添加到重绘队列,然后就是调用元素中的Rebuild重绘方法,重绘又包括布局重绘以及Render重绘,最终会调用到具体元素的OnpopulateMesh()方法以及后续的将其填充到网格中并最终将其渲染到画布上去。下篇文章会讨论一下我们自己控制绘制过程并实现自己画一个圆形的Image,其实就是重写了Graphic中的OnpopulateMesh()方法