43.DrawCall优化

43.FGUI基础-Unity中的使用必备-DrawCall优化


43.1 知识点

DrawCall是什么

在 NGUI 中已经详细讲解了 DrawCall 的概念。

简单回顾 DrawCall:

  • DC 就是 CPU 通知 GPU 进行一次渲染的命令。
  • 如果 DC 次数较多会导致游戏卡顿。
  • 我们可以通过打图集,将小图合并成大图,将本应 n 次的 DC 变成 1 次 DC 来提高性能。

FairyGUI 和 UGUI 以及 NGUI 的 DrawCall 优化异同

相同点:

  • 三种 UI 都是通过打图集来优化 DC。

不同点:

  • UGUI 和 NGUI 的 UI 元素层级会影响 DC 的数量,我们拼 UI 时,要注意不同图集的 UI 元素的层级关系。
  • FairyGUI 中不用太过在意 UI 元素的层级,它会帮助我们进行深度调整。
  • FairyGUI 采用了 Unity 的动态批处理技术,提供了深度调整技术进行 DC 优化。

动态批处理:CPU 每帧把可以进行动态批处理的网格进行合并,再把合并后的数据传给 GPU,使用同一个材质对其渲染,达到降低 DC 的目的。

FairyGUI 中优化 DrawCall 的关键步骤

TeachPanel panel = UIManager.Instance.ShowPanel<TeachPanel>("Teach");

// 1. 打开 Unity 中的动态批处理 File——>Build Setting——>PlayerSetting——>Other Setting——>Dynamic Batching

// 2. 将组件的 fairyBatching 属性设置为 true
panel.fairyBatching = true;

// 注意:
// 某个组件设置了 fairyBatching,那么无需在子组件和孙子组件再启用 fairyBatching
// 一般只在顶层组件打开这个功能(面板组件)
// 永远不要在 GRoot 上开启 fairyBatching


手动触发深度调整

对于打开了 fairyBatching 的组件,当自己调用 SetPosition 等 API 改变子元件或者孙子元件的位置、大小、旋转或缩放,并不会自动触发深度调整,可能导致 DrawCall 的增加。

例如一个图片原来显示在一个窗口里的顶层,你将它从原来的位置移到另外一个位置,这个图片就有可能被窗口里的其他元素遮挡。

开发者需要手动触发深度调整,使用 GObject 中的 InvalidateBatchingState 方法重新调整 DrawCall。

panel.InvalidateBatchingState();

注意:这个 API 并不需要由开启了 fairyBatching 的组件调用,可以是任何一个内含的元件。


43.2 知识点代码

using System.Collections;
using System.Collections.Generic;
using Teach;
using UnityEngine;

public class Lesson43_FGUI基础_Unity中的使用必备_DrawCall优化 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 DrawCall是什么?
        //具体DrawCall是什么在NGUI课程中已经详细讲解
        //该节课是免费课 即使没有购买 也可以前往观看

        //简单回顾DrawCall
        //DC就是CPU通知GPU进行一次渲染的命令
        //如果DC次数较多会导致游戏卡顿
        //我们可以通过打图集,将小图合并成大图,将本应n次的DC变成1次DC来提高性能
        #endregion

        #region 知识点二 FairyGUI 和 UGUI以及NGUI的 DrawCall优化异同

        //相同点:
        //3种UI都是通过打图集来优化DC

        //不同点:
        //UGUI和NGUI的UI元素层级会影响DC的数量,我们拼UI时,要注意不同图集的UI元素的层级关系

        //FairyGUI中不用太过在意UI元素的层级,它会帮助我们进行深度调整
        //FairyGUI采用了Unity的动态批处理技术,提供了深度调整技术进行DC优化

        //动态批处理:cpu每帧把可以进行动态批处理的网格进行合并,再把合并后的数据传给gpu,使用同一个材质对其渲染,达到降低DC的目的

        #endregion

        #region 知识点三 FairyGUI中优化DrawCall的关键步骤

        TeachPanel panel = UIManager.Instance.ShowPanel<TeachPanel>("Teach");

        //1.打开Unity中的动态批处理 File——>Build Setting——>PlayerSetting——>Other Setting——>Dynamic Batching

        //2.将组件的fairyBatching属性设置为true
        panel.fairyBatching = true;

        //注意:某个组件设置了fairyBatching,那么无需在子组件和孙子组件再启用fairyBatching
        //      一般只在顶层组件打开这个功能(面板组件)
        //      永远不要在GRoot上开启fairyBatching

        #endregion

        #region 知识点四 手动触发深度调整

        //对于打开了fairyBatching的组件
        //当自己调用SetPosition等API改变子元件或者孙子元件的位置、大小,旋转或缩放
        //并不会自动触发深度调整
        //可能导致DrawCall的增加

        //例如一个图片原来显示在一个窗口里的顶层
        //你将它从原来的位置移到另外一个位置
        //这个图片就有可能被窗口里的其他元素遮挡

        //这时开发者需要手动触发深度调整 GObject中的InvalidateBatchingState方法 重新调整DrawCall
        panel.InvalidateBatchingState();

        //注意:这个API并不需要由开启了fairyBatching的组件调用
        //可以是任何一个内含的元件

        #endregion
    }
}

