NGUI渲染原理:UIDrawCall\UIPanel

UIDrawCall:所有的NGUI渲染都是通过UIDrawCall这个类实现的。
UIDrawCall的实现原理前面已经讲过了:NGUI Panel裁剪、层级实现原理 
核心方法及属性:
UIDrawCall:矩形渲染,本身是new出来的GameObject,动态挂载了UIDraw、Mesh、Render等渲染组件。一个DC可以包含多个Widget,但一个Widget只能有一个DC。
        property:
        verts/triangles/norms/tans/uvs/cols:Mesh渲染相关参数,顶点、三角形面、法线、切线、uv、颜色
        isDirty:标记dc是否改变,是否需要更新
        mRebuildMat:标记材质是否发生变化,true的话会调用RebuildMaterial重新生成材质球
        depthStart/depthEnd:渲染层级
        renderQueue/sortingOrder:get;set;材质球渲染层级
        baseMaterial/dynamicMaterial/mainTexture/shader:渲染相关参数
        mMesh/mFilter/mRenderer:渲染组件
        
        function:
        CreateMaterial:创建mDynamicMat,会根据panel.clipping和mClipCount加载对应的shader并赋值给mDynamicMat
        RebuildMaterial/UpdateMaterials:重新生成/更新材质球,在UpdateGeometry填充完Mesh后调用
        UpdateGeometry:核心方法,在UIPanel的FillAllDrawCalls、FillDrawCall调用,更新geometry,生成和设置mFilter、mMesh、mRenderer,设置mesh的顶点、uv、颜色、三角形面、法线、切线等
        OnWillRenderObject:调用SetClipping更新mDynamicMat shader的_ClipRange、_ClipArgs属性,该属性用于裁剪
        SetClipping:设置裁剪信息
        Create:创建一个UIDrawCall,这边NGUI有做缓存池(mInactiveList缓存暂时不用的)。HideFlags.HideAndDontSave不在界面显示
这个类主要做以下几件事:
  1. 生成dc对象,需要先生成GameObject用于绑定。
static UIDrawCall Create (string name)
   {
      while (mInactiveList.size > 0)
      {
         UIDrawCall dc = mInactiveList.Pop();
 
         if (dc != null)
         {
            mActiveList.Add(dc);
            if (name != null) dc.name = name;
            NGUITools.SetActive(dc.gameObject, true);
            return dc;
         }
      }
 
      GameObject go = new GameObject(name);
      DontDestroyOnLoad(go);
      UIDrawCall newDC = go.AddComponent<UIDrawCall>();
      // Create the draw call
      mActiveList.Add(newDC);
      return newDC;
   }
  1. 把所属的widget的顶点、uv等信息填充到自己的verts/triangles/norms/tans/uvs/cols,在UIPanel的更新DC方法里调用,widgetsInDrawCall是当前DC的widget列表缓存。
foreach (var widget in widgetsInDrawCall) {
   if (generateNormals) {
      widget.WriteToBuffers(dc.verts, dc.uvs,dc.cols, dc.norms, dc.tans, generateUV2? dc.uv2:null);
   } else {
      widget.WriteToBuffers(dc.verts, dc.uvs,dc.cols, null, null, generateUV2? dc.uv2:null);
   }
}
widgetsInDrawCall.Clear();
  1. 填充Mesh显示所需要的顶点、颜色、uv、法线等信息,UpdateGeometry方法。
  1. 生成临时的材质球,设置材质球的贴图、shader等信息,CreateMaterial方法。
  1. 设置shader的裁剪属性,已实现先UIPanel的裁剪功能,OnWillRenderObject和SetClipping方法,最终裁剪的实现在shader里。
  1. 到这里,一个DC就可以在unity里显示出来了。
