NGUI中穿插粒子或者mesh渲染层级
在项目中由于特效的层级与NGUI UI的层级不太还规范,导致特效的渲染层级较为杂乱于是就想把特效层级与NGUI的层级管理混合在一起;
在修改之前首先要了解NGUI的层级管理以及DC的合并:
在NGUI中层级的管理以及Drawcall的合并都是由UIPanel这个组件来完成的;在NGUI中UIpanel就相当于UGUI中canvas和canvasrender,在UIpanel中会维护两个队列分别是UIWidget和UIDrawcall的队列并按照深度排序;
每当该UIPanel下有UIWidget的信息改变例如可见性或者是大小等改变的时候这个UIpanel就会刷新该panel的所有UIWidget并重新生成UIdrawcall进行合并;而说到合并Drawcall在NGUI中是按照就近合并的原则:当下一个Drawcall(即UIWidget组件的派生组件如UIsprite等的材质,贴图和shader)和上一个完全一样的时候则合并两个Drawcall;在生成并排序合并之后就开始按照既定的顺序开始指定每个Drawcall的渲染层级,而对于同一个Drawcall的不同元素则通过shader进行相对排序显示。基本流程讲完了上代码:
1 void LateUpdate () 2 { 3 #if UNITY_EDITOR 4 if (mUpdateFrame != Time.frameCount || !Application.isPlaying) 5 #else 6 if (mUpdateFrame != Time.frameCount) 7 #endif 8 { 9 mUpdateFrame = Time.frameCount; 10 11 // Update each panel in order 12 for (int i = 0, imax = list.Count; i < imax; ++i) 13 list[i].UpdateSelf(); 14 // updateself开始刷新widget并排序 15 int rq = 3000; 16 17 // Update all draw calls, making them draw in the right order 18 for (int i = 0, imax = list.Count; i < imax; ++i) 19 { 20 UIPanel p = list[i]; 21 22 if (p.renderQueue == RenderQueue.Automatic) 23 { 24 // 循环分配渲染层级 25 p.startingRenderQueue = rq; 26 p.UpdateDrawCalls(); 27 rq += p.drawCalls.Count; 28 } 29 else if (p.renderQueue == RenderQueue.StartAt) 30 { 31 // 循环分配渲染层级 32 p.UpdateDrawCalls(); 33 if (p.drawCalls.Count != 0) 34 rq = Mathf.Max(rq, p.startingRenderQueue + p.drawCalls.Count); 35 } 36 else // Explicit 37 { 38 // 循环分配渲染层级 39 p.UpdateDrawCalls(); 40 if (p.drawCalls.Count != 0) 41 rq = Mathf.Max(rq, p.startingRenderQueue + 1); 42 } 43 } 44 } 45 } 46 void UpdateSelf () 47 { 48 mUpdateTime = RealTime.time; 49 50 UpdateTransformMatrix(); 51 UpdateLayers(); 52 // 更新widget并排序 53 UpdateWidgets(); 54 55 if (mRebuild) 56 { 57 mRebuild = false; 58 // 重新绘制合并排序DC 59 FillAllDrawCalls(); 60 } 61 else 62 { 63 // 移除不可用dc 64 for (int i = 0; i < drawCalls.Count; ) 65 { 66 UIDrawCall dc = drawCalls[i]; 67 68 if (dc.isDirty && !FillDrawCall(dc)) 69 { 70 UIDrawCall.Destroy(dc); 71 drawCalls.RemoveAt(i); 72 continue; 73 } 74 ++i; 75 } 76 } 77 78 if (mUpdateScroll) 79 { 80 mUpdateScroll = false; 81 UIScrollView sv = GetComponent<UIScrollView>(); 82 if (sv != null) sv.UpdateScrollbars(); 83 } 84 }
1 void FillAllDrawCalls () 2 { 3 for (int i = 0; i < drawCalls.Count; ++i) 4 UIDrawCall.Destroy(drawCalls[i]); 5 drawCalls.Clear(); 6 7 Material mat = null; 8 Texture tex = null; 9 Shader sdr = null; 10 UIDrawCall dc = null; 11 // widget排序 12 if (mSortWidgets) SortWidgets(); 13 // 根据既定顺序开始生成Drawcall并合并 14 for (int i = 0; i < widgets.Count; ++i) 15 { 16 UIWidget w = widgets[i]; 17 18 if (w.isVisible && w.hasVertices) 19 { 20 Material mt = w.material; 21 Texture tx = w.mainTexture; 22 Shader sd = w.shader; 23 24 if (mat != mt || tex != tx || sdr != sd) 25 {
// 跟上一个不同重新生成DC 26 if (dc != null && dc.verts.size != 0) 27 { 28 drawCalls.Add(dc); 29 dc.UpdateGeometry(); 30 dc = null; 31 } 32 33 mat = mt; 34 tex = tx; 35 sdr = sd; 36 } 37 38 if (mat != null || sdr != null || tex != null) 39 { 40 if (dc == null) 41 {
// 生成dc 42 dc = UIDrawCall.Create(this, mat, tex, sdr); 43 dc.depthStart = w.depth; 44 dc.depthEnd = dc.depthStart; 45 dc.panel = this; 46 47 } 48 else 49 {
// 合并dc 50 int rd = w.depth; 51 if (rd < dc.depthStart) dc.depthStart = rd; 52 if (rd > dc.depthEnd) dc.depthEnd = rd; 53 } 54 55 w.drawCall = dc; 56 57 if (generateNormals) w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, dc.norms, dc.tans); 58 else w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, null, null); 59 } 60 } 61 else w.drawCall = null; 62 } 63 64 if (dc != null && dc.verts.size != 0) 65 { 66 drawCalls.Add(dc);
// 绘制dc 67 dc.UpdateGeometry(); 68 } 69 }
在NGUI中所有的UI组件都是继承自UIwidget这个容器;在UIwidget组件中可以看见他维护了这样几个属性:材质,贴图,以及shader,还有该容器所属的UIpanel和该容器的Drawcall;
1 public UIPanel panel; 2 public UIDrawCall drawCall; 3 4 public virtual Material material 5 { 6 get 7 { 8 return null; 9 } 10 set 11 { 12 throw new System.NotImplementedException(GetType() + " has no material setter"); 13 } 14 } 15 16 public virtual Texture mainTexture 17 { 18 get 19 { 20 Material mat = material; 21 return (mat != null) ? mat.mainTexture : null; 22 } 23 set 24 { 25 throw new System.NotImplementedException(GetType() + " has no mainTexture setter"); 26 } 27 } 28 29 public virtual Shader shader 30 { 31 get 32 { 33 Material mat = material; 34 return (mat != null) ? mat.shader : null; 35 } 36 set 37 { 38 throw new System.NotImplementedException(GetType() + " has no shader setter"); 39 } 40 } 41 42 virtual public void OnFill(BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols) 43 { 44 // Call this in your derived classes: 45 //if (onPostFill != null) 46 // onPostFill(this, verts.size, verts, uvs, cols); 47 }
材质,贴图,以及shader,还有该容器所属的UIpanel和该容器的Drawcall;
以及一个虚函数OnFill;这些是对渲染来说是主要的属性;
而将特效层级插入的思路则是给特效上添加一个widget组件并归入NGUI渲染排序中,这样就可以和讨巧的去管理整个渲染顺序:
首先新建一个继承自UIWidget的UINGUIEffect组件:
1 using UnityEngine; 2 using System.Collections; 3 4 // 该组件必须有渲染组件 5 [RequireComponent(typeof(Renderer))] 6 public class UINGUIEffect : UIWidget { 7 // 维护材质 贴图 以及 shader 8 private Material mMaterial; 9 private Texture mMainTexture; 10 private Shader mShader; 11 private Renderer mRender; 12 13 // 重写 14 public override Material material 15 { 16 get 17 { 18 return mMaterial; 19 } 20 21 set 22 { 23 mMaterial = value; 24 } 25 } 26 27 public override Shader shader 28 { 29 get 30 { 31 return mShader; 32 } 33 34 set 35 { 36 mShader = value; 37 } 38 } 39 40 public override Texture mainTexture 41 { 42 get 43 { 44 return mMainTexture; 45 } 46 47 set 48 { 49 mMainTexture = value; 50 } 51 } 52 53 protected override void Awake() 54 { 55 if (GetComponent<Renderer>()) 56 { 57 mRender = GetComponent<Renderer>(); 58 mMaterial = mRender.sharedMaterial; 59 mMainTexture = mRender.sharedMaterial.mainTexture; 60 mShader = mRender.sharedMaterial.shader; 61 } 62 // 这里缓存设置Drawcall渲染层级时的回调 63 onRenderQueueChanged = OnRQChanged; 64 base.Awake(); 65 } 66 67 void OnRQChanged(int rq) 68 { 69 // 回调指定该渲染层级 70 GetComponent<Renderer>().sharedMaterial.renderQueue = rq; 71 72 } 73 74 protected override void OnInit() 75 { 76 base.OnInit(); 77 } 78 79 protected override void OnStart() 80 { 81 base.OnStart(); 82 } 83 84 protected override void OnEnable() 85 { 86 base.OnEnable(); 87 } 88 89 protected override void OnDisable() 90 { 91 base.OnDisable(); 92 } 93 94 protected override void OnUpdate() 95 { 96 base.OnUpdate(); 97 } 98 99 public override void OnFill(BetterList<Vector3> verts, BetterList<Vector2> uvs, BetterList<Color32> cols) 100 { 101 // 创建一个空四边形占据一个dc;如过这里是空的话该组件就不会生成dc所以必须有一个; 102 verts.Add(new Vector3(1, 0, 1)); 103 verts.Add(new Vector3(1, 0, -1)); 104 verts.Add(new Vector3(-1, 0, 1)); 105 verts.Add(new Vector3(-1, 0, -1)); 106 107 uvs.Add(new Vector2(1, 1)); 108 uvs.Add(new Vector2(1, 0)); 109 uvs.Add(new Vector2(0, 1)); 110 uvs.Add(new Vector2(0, 0)); 111 112 cols.Add(new Color32(255,255,255, 255)); 113 cols.Add(new Color32(255, 255, 255, 255)); 114 cols.Add(new Color32(255, 255, 255, 255)); 115 cols.Add(new Color32(255, 255, 255, 255)); 116 117 base.OnFill(verts, uvs, cols); 118 if (onPostFill != null) 119 onPostFill(this, verts.size, verts,uvs,cols); 120 } 121 }
因此NGUi代码就需要稍作需改:
//添加委托 public delegate void OnRenderQueueChanged(int renderQueue); //在UIWidget 以及UIdrawcall中增加委托 public OnRenderQueueChanged onRenderQueueChanged; //在UIPanel中FillAllDrawCalls方法中生成Drawcall时将widget维护的委托赋给它的dc if (mat != null || sdr != null || tex != null) { if (dc == null) { dc = UIDrawCall.Create(this, mat, tex, sdr); dc.depthStart = w.depth; dc.depthEnd = dc.depthStart; dc.panel = this; // 赋值委托 dc.onRenderQueueChanged = w.onRenderQueueChanged; } //最后在UIdrawcall设置renderQueque时调用委托设置特效层级 public int renderQueue { get { return mRenderQueue; } set { if (mRenderQueue != value) { mRenderQueue = value; // 调用回调设置特效层级 if (onRenderQueueChanged != null) onRenderQueueChanged(mRenderQueue); if (mDynamicMat != null) { mDynamicMat.renderQueue = value; #if UNITY_EDITOR if (mRenderer != null) mRenderer.enabled = isActive; #endif } } } }
这样特效的层级就可以归入NGUI的渲染层级管理中了
优化:在一些共享材质的效果上会引起渲染混乱所以在Awake的时候复制一个材质避免影响其他界面显示:
1 protected override void Awake() 2 { 3 if (GetComponent<Renderer>()) 4 { 5 mRender = GetComponent<Renderer>(); 6 mMaterial = new Material(mRender.sharedMaterial) ; 7 GetComponent<Renderer>().sharedMaterial = mMaterial; 8 mMainTexture = mMaterial.mainTexture; 9 mShader = mMaterial.shader; 10 } 11 onRenderQueueChanged = OnRQChanged; 12 base.Awake(); 13 } 14 15 void OnRQChanged(int rq) 16 { 17 if (!gameObject.activeSelf) return; 18 mMaterial.renderQueue = 0; 19 mMaterial.renderQueue = rq; 20 }
这是一个较为粗糙的做法,还有很多不足,如果有更好的做法还请留言相告谢谢!!!!好了回去撸代码了