天天看點

UnityUGUI源碼閱讀之Graphic

關于Graphic元件

Graphic元件是UGUI中比較重要的一個元件,例如Image,RawImag,MaskableGraphic 可遮罩的圖形元件 這些都是繼承自Graphic的
UnityUGUI源碼閱讀之Graphic

它必須要有 CanvasrRenderer元件以級RectTransform 元件,并且一個對象隻允許挂載一個。

RectTransform:

UnityUGUI源碼閱讀之Graphic

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);
     }
 }
           

大概效果就是這樣:

UnityUGUI源碼閱讀之Graphic

那麼它是如何被渲染出來的呢?

首先這個方法會在填充網格資料的時候調用。

在onEnable時 ,

會把自己graphic和對應的Canvas的引用注冊到GraphicRegistry的 m_Graphics 字典裡 ,

GraphicRegistry. RegisterGraphicForCanvas

如果沒有canvas就會去上級節點擷取所有的canvas

UnityUGUI源碼閱讀之Graphic

然後

關于它的調用順序:

onEnble->SetAllDirty-> Rebuild->UpdateGeometry> DoMeshGeneration

關于Rebuild

判斷目前是哪個Dirty 然後進行更新,首次會SetAllDirty 是以

UpdateGeometry 以級UpdateMaterial 都會被調用

UnityUGUI源碼閱讀之Graphic

Graphic.UpdateGeometry 更新幾何

判斷是否使用遺産mesh生成

遺産用Mesh 不是用VertextHelper

UnityUGUI源碼閱讀之Graphic

接着是 做網格資料的生成

UnityUGUI源碼閱讀之Graphic

OnPopulateMesh 填充網格資料 也就是頂點 資料 填充到 s_vertexHelper變量裡,然後 取得 所有的IMeshModifier 元件

把s_vertexHelper作為參數 傳遞過去。

然後進行通知網格資料改變,進行網格資料修改

然後再填充到workerMesh上

最後 調用SetMesh 把workerMesh作為參數傳遞到canvasRender元件裡,

最後再送出資料到根對象canvas上,進行判斷合批等等

最後再送出資料出去 進行渲染。

關于Unity UGUI做的合批

合批這部分Unity貌似沒公開,然後可以通過下面這個文章參考

基于4.6的 有點舊

UGUI優化源碼合批

canvas底下的 更新方法,通過過一個靜态的 委托 willRenderCanvases進行注冊

UnityUGUI源碼閱讀之Graphic

關于它的 注冊的時機,在CanvasUpdateRegistry的構造函數裡

UnityUGUI源碼閱讀之Graphic

關于 CanvasUpdateRegistry.PerformUpdate

CanvasUpdateRegistry它是這個單例類,負責所有畫布 元素的rebuild注冊;

因為所有畫布元素都是繼承自 ICanvasElement 接口的。

CanvasUpdateRegistry在構造出來的時候,就把PerformUpdate注冊進去了

UnityUGUI源碼閱讀之Graphic

willRenderCanvases 就是底層的SendWillRenderCanvas,網格重建,

關于它的調用次數的監聽,以及那些元素導緻了這個方法調用

可以查考這個 連結,具體觸發機制我也不太清楚,一般來說應該是 Canvas的 layout或者Graphic發生變化時,Unity3D 底層幫我們進行 網格合批,等一系列判斷處理後,再發送這個事件給UGUI,然後觸發這個PreformUpdate方法。 然後它會去通知注冊進來的 元素,layoutQueue,以及GraphicQueue,然後調用它們的Rebuild方法, 關于Graphic的Rebuild方法是會根據本身的 dirty 标志位,判斷是否真的需要Rebuild。

關于PerformUpdate的事情:

  1. Layout的Rebuild
  2. Graphic的Rebuild

    1與2的不同點就在于 Layout要先按照子物體多少進行排序,而2并沒有。

具體邏輯:

會從m_LayoutRebuildQueue 按子物體的多少進行排序,越多的越靠前,然後進行周遊 調用 rebuild,分别是

UnityUGUI源碼閱讀之Graphic

順序是 preLayout ,layout ,postLayout,0,1,2 意思就是 布局Rebuild 以級先後

UnityUGUI源碼閱讀之Graphic

最後再 是 layoutComlete.

UnityUGUI源碼閱讀之Graphic

接着是進行Render了 ,從preRender 到LaterRender的 Rebuild

render的 Rebuild 是存在 m_GraphicRebuildQueue 這個隊列裡的

然後周遊 調用 graphicUpdateComplete 表示渲染 完成

UnityUGUI源碼閱讀之Graphic

以上這些 RebuildQueue是如何添加的呢

其實是在 調用SetXXXDirty的時候,标志髒,在Graphic中

UnityUGUI源碼閱讀之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中

UnityUGUI源碼閱讀之Graphic

這個單例在構造出來的時候,會把 performUpdate 注冊到

canvas的 靜态事件變量 willRenderCanvases 裡,

UnityUGUI源碼閱讀之Graphic
UnityUGUI源碼閱讀之Graphic

PerformUpdate 會先處理 layOutReBuildQueue,進行按子物體量進行排序,再判斷是否可以ReBuild再依次調用 preLayout,layout,lateyout 3個Rebuild

最後才是layoutComplete , 然後 類似的 處理GraphicRebuildQueue;

而這個Rebuild 實際上是由Graphic處理的,Graphic是繼承自ICanvasElement的,而對于Graphic而言呢,隻在PreRender的時候,進行了 幾何學,材質的處理。

UnityUGUI源碼閱讀之Graphic

ICanvasElement 接口

UnityUGUI源碼閱讀之Graphic

最後 OnPopulateMesh 隻是 在UpdateGeometry 時,調用了;

渲染過程的調用堆棧,在UnityEditor模式下

UnityUGUI源碼閱讀之Graphic

因為我使用了快捷鍵進行了激死激活,是以可以看到調用了key事件

HanleKeyEvent的派發過程

UnityUGUI源碼閱讀之Graphic

是以它的調用順序在編輯器下是

UnityEditor.DrivenRectTransformUndo:ForceUpdateCanvases ()

UnityEngine.Canvas:ForceUpdateCanvases ()

UnityEngine.Canvas:SendWillRenderCanvases 事件

UnityEngine.UI.CanvasUpdateRegistry:PerformUpdate

UnityEngine.UI.Graphic:Rebuild (CanvasUpdate)

ImeshModifier mesh改變接口

UnityUGUI源碼閱讀之Graphic

關于Graphic 的優化Tips 技巧:

  1. 減少SetXXXDirty 次數,減少激死激活次數,例如消失隐藏,可以使用alpha或者位置的偏移來代替 激死激活,
  2. Canvas動靜分離
  3. UI界面 動畫盡量不要使用 動畫控制器,使用DOTween等動畫插件來實作界面動畫。