關于Graphic元件
Graphic元件是UGUI中比較重要的一個元件,例如Image,RawImag,MaskableGraphic 可遮罩的圖形元件 這些都是繼承自Graphic的
它必須要有 CanvasrRenderer元件以級RectTransform 元件,并且一個對象隻允許挂載一個。
RectTransform:
RectTransform繼承于Transform,在 Transform 基礎上,RectTransform 增加了 軸心(pivot)、錨點(anchors)、和 尺寸變化量(sizeDelta)。
其中anchors和pivot都是坐标原點在左下角的0-1向量空間,0-1代表的是比例。
anchors的向量空間是子UI相對父UI的比例位置,pivot的向量空間是相對UI本身的比例位置,這個元件是比較常見的。
CanvasRenderer:當然不用說了,如果 沒有這個元件,是不會去通知根節點的Canvas 進行渲染的;
關于它的繼承關系
Graphic->UIBehaviour,ICanvasElement
關于它的一些使用 例子
這裡有個簡單的例子:
重寫了這個 onPopulateMesh 方法,它的作用是計算元素網格資料
填充網格,計算上下左右四個角的
[ExecuteInEditMode]
public class SimpleGraphic : Graphic
{
protected override void OnPopulateMesh(VertexHelper vh)
{
Debug.Log("vh"+vh.currentIndexCount+"VertCount"+vh.currentVertCount);
//兩個對角 (0,0) (0.5,0.5)是中心點 (1,1)
Vector2 corner1 = Vector2.zero;
Vector2 corner2 = Vector2.zero;
corner1.x = 0f;
corner1.y = 0f;
corner2.x = 1f;
corner2.y = 1f; //寬高比
corner1.x -= rectTransform.pivot.x;
corner1.y -= rectTransform.pivot.y;
corner2.x -= rectTransform.pivot.x;
corner2.y -= rectTransform.pivot.y;
//寬高保持一緻
corner1.x *= rectTransform.rect.width;
corner1.y *= rectTransform.rect.height;
corner2.x *= rectTransform.rect.width;
corner2.y *= rectTransform.rect.height;
//重新計算
vh.Clear();
UIVertex vert = UIVertex.simpleVert;
//添加四個點 左下左上,右上右下
vert.position = new Vector2(corner1.x, corner1.y);
vert.color = color;
vh.AddVert(vert);
vert.position = new Vector2(corner1.x, corner2.y);
vert.color = color;
vh.AddVert(vert);
vert.position = new Vector2(corner2.x, corner2.y);
vert.color = color;
vh.AddVert(vert);
vert.position = new Vector2(corner2.x, corner1.y);
vert.color = color;
vh.AddVert(vert);
//添加三角形到 緩沖區中
vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}
}
大概效果就是這樣:
那麼它是如何被渲染出來的呢?
首先這個方法會在填充網格資料的時候調用。
在onEnable時 ,
會把自己graphic和對應的Canvas的引用注冊到GraphicRegistry的 m_Graphics 字典裡 ,
GraphicRegistry. RegisterGraphicForCanvas
如果沒有canvas就會去上級節點擷取所有的canvas
然後
關于它的調用順序:
onEnble->SetAllDirty-> Rebuild->UpdateGeometry> DoMeshGeneration
關于Rebuild
判斷目前是哪個Dirty 然後進行更新,首次會SetAllDirty 是以
UpdateGeometry 以級UpdateMaterial 都會被調用
Graphic.UpdateGeometry 更新幾何
判斷是否使用遺産mesh生成
遺産用Mesh 不是用VertextHelper
接着是 做網格資料的生成
OnPopulateMesh 填充網格資料 也就是頂點 資料 填充到 s_vertexHelper變量裡,然後 取得 所有的IMeshModifier 元件
把s_vertexHelper作為參數 傳遞過去。
然後進行通知網格資料改變,進行網格資料修改
然後再填充到workerMesh上
最後 調用SetMesh 把workerMesh作為參數傳遞到canvasRender元件裡,
最後再送出資料到根對象canvas上,進行判斷合批等等
最後再送出資料出去 進行渲染。
關于Unity UGUI做的合批
合批這部分Unity貌似沒公開,然後可以通過下面這個文章參考
基于4.6的 有點舊
UGUI優化源碼合批
canvas底下的 更新方法,通過過一個靜态的 委托 willRenderCanvases進行注冊
關于它的 注冊的時機,在CanvasUpdateRegistry的構造函數裡
關于 CanvasUpdateRegistry.PerformUpdate
CanvasUpdateRegistry它是這個單例類,負責所有畫布 元素的rebuild注冊;
因為所有畫布元素都是繼承自 ICanvasElement 接口的。
CanvasUpdateRegistry在構造出來的時候,就把PerformUpdate注冊進去了
willRenderCanvases 就是底層的SendWillRenderCanvas,網格重建,
關于它的調用次數的監聽,以及那些元素導緻了這個方法調用
可以查考這個 連結,具體觸發機制我也不太清楚,一般來說應該是 Canvas的 layout或者Graphic發生變化時,Unity3D 底層幫我們進行 網格合批,等一系列判斷處理後,再發送這個事件給UGUI,然後觸發這個PreformUpdate方法。 然後它會去通知注冊進來的 元素,layoutQueue,以及GraphicQueue,然後調用它們的Rebuild方法, 關于Graphic的Rebuild方法是會根據本身的 dirty 标志位,判斷是否真的需要Rebuild。
關于PerformUpdate的事情:
- Layout的Rebuild
-
Graphic的Rebuild
1與2的不同點就在于 Layout要先按照子物體多少進行排序,而2并沒有。
具體邏輯:
會從m_LayoutRebuildQueue 按子物體的多少進行排序,越多的越靠前,然後進行周遊 調用 rebuild,分别是
順序是 preLayout ,layout ,postLayout,0,1,2 意思就是 布局Rebuild 以級先後
最後再 是 layoutComlete.
接着是進行Render了 ,從preRender 到LaterRender的 Rebuild
render的 Rebuild 是存在 m_GraphicRebuildQueue 這個隊列裡的
然後周遊 調用 graphicUpdateComplete 表示渲染 完成
以上這些 RebuildQueue是如何添加的呢
其實是在 調用SetXXXDirty的時候,标志髒,在Graphic中
SetVerticesDirty 會添加到 m_GraphicRebuildQueue 當中,
SetLaoutDirty 會添加到 m_LayoutRebuildQueue 中
SetMaterialDirty 會添加到m_GraphicRebuildQueue 中
而這些髒标志會 什麼時候調用呢
一個是OnTransformParentChanged
在父親Transform發生變化時, 旋轉,位置,縮放等等;
以級OnRectTransformDimensionsChange
在RectTransform次元發生變化時
如果關聯的RectTransform的次元被更改,則調用此回調。這個調用也會對所有的子rect轉換進行調用,即使子轉換本身沒有改變——它可能會改變,這取決于它的錨定
SetVerticesDirty還會在 顔色發生變化時調用.
SetMaterialDirty還會在 材質進行替換時調用
SetAllDirty 會在 OnEnable,Reset,OnDidApplyAnimationProperties 動畫屬性變化時調用
是以呢,項目裡隐藏,可以改變Alpha,或者移到看不見的地方,這樣就不會 觸發SetAllDirty了 而是render 或者是vertices的更新了。
此外
畫布分離,動靜分離的優化就很有必要了。
總結下, Graphic的SetDirty 會添加到 CanvasUpdateRegistry的 單例的 m_layoutRebuildQueue 以級 m_GraphicRebuildQueue中
這個單例在構造出來的時候,會把 performUpdate 注冊到
canvas的 靜态事件變量 willRenderCanvases 裡,
PerformUpdate 會先處理 layOutReBuildQueue,進行按子物體量進行排序,再判斷是否可以ReBuild再依次調用 preLayout,layout,lateyout 3個Rebuild
最後才是layoutComplete , 然後 類似的 處理GraphicRebuildQueue;
而這個Rebuild 實際上是由Graphic處理的,Graphic是繼承自ICanvasElement的,而對于Graphic而言呢,隻在PreRender的時候,進行了 幾何學,材質的處理。
ICanvasElement 接口
最後 OnPopulateMesh 隻是 在UpdateGeometry 時,調用了;
渲染過程的調用堆棧,在UnityEditor模式下
因為我使用了快捷鍵進行了激死激活,是以可以看到調用了key事件
HanleKeyEvent的派發過程
是以它的調用順序在編輯器下是
UnityEditor.DrivenRectTransformUndo:ForceUpdateCanvases ()
UnityEngine.Canvas:ForceUpdateCanvases ()
UnityEngine.Canvas:SendWillRenderCanvases 事件
UnityEngine.UI.CanvasUpdateRegistry:PerformUpdate
UnityEngine.UI.Graphic:Rebuild (CanvasUpdate)
ImeshModifier mesh改變接口
關于Graphic 的優化Tips 技巧:
- 減少SetXXXDirty 次數,減少激死激活次數,例如消失隐藏,可以使用alpha或者位置的偏移來代替 激死激活,
- Canvas動靜分離
- UI界面 動畫盡量不要使用 動畫控制器,使用DOTween等動畫插件來實作界面動畫。