UIPanel:在lateUpdate轮询所属widget变化,更新DC的Mesh、层级、裁剪裁剪、坐标等信息。
核心属性/方法:
UIPanel:UIRect。
        property:
        renderQueue/startingRenderQueue:渲染队列
        widgets:panel管理的渲染对象
        drawCalls:panel生成的渲染dc
        worldToLocal:世界坐标转成相对panel的本地坐标矩阵
        drawCallClipRange:特殊裁剪区域,DC使用。new Vector4(finalClipRegion.x, finalClipRegion.y, finalClipRegion.z/2, finalClipRegion.w/2)
        mAlpha:透明度,会影响所有子对象
        mDepth/mSortingOrder:渲染层级相关
        mRebuild:是否需要重绘界面。需要重新设置所有的DC
        mResized:是否需要更新widget的可见性
        
        nextUnusedDepth:获取当前已使用的最大depth+1,可以用于界面的depth动态管理
        alpha:透明度,会直接影响子节点的透明度
        mClipRange:显示范围,范围以外的widgit不显示。vector4(x,y,z,w),其中x/y是坐标,z/w是范围大小
        clipCount:总裁剪次数,一个panel裁剪次数加1(父panel和自己本身)
        clipOffset:裁剪区域偏移,通过值偏移实现Scroll views,避免了移动造成的界面重建
        finalClipRegion:Vector4(mClipRange.x + mClipOffset.x, mClipRange.y + mClipOffset.y, mClipRange.z, mClipRange.w),x,y是矩形中心点坐标
        clipSoftness:裁剪时的渐隐特效范围大小(例如y=4代表从顶部往下、底部往上4个像素渐变)
        worldCorners/localCorners:裁剪区域(矩形)四个顶点的坐标
        
        function:
        CalculateFinalAlpha:获取panel的最终透明度,该透明根据当前panel的alpha*所有父panel的alpha(panel的多层嵌套)
        SetRect:设置裁剪区域mClipRange
        IsVisible:判断某个widget或position是否在裁剪范围内
        RebuildAllDrawCalls:设置mRebuild = true,重绘整个panel
        GetViewSize:获取界面显示范围,即裁剪区域
 
        OnDisable:隐藏界面会删除UIPanel所属的所有的UIDrawCall,在下次LateUpdate时需要重新生成,LateUpdate在对象Active时每帧调用
        LateUpdate:更新界面,调用顺序UIPanel.UpdateSelf-UIPanel.UpdateDrawCalls,先更新dc的渲染信息,在更新DC坐标
        UpdateTransformMatrix:设置当前更新帧,设置Panel的显示范围mMin/mMax
        UpdateWidgets:调用child widget的UpdateTransform/UpdateVisibility/UpdateGeometry进行更新
        UpdateSelf:更新所有widget和DrawCall,更新UIScrollView.调用UpdateWidgets/FillAllDrawCalls.
        FillAllDrawCalls:删除当前所有的DC对象重新生成dc,性能很差。
                核心方法 1.根据depth排序widget。2.若widget可见,加入dc。3.如果当前widget的material、mainTexture、shader和上一个相等,共用一个dc。4.把widget的geometry内的顶点、纹理、颜色等信息写入dc 5.调用dc.UpdateGeometry更新DC渲染
        FillDrawCall:更新单个dc,只要dc.verts.Count != 0,也就是说当前dc有顶点需要渲染,调用dc.UpdateGeometry)更新Mesh信息
        FindDrawCall:将某个widget加入到已存在的material、mainTexture、shader相等的dc内
        AddWidget:添加指定widget到当前panel中
        RemoveWidget:移除指定widget,如果widget的(depth == w.drawCall.depthStart || depth == w.drawCall.depthEnd),会触发整个panel重建
        UpdateDrawCalls:更新drawCallClipRange(UIDrawcall裁剪区域)、所有drawCalls的坐标、旋转、缩放、renderQueue、sortingOrder等属性

 

lateUpdate更新流程图:
核心方法就是FillAllDrawCalls,负责DC的生成(合批也在这里处理)。
FillDrawCall更新单个的DC。
posted @ 2020-08-15 15:59  柯腾_wjf  阅读(475)  评论(0编辑  收藏  举报