43.3 练习题

在上节课的练习题基础上,优化练习题面板的DC

项目设置中开启动态批处理

在UI管理器,显示面板时设置开启动态批处理

//进行批处理 DC优化 开关开启
panel.fairyBatching = true;

43.4 练习题代码

using FairyGUI;
using System;
using System.Collections;
using System.Collections.Generic;
using Teach;
using UnityEngine;

public class UIManager
{
    //在上节课的练习题基础上
    //优化练习题面板的DC

    private static UIManager instance = new UIManager();
    public static UIManager Instance => instance;
    //用于存储已经显示的 UI面板
    private Dictionary<string, GComponent> panelDic = new Dictionary<string, GComponent>();
    
    private UIManager()
    {
        //默认字体
        UIConfig.defaultFont = "Other/STHUPO";
        //默认音效
        UIPackage.AddPackage("UI/Public");
        UIConfig.buttonSound = (NAudioClip)UIPackage.GetItemAssetByURL("ui://Public/btnMusic");

        //适配相关的设置
        GRoot.inst.SetContentScaleFactor(1365, 768, UIContentScaler.ScreenMatchMode.MatchHeight);

        //注册相关的代码
        UIObjectFactory.SetPackageItemExtension("ui://Teach/ExercisesPanel", typeof(ExercisesPanel));
        UIObjectFactory.SetPackageItemExtension("ui://Teach/ExercisesBagPanel", typeof(ExercisesBagPanel));
        UIObjectFactory.SetPackageItemExtension("ui://Teach/TeachPanel", typeof(TeachPanel));
        UIObjectFactory.SetPackageItemExtension("ui://Teach/ControllerTest", typeof(ControllerTest));
    }

    //组件名和面板类名 是一致的 
    public T ShowPanel<T>(string packageName) where T:GComponent
    {
        Type panelType = typeof(T);
        string panelName = panelType.Name;
        //如果字典中有该面板的名字 证明已经创建过了 直接返回即可
        if (panelDic.ContainsKey(panelName))
        {
            panelDic[panelName].visible = true;
            return panelDic[panelName] as T;
        }

        //加载包和依赖包 
        //由于从Resources文件夹中加载包 会帮助我们判断重复没有 所以 这里既是重复执行也没什么问题
        UIPackage package = UIPackage.AddPackage("UI/" + packageName);
        foreach (var item in package.dependencies)
        {
            UIPackage.AddPackage("UI/" + item["name"]);
        }

        //创建组件面板
        GComponent panel = UIPackage.CreateObject(packageName, panelName).asCom;
        //把组件的尺寸设置的和逻辑分辨率一致
        panel.MakeFullScreen();
        GRoot.inst.AddChild(panel);
        //和父对象建立 宽高关联 这样 分辨率变化时 面板也不会出问题
        panel.AddRelation(GRoot.inst, RelationType.Size);

        //进行批处理 DC优化 开关开启
        panel.fairyBatching = true;


        //把当前显示的面板存起来 用于之后的隐藏
        panelDic.Add(panelName, panel);

        //把父类转换成对应的 子类
        return panel as T;
    }

    public void HidePanel<T>(bool isDispose = false) where T:GComponent
    {
        Type panelType = typeof(T);
        string panelName = panelType.Name;
        //如果没有面板显示着  就直接返回
        if (!panelDic.ContainsKey(panelName))
            return;
        //希望移除面板
        if( isDispose )
        {
            //移除面板 并且从字典中移除
            panelDic[panelName].Dispose();
            panelDic.Remove(panelName);
        }
        //希望只是失活
        else
        {
            panelDic[panelName].visible = false;
        }
    }

    public T GetPanel<T>() where T:GComponent
    {
        Type panelType = typeof(T);
        string panelName = panelType.Name;
        //如果有这个面板 直接返回
        if (panelDic.ContainsKey(panelName))
            return panelDic[panelName] as T;

        return null;
    }

    //主要用于销毁所有面板 和 资源垃圾回收的方法
    public void ClearPanel(bool isGC = false)
    {
        //销毁所有面板 并且清空字典
        foreach (var item in panelDic.Values)
        {
            item.Dispose();
        }
        panelDic.Clear();

        if(isGC)
        {
            //释放所有包资源
            UIPackage.RemoveAllPackages();
            //垃圾回收
            GC.Collect();
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