NGUI中UIDrawCall全流程解析

文章摘要

UIDrawCall是NGUI中封装Unity底层DrawCall的核心组件,每个实例对应一次Mesh渲染调用。其流程包括:1)由UIWidget属性变更或UIPanel脏标记触发;2)UIPanel按材质/贴图/Panel/Depth分组分配UIDrawCall对象;3)收集所属Widget的顶点数据并填充至Mesh;4)提交Unity渲染。通过对象池复用DrawCall减少GC,优化性能。关键问题包括DrawCall过多(合批失败)、Mesh数据异常或频繁创建销毁,需检查分组条件或UI动态变更。核心源码涉及UIDrawCall.cs、UIPanel.cs和UIWidget.cs。


1. UIDrawCall的作用

UIDrawCall是NGUI中对Unity底层DrawCall的封装。每个UIDrawCall对象对应一次Unity的Mesh渲染调用,负责收集一组可合批的UIWidget的顶点数据,生成Mesh并提交给Unity渲染。


2. UIDrawCall全流程详解

2.1 触发时机

  • UIWidget属性变更(如文本、图片、颜色、位置等)→ MarkAsChanged
  • UIPanel被标记为脏 → 下一帧LateUpdate时刷新

2.2 DrawCall分组与分配

入口:UIPanel.UpdateDrawCalls()

  1. 遍历所有Widget,按材质/贴图/Panel/Depth分组
  2. 为每组分配一个UIDrawCall对象
    • 复用池中已有的UIDrawCall
    • 不够用则新建
  3. Widget的drawCall字段指向所属UIDrawCall

关键源码片段(UIPanel.cs)

for (int i = 0; i < mWidgets.size; ++i)
{
    UIWidget w = mWidgets.buffer[i];
    if (!w.isVisible) continue;

    Material mat = w.material;
    Texture tex = w.mainTexture;

    if (drawCall == null || mat != lastMat || tex != lastTex)
    {
        drawCall = UIDrawCall.Create(this, mat, tex, ...);
        mDrawCalls.Add(drawCall);
        lastMat = mat;
        lastTex = tex;
    }

    w.drawCall = drawCall;
}

2.3 顶点数据收集与填充

入口:UIPanel.FillDrawCall(UIDrawCall dc)

  1. 清空临时顶点缓存(verts、uvs、cols等)
  2. 遍历所有属于该DrawCall的Widget
    • 调用w.WriteToBuffers()将自己的顶点、UV、颜色等数据写入缓存
  3. 调用dc.Set()将数据传递给UIDrawCall

关键源码片段

public void FillDrawCall(UIDrawCall dc)
{
    mVerts.Clear();
    mUVs.Clear();
    mCols.Clear();

    for (int i = 0; i < mWidgets.size; ++i)
    {
        UIWidget w = mWidgets.buffer[i];
        if (w.isVisible && w.drawCall == dc)
        {
            w.WriteToBuffers(mVerts, mUVs, mCols);
        }
    }

    dc.Set(mVerts, mUVs, mCols);
}

2.4 Mesh生成与提交

入口:UIDrawCall.Set()

  1. 将收集到的顶点、UV、颜色等数据写入Mesh
  2. 设置Mesh的三角面(indices)
  3. 将Mesh赋值给MeshFilter/MeshRenderer,准备渲染

关键源码片段(UIDrawCall.cs)

public void Set(List<Vector3> verts, List<Vector2> uvs, List<Color32> cols)
{
    mesh.Clear();
    mesh.SetVertices(verts);
    mesh.SetUVs(0, uvs);
    mesh.SetColors(cols);
    // 计算并设置三角面索引
    mesh.SetTriangles(indices, 0);
    mesh.RecalculateBounds();
}

2.5 渲染

  • Unity的渲染管线会自动根据MeshRenderer和材质渲染UIDrawCall生成的Mesh。
  • 每个UIDrawCall对应一次Unity DrawCall。

2.6 回收与复用

  • UIPanel会维护一个DrawCall池,每帧只保留需要的DrawCall,多余的会被隐藏并缓存,下次复用。
  • 避免频繁创建/销毁DrawCall对象,减少GC。

关键源码片段

// 多余的DrawCall
if (mDrawCalls.Count > requiredCount)
{
    for (int i = requiredCount; i < mDrawCalls.Count; ++i)
    {
        mDrawCalls[i].gameObject.SetActive(false);
        mInactiveDrawCalls.Add(mDrawCalls[i]);
    }
    mDrawCalls.RemoveRange(requiredCount, mDrawCalls.Count - requiredCount);
}

3. UIDrawCall的生命周期流程图

UIWidget属性变更
    ↓
UIPanel被标记为脏
    ↓
UIPanel.LateUpdate()
    ↓
UpdateDrawCalls()
    ↓
分组分配UIDrawCall
    ↓
FillDrawCall(收集顶点数据)
    ↓
UIDrawCall.Set(生成Mesh)
    ↓
Unity渲染
    ↓
DrawCall回收/复用

4. 关键点总结

  • UIDrawCall的创建/分配:由UIPanel统一管理,按材质/贴图/Panel/Depth分组。
  • 数据填充:所有Widget的顶点数据合并到DrawCall的Mesh中。
  • Mesh生成:UIDrawCall负责Mesh的创建和更新。
  • 渲染:每个UIDrawCall就是一次Unity DrawCall。
  • 回收复用:DrawCall对象池优化性能。

5. 典型问题与优化

  • DrawCall数量过多:通常是分组条件不满足(见前一问“合批失败的典型原因分析”)。
  • Mesh数据异常:Widget未正确写入顶点数据或Widget未被分配到DrawCall。
  • DrawCall频繁创建/销毁:应检查是否有UI频繁增删,优化为复用。

6. 相关源码文件

  • UIDrawCall.cs:DrawCall对象本体,Mesh生成与渲染
  • UIPanel.cs:DrawCall的分组、分配、回收、数据填充
  • UIWidget.cs:Widget顶点数据的生成与写入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值