如何判断选择哪种凭证uitweener是哪一种

安全检查中...
请打开浏览器的javascript,然后刷新浏览器
< 浏览器安全检查中...
还剩 5 秒&you have been blocked安全检查中...
请打开浏览器的javascript,然后刷新浏览器
< 浏览器安全检查中...
还剩 5 秒&NGUI(24)
之前在 文中就这样用过这样的一个例子:
&&&&&&&&&&&&&&& UIGeometry好比为煮菜准备食材,UIDrawCall好比是煮菜的工具(锅,炉子等),UIPanel就是大厨了决定着什么时候该煮菜,UIWidget(UILabel,UISprite和UITexture)是这道菜怎么样的最终呈现。
&&&&&&&& 本来不打算继续写UIPanel的内容的,因为没有这么深刻的需求,后面自己根据FastGUI生成的UI发现DrawCall数很多:
&&&&&& 一个很简单的界面竟然用了9个DrawCall,相同的material没有进行DrawCall完全的合并,相对于以前一个Material一个DrawCall是不可接受的,所以这样就很必要去看下UIPanel都做了哪些事情,看下了NGUI的更新日志有这样的一句话:
&&&&&& 3.0.0:
&&&&&& - NEW: Changed the way widgets get batched, properly fixing all remaining Z/depth issues.
&&&&&& - NEW: Draw calls are now automatically split up as needed (no more sandwiching issues!)
&&&&&& NGUI之前的版本关于组件的显示跟Z周,depth以及图集和UIPanel的关系一直都受到大家吐槽和诟病(尤其夹层问题),所以NGUI3.0.3就彻底解决这个问题:使用DrawCall切割,然后由depth完全决定组件显示的前后。
&&&&&&& 也就是说,NGUI对DrawCall进行了分割处理,导致DrawCall数量“剧烈”增加,所以要解决DrawCall数量增加,就要UIPanel产生一个UIDrawCall的原理,然后减少UIDrawCall的生成或进行合并。
&&&&&&& 从上图,可以发现NGUI还是对部分组件进行了UIDrawCall合并——多个UIWidget使用同一个UIDrawCall,所以要想做到同一个Material使用一个UIDrawCall理论上是完全可行的。
再说UIWidget,UIGeometry&UIDrawCall
&&&&&&& 虽然已经有
一文,但是由于之前是在几乎忽略UIPanel的情况下理顺UIWiget,UIGeometry&UIDrawCall三者的关系的,所以文中的组织逻辑比较混乱,条理不强,加上本文也是建立者三者之上的,作为行为的结构的流畅性和完整性,所以还是在简要交代下。
&&&&&& 上图是UIWidget,UIGeometry&UIDrawCall的关系图,UIWidget用于UIDrawcall mDrawCall和UIGeometry mGeo两个成员变量,其中UIGeometry就是对UIWidget的顶点vertices,uvs和color进行存储和更新,UIDrawCall就是根据提供的数据(统一在UIPanel指派)进行渲染绘制。
&&&&&&& UIGeometry完全由UIWidget维护,首先UILabel,UISprite,UITexture对UIWidget的OnFill进行重写——初始化mGeo的verts,uvs,cols的BetterList。然后UIWidget的UpdateGeometry函数对UIGeometry的ApplyTransform()和WriteToBuffer()调用进行更新。
&&&&&&&& 每一个UIWidget都有一个UIGeometry,但是并不都有一个UIDrawCall,而是要通过Batch合并达到减少DrawCall的数量,UIDrawCall是由UIPanel生成的。至于什么是DrawCall,因为没有3D引擎经验,只能从只言片语中拾获一点理解:
&&&&&&&&&&&& “Unity(或者说基本所有图形引擎)生成一帧画面的处理过程大致可以这样简化描述:引擎首先经过简单的可见性测试,确定摄像机可以看到的物体,然后把这些物体的顶点(包括本地位置、法线、UV等),&&&&&& 索引(顶点如何组成三角形),变换(就是物体的位置、旋转、缩放、以及摄像机位置等),相关光源,纹理,渲染方式(由材质/Shader决定)等数据准备好,然后通知图形API——或者就简单地看作是通知GPU&&&&&&
——开始绘制,GPU基于这些数据,经过一系列运算,在屏幕上画出成千上万的三角形,最终构成一幅图像。 在Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。这一过程是逐个物体进行的,对&&&&&& 于每个物体,不只GPU的渲染,引擎重新设置材质/Shader也是一项非常耗时的操作。因此每帧的Draw Call次数是一项非常重要的性能指标。”
&&&&&& NGUI被说的最多的优点就是:减少DrawCall数量。但现在为了解决sandwiching
issues和Z/depth issues,对DrawCall进行split。
NGUI指派DrawCall的原理
&&&&&& 前面说到,UIDrawCall是由UIPanel生成指派的,哪些UIWiget共用(也就是Batch)一个DrawCall在UIPanel中决定的。UIDrawCall有一个静态变量:
& static public BetterList&UIDrawCall& list =
new BetterList&UIDrawCall&();&
/// &summary&
/// All draw calls created by the panels.
/// &/summary&
static public BetterList&UIDrawCall& list = new BetterList&UIDrawCall&();
&&&&& 也就是说所有的UIDrawCall都会保存在list中,都说“大蛇要打七寸”,只要找到哪里有 list.add 的调用就知道生成增加了一个UIDrawCall,这样就找到GetDrawCall函数(也可以通过MonoBehaviour的调试功能打断点进行函数跟踪):
&&& & & &&& UIDrawCall GetDrawCall (int index, Material mat)&
&&& {& &&&&&&& if (index & UIDrawCall.list.size)&
&&&&&&& {& &&&&&&&&&&& UIDrawCall dc = UIDrawCall.list.buffer[index];& & &&&&&&&&&&& &
&&&&&&&&&&& if (dc != null && dc.panel ==
this && dc.baseMaterial == mat && dc.mainTexture == mat.mainTexture)
return& & &&&&&&&&&&& &
&&&&&&&&&&& for (int i = UIDrawCall.list. i & )&
&&&&&&&&&&& {& &&&&&&&&&&&&&&& UIDrawCall rem = UIDrawCall.list.buffer[--i];& &&&&&&&&&&&&&&& DestroyDrawCall(rem, i);& &&&&&&&&&&& }& &&&&&&& }& #if UNITY_EDITOR& &&&&&&& &
&&&&&&& GameObject go = UnityEditor.EditorUtility.CreateGameObjectWithHideFlags(&_UIDrawCall [& &#43; mat.name &#43;
&]&,& &&&&&&&&&&& &
&&&&&&&&&&& HideFlags.HideAndDontSave);& #else& &&&&&&& GameObject go = new GameObject(&_UIDrawCall [& &#43; mat.name &#43;
&]&);& &&&&&&& DontDestroyOnLoad(go);& #endif& &&&&&&& go.layer = cachedGameObject.& &&&&&&&&& &&&&&&& &
&&&&&&& UIDrawCall drawCall = go.AddComponent&UIDrawCall&();& &&&&&&& drawCall.baseMaterial =& &&&&&&& drawCall.renderQueue = UIDrawCall.list.& &&&&&&& drawCall.panel = this;&
&&&&&&& UIDrawCall.list.Add(drawCall);& &&&&&&& return drawC& &&& }&
/// &summary&
/// Get a draw call at the specified index position.
/// &/summary&
UIDrawCall GetDrawCall (int index, Material mat)
if (index & UIDrawCall.list.size)
UIDrawCall dc = UIDrawCall.list.buffer[index];
// If the material and texture match, keep using the same draw call
if (dc != null && dc.panel == this && dc.baseMaterial == mat && dc.mainTexture == mat.mainTexture)
// Otherwise we need to destroy all the draw calls that follow
for (int i = UIDrawCall.list. i & )
UIDrawCall rem = UIDrawCall.list.buffer[--i];
DestroyDrawCall(rem, i);
#if UNITY_EDITOR
// If we&#39;re in the editor, create the game object with hide flags set right away
GameObject go = UnityEditor.EditorUtility.CreateGameObjectWithHideFlags(&_UIDrawCall [& + mat.name + &]&,
//HideFlags.DontSave | HideFlags.NotEditable);
HideFlags.HideAndDontSave);
GameObject go = new GameObject(&_UIDrawCall [& + mat.name + &]&);
DontDestroyOnLoad(go);
go.layer = cachedGameObject.
// Create the draw call
UIDrawCall drawCall = go.AddComponent&UIDrawCall&();
drawCall.baseMaterial =
drawCall.renderQueue = UIDrawCall.list.
drawCall.panel =
//Debug.Log(&Added DC & + mat.name + & as & + UIDrawCall.list.size);
UIDrawCall.list.Add(drawCall);
return drawC
&&&&&& 进一步找到Fill()的调用:
& & static void Fill ()&
{& &&& for (int i = UIDrawCall.list. i & 0; )&
&&&&&&& DestroyDrawCall(UIDrawCall.list[--i], i);& & &&& int index = 0;& &&& UIPanel pan = null;& &&& Material mat = null;& &&& UIDrawCall dc = null;& & &&& for (int i = 0; i & UIWidget.list. )&
&&& {& &&&&&&& UIWidget w = UIWidget.list[i];& & &&&&&&& if (w == null)&
&&&&&&& {& &&&&&&&&&&& UIWidget.list.RemoveAt(i);& &&&&&&&&&&& continue;& &&&&&&& }& & &&&&&&& if (w.isVisible && w.hasVertices)&
&&&&&&& {& &&&&&&&&&&& if (pan != w.panel || mat != w.material)&&&
& &&&&&&&&&&& {& &&&&&&&&&&&&&&& if (pan != null && mat !=
null && mVerts.size != 0)& &&&&&&&&&&&&&&& {& &&&&&&&&&&&&&&&&&&& pan.SubmitDrawCall(dc);& &&&&&&&&&&&&&&&&&&& dc = null;&
&&&&&&&&&&&&&&& }& & &&&&&&&&&&&&&&& pan = w.& &&&&&&&&&&&&&&& mat = w.& &&&&&&&&&&& }& & &&&&&&&&&&& if (pan != null && mat !=
&&&&&&&&&&& {& &&&&&&&&&&&&&&& if (dc == null) dc = pan.GetDrawCall(index&#43;&#43;, mat);&
&&&&&&&&&&&&&&& w.drawCall =& &&&&&&&&&&&&&&& if (pan.generateNormals) w.WriteToBuffers(mVerts, mUvs, mCols, mNorms, mTans);&
&&&&&&&&&&&&&&& else w.WriteToBuffers(mVerts, mUvs, mCols,
null, null);&
&&&&&&&&&&& }& &&&&&&& }& &&&&&&& else w.drawCall = null;&
&&&&&&& &#43;&#43;i;& &&& }& & &&& if (mVerts.size != 0)& &&&&&&& pan.SubmitDrawCall(dc);& }&
/// &summary&
/// Fill the geometry fully, processing all widgets and re-creating all draw calls.
/// &/summary&
static void Fill ()
for (int i = UIDrawCall.list. i & 0; )
DestroyDrawCall(UIDrawCall.list[--i], i);
int index = 0;
UIPanel pan =
Material mat =
UIDrawCall dc =
for (int i = 0; i & UIWidget.list. )
UIWidget w = UIWidget.list[i];
if (w == null)
UIWidget.list.RemoveAt(i);
if (w.isVisible && w.hasVertices)
if (pan != w.panel || mat != w.material)
if (pan != null && mat != null && mVerts.size != 0)
pan.SubmitDrawCall(dc);
if (pan != null && mat != null)
if (dc == null) dc = pan.GetDrawCall(index++, mat);
w.drawCall =
if (pan.generateNormals) w.WriteToBuffers(mVerts, mUvs, mCols, mNorms, mTans);
else w.WriteToBuffers(mVerts, mUvs, mCols, null, null);
else w.drawCall =
if (mVerts.size != 0)
pan.SubmitDrawCall(dc);
整理Fill函数的原理如下r:
&&&&&& (1) 获取UIWidget的队列UIWidget.list(已经根据depth排好序),声明一个UIPanel pan,Material mat和UIDrawCall dc,pan,mat和dc都是保存上一次循环的UIPanel,Material和UIDrawCall。
&&&&&& (2) 遍历UIWidget.list,循环体中对
当前UIWiget w的panel和material是否和当前pan,mat是否相同 进行判断,分为两种情况:
&&&&&&&&&&&&&&&&& a)如果有一种不相同,调用SubmitDrawCall函数,SubmitDrawCall函数其实就是使用pan的mVerts, mUvs,
mCols数据,调用UIDrawCall的set函数对Mesh,MeshRender,MeshFilter等进行“设置组装”。
&&&&&&&&&&&&&&&&& b)如果相同,通过调用GetDrawCall获取当前pan和mat的DrawCall,然后将UIWidget w的UIGeometry数据放入mVerts,
mUvs, mCols(通过调用函数w.WriteToBuffers(mVerts, mUvs, mCols, mNorms, mTans))&
&&&& 小结:UIPanel的mVerts,mUVs,mCols只是要将要传给UIDrawCall数据的一个“积蓄”过渡的一个概念,也就是说,Fill函数式这么操作的:先将UIWidget w的中UIGeometry的数据缓存在UIPanel的mVerts,mUVs,mCols,只有当不能再pan或mat与当前的w.panel或w.material不同时就不能再缓存了,然后通过SubmitDrawCall,生成UIDrawCall的工作才完成,然后再重新
new 一个新的UIDrawCall继续缓存数据。
UIPanel完整工作流程——LateUpdate
&&&& 前面介绍UIDrawCall的产生过程,当然这是UIPanel最重要的工作之一,在对UIDrawCall进行更新是要对UIPanel的其他信息(transform,layer,widget)等进行更新:
& & & & void LateUpdate ()& {& &&& &
&&& if (list[0] != this)
return;& & &&& & &&& for (int i = 0; i & list. &#43;&#43;i)&
&&& {& &&&&&&& UIPanel panel = list[i];& &&&&&&& panel.mUpdateTime = RealTime.& &&&&&&& panel.UpdateTransformMatrix();& &&&&&&& panel.UpdateLayers();& &&&&&&& panel.UpdateWidgets();& &&& }& &&& &
&&& if (mFullRebuild)& &&& {& &&&&&&& UIWidget.list.pareFunc);& &&&&&&& Fill();& &&& }& &&& else& &&& {& &&&&&&& for (int i = 0; i & UIDrawCall.list. )&
&&&&&&& {& &&&&&&&&&&& UIDrawCall dc = UIDrawCall.list[i];& & &&&&&&&&&&& if (dc.isDirty)&
&&&&&&&&&&& {& &&&&&&&&&&&&&&& if (!Fill(dc))&
&&&&&&&&&&&&&&& {& &&&&&&&&&&&&&&&&&&& DestroyDrawCall(dc, i);& &&&&&&&&&&&&&&&&&&& continue;&
&&&&&&&&&&&&&&& }& &&&&&&&&&&& }& &&&&&&&&&&& &#43;&#43;i;& &&&&&&& }& &&& }& & &&& &
&&& for (int i = 0; i & list. &#43;&#43;i)&
&&& {& &&&&&&& UIPanel panel = list[i];& &&&&&&& panel.UpdateDrawcalls();& &&& }& &&& mFullRebuild = false;& }&
/// &summary&
/// Main update function
/// &/summary&
void LateUpdate ()
// Only the very first panel should be doing the update logic
if (list[0] != this)
// Update all panels
for (int i = 0; i & list. ++i)
UIPanel panel = list[i];
panel.mUpdateTime = RealTime.
panel.UpdateTransformMatrix();
panel.UpdateLayers();
panel.UpdateWidgets();
// Fill the draw calls for all of the changed materials
if (mFullRebuild)
UIWidget.list.pareFunc);
for (int i = 0; i & UIDrawCall.list. )
UIDrawCall dc = UIDrawCall.list[i];
if (dc.isDirty)
if (!Fill(dc))
DestroyDrawCall(dc, i);
// Update the clipping rects
for (int i = 0; i & list. ++i)
UIPanel panel = list[i];
panel.UpdateDrawcalls();
mFullRebuild =
&&&&&&& 就不进行文字描述了,贴一张自己的画的LateUpdate()函数调用栈图(不光文笔不好,画图也不行,硬伤呀,就这样也是琢磨很久画的):
DrawCall数量优化
&&&&&&&& 言归正传,本文的话题就是对于NGUI3.0.4的版本(目前最新版)如何减少DrawCall, 先回到文中的第一幅图,发现两个以New atlas图集为material的DrawCall夹着一个以font为字体集的DrawCall间隔,然后使用MonoBehaviour的断点调试功能进行跟踪得到UIWidget.list队列:
&&&&&&& 发现一个规律:使用相同material的连续UIWidget(UILabel,UISprite)共用一个UIDrawCall。这样就给了一个解决策略:对UIWidget.list进行排序,使得使用相同的material的UIWidget在UIWidget.list相连,而UIWidget.list是根据UIWidget的depth进行排序的。所以可以有如下两种方法:
&&&&&&& 1)修改UIWidget(UILabel,UISprite)的depth,限定好UIWidget.list的排序
&&&&&&& 2)重写UIWidget的CompareFunc方法。
& & static public
int CompareFunc (UIWidget left, UIWidget right)&
{& &&& int val = pareFunc(left.mPanel, right.mPanel);&
& &&& if (val == 0)& &&& {& &&&&&&& if (left.mDepth & right.mDepth)
return -1;& &&&&&&& if (left.mDepth & right.mDepth)
return 1;& & &&&&&&& Material leftMat = left.& &&&&&&& Material rightMat = right.& & &&&&&&& if (leftMat == rightMat)
return 0;& &&&&&&& if (leftMat != null)
return -1;& &&&&&&& if (rightMat != null)
return 1;& &&&&&&& return (leftMat.GetInstanceID() & rightMat.GetInstanceID()) ? -1 : 1;&
&&& }& &&& return& }&
/// &summary&
/// Static widget comparison function used for depth sorting.
/// &/summary&
static public int CompareFunc (UIWidget left, UIWidget right)
int val = pareFunc(left.mPanel, right.mPanel);
if (val == 0)
if (left.mDepth & right.mDepth) return -1;
if (left.mDepth & right.mDepth) return 1;
Material leftMat = left.
Material rightMat = right.
if (leftMat == rightMat) return 0;
if (leftMat != null) return -1;
if (rightMat != null) return 1;
return (leftMat.GetInstanceID() & rightMat.GetInstanceID()) ? -1 : 1;
&&&&&&&& 下面对原来对属于第三个DrawCall的两个UILabel增大他们的depth,发现DrawCall立马减少一个了,说明这个方法是可行的:
&&&&&&& 同理,重写UIWidget的CompareFunc也是可以的,按照Material的name优先排序,只有当material一样是才考虑depth进行排序:
& & static public
int CompareFunc (UIWidget left, UIWidget right)&
{& &&& int val = pareFunc(left.mPanel, right.mPanel);&
& &&& if (val == 0)& &&& {& &&&&&&& & &&&&&&&
&&&&&&&&& & &&&&&&& Material leftMat = left.& &&&&&&& Material rightMat = right.& & &&&&&&& if (leftMat == rightMat)&&
&&&&&&& {& &&&&&&&&&&& if (left.mDepth & right.mDepth)
return -1;& &&&&&&&&&&& else if (left.mDepth & right.mDepth)
return 1;& &&&&&&&&&&& else return 0;&
&&&&&&& }& &&&&&&& if(leftMat !=null & rightMat !=
null)& &&&&&&&&&&& return string.Compare(leftMat.name,rightMat.name);&
&&&&&&& if (leftMat != null)
return -1;& &&&&&&& if (rightMat != null)
return 1;& &&&&&&& return (leftMat.GetInstanceID() & rightMat.GetInstanceID()) ? -1 : 1;&
&&&&&&&&& &&& }& &&& return& }&
/// &summary&
/// Static widget comparison function used for depth sorting.
/// &/summary&
static public int CompareFunc (UIWidget left, UIWidget right)
int val = pareFunc(left.mPanel, right.mPanel);
if (val == 0)
//原理排序的方法
/*if (left.mDepth & right.mDepth) return -1;
if (left.mDepth & right.mDepth) return 1;
Material leftMat = left.
Material rightMat = right.
if (leftMat == rightMat) return 0;
if (leftMat != null) return -1;
if (rightMat != null) return 1;
return (leftMat.GetInstanceID() & rightMat.GetInstanceID()) ? -1 : 1;*/
Material leftMat = left.
Material rightMat = right.
if (leftMat == rightMat)
if (left.mDepth & right.mDepth) return -1;
else if (left.mDepth & right.mDepth) return 1;
else return 0;
if(leftMat !=null & rightMat != null)
pare(leftMat.name,rightMat.name);
if (leftMat != null) return -1;
if (rightMat != null) return 1;
return (leftMat.GetInstanceID() & rightMat.GetInstanceID()) ? -1 : 1;
&&&&&& 最终的DrawCall数量一定是等于使用的Material的数量:
还是夹层问题(sandwiching issues!)
&&&&&& 现在我们完全可以实现一个Material一个DrawCall,但是这样还是没有解决夹层的难题,NGUI给我们解决方法就是多一个DrawCall,这个其实跟3.0之前的版本多用一个UIPanel或者UIAtlas是一样的道理。这样还是感觉没有从本质上解决这个问题,只是换了一种方式权衡了一下。
&&&&&& 记得NGUI3.0之前的版本还是有Z轴的概念的,现在Z轴完全是形同虚设,但是3D引擎的图形一定是跟Z轴是密切的关系的,而最终的图形显示的位置关系是由Mesh的顶点决定的,所以可以考虑Z轴来解决夹层问题:DrawCall控制的渲染队列的次序renderQueue,Mesh控制的是实际绘制的“地理位置”,如下图所示,A和C使用相同的图集有相同的material,B单独使用一个图集,可以通过material来排序或者定制好depth,让A和C使用一个DrawCall,但是C的Mesh(参考transform的Z轴)会在B的“前面”,这样就应该可以实现夹层的效果了。
&&&&&& 做了下测试,修改Mesh的Z轴没有什么变化,然后早上向同事请教了,因为Material使用的Shader使用了透明,这样就不能做深度测试,也就是Mesh的“深度”是不影响的,这样最终的显示就跟Shader的renderQueue有关了,即renderQueue越大,显示的越靠前面(重叠的图层,renderQueue越大,越靠前)。当然现在只有增加一个DrawCall(如多使用一个UIPanel或用另外一个Material)来做到Material的夹层效果。
&&&&&&& NGUI更新的很快,之前一直也没有仔细研究,最近开始慢慢看了些,也写了些博客,主要有3点收获:1)NGUI的渲染机制,2)NGUI相关“组件”(Font,Atlas,UIWidget等)实现方法,3)NGUI的设计模式。当然D.S.Qiu觉得NGUI作为一个大的系统一定会有冗余和诟病,使用了很多“缓存”的思想,很多细节都没有处理好,所以我们都可以再努力完善,争取做“站在巨人的肩膀上”的那个人。
&&&&&&& 发现没有3D引擎以及图形渲染的基础,做点事情还是很蹩脚的,GPU的处理输出,显存的大小,CPU与GPU的交互都是要考虑的,有空的时候还是要找到这方面的书来恶补下……
&&&&&& 如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。
&&&&&&& 转载请在文首注明出处:/blog/1973651
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:53297次
排名:千里之外
转载:101篇
(1)(1)(8)(71)(20)

我要回帖

更多关于 纯种英短蓝猫怎么判断 的文章

 

随机推荐