笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN视频网址:http://edu.csdn.net/lecturer/144

最近给读者分享一下关于Unity3D的优化,这个问题对于开发者来说都是比较头疼的问题,这里先介绍一下关于项目开发通常的做法。开发项目前期由于赶进度,不停的堆积功能和资源,这样项目完成后,包体非常庞大,代码写的也很乱,后期项目进入优化阶段,包括代码的重构,各种BUG的修复,从而导致版本开始变的不可控制,项目研发周期也是不停的延期延期,这种情况在大部分公司都是常见的问题。其实作为老板来说,他肯定要着急看到项目成果,所以就不停的督促开发人员加班加点的搞,站在老板的角度考虑问题,情有可原。

但是,作为开发者来说,我们不能按照老板的节奏走,这样后面不仅坑的是自己,也把公司坑掉了,后期项目由于各种问题很容易夭折,这种局面是不可控制的,因为项目开发完成了,后期还会加入很多功能,如果前期没有规划好,就会出现前一发而动全身的情况,这样也预示着项目即将死掉。如何避免这种情况发生,在这些系列文章中给读者分享一下笔者关于这些问题的解决方案。

用Unity引擎开发,我们就要了解它们的工作机制,这样在项目进行优化时也会有针对性,关于优化方面,笔者也做过一些视频讲座,当然这些系列文章是视频中没有的,Unity首先遇到的问题就是效率问题,很多项目由于效率问题夭折了,所以这个问题的解决非常重要,如何优化效率也就是运行帧率,针对于Unity就是减少Draw Call的数量。

网上关于Draw Call的介绍非常多,这里再给读者向细的方向讲一下,其实Draw Call的执行就是CPU与GPU之间的通信,这就涉及到渲染流水线的概念,渲染流水线的起点是CPU,主要分三个阶段:

1、CPU把数据加载到显存中

2、设置渲染状态

3、调用Draw Call

根据上面提到的三个步骤,再详细介绍一下,所有游戏中渲染所需要的数据都需要从硬盘中加载到内存中,然后网格和纹理等数据被加载到显卡上的存储空间也就是我们所说的显存。这是因为显卡对于显存的访问速度更快,效果如下:

实际项目开发中,真实渲染中需要加载到显存中的数据比图中显示的更复杂,举例说明一下:顶点的位置信息、法线方向、顶点颜色、纹理坐标等等。

当把数据加载到显存后,在内存中的数据部分可以移除,因为对于一些数据来说,CPU仍然要访问它们,从硬盘加载到内存的过程是十分耗时的,这个也要考虑的。在这之后,开发者还需要通过CPU来设置渲染状态,从而“指导”GPU如何进行渲染工作。

接下来介绍设置渲染状态了,渲染状态定义了场景中的网格是如何被渲染的,如果我们编程没有更改渲染状态,所有的网格都将使用同一种渲染状态。如下图所示效果演示:

以上图片只是表达这个意思,同样的渲染状态,材质是一样的。准备好上述工作后,CPU就需要调用一个渲染命令来告诉GPU:数据准备好了,可以按照我的设置开始渲染,这个渲染命令就是Draw Call,讲了这么多终于回来了。

实际上,Draw Call就是一个命令,它的发起方是CPU,接收方是GPU。当给定了一个Draw Call时,GPU就会根据渲染状态(比如材质,纹理,着色器等)和所有输入的顶点数据来进行计算,最终输出成屏幕上显示的那些像素,这个过程也会涉及到GPU流水线。如果有读者对此不清楚可以看看笔者以前的博客有详细的介绍。接下来我们继续解释Draw Call ,给开发者造成的误区认为 造成Draw Call问题的主要原因是GPU, 认为GPU上的状态切换是耗时的,其实不是的,真正的罪魁祸手是CPU。

下面我们就介绍CPU和GPU工作原理,大家先想一下,如果没有流水线,那么CPU需要等到GPU完成上一个渲染任务才能再次发送渲染命令。但这种方法显然会造成效率低下。我们需要让CPU和GPU可以并行工作,解决方法是使用一个命令缓冲区(Command Buffer)。命令缓冲区包含了一个命令队列,它是由CPU向其中添加命令,而由GPU从中读取命令,添加和读取的过程是相互独立的。命令缓冲区使得CPU和GPU可以相互独立工作。当CPU需要渲染一些对象时,他可以向命令缓冲区中添加命令,而当GPU完成了上一次的渲染任务后,它就可以从命令队列中再取出一个命令并执行它。

命令缓冲区中的命令有很多种,而Draw Call是其中一种,其它命令还有改变渲染状态等。效果如下图所示:

图中显示的是CPU与GPU通过缓冲区进行交互,Draw Call执行的是图中灰色的显示的,而白色的是改变渲染状态的,这个相对来说比较耗时间。

为什么Draw Call 多了会影响帧率?读者可以做一个实验,比如你复制1000个文本文件到另一个文件夹中,每个文件大小是100K,总计大小是10MB,这个要花费很长时间。我们再来创建一个单独的文件,它的大小是10M,然后也把它从一个文件夹复制到另一个文件夹。这次复制的时间少很多。主要原因在于,每一个复制动作需要很多额外的操作,比如分配内存等,如果复制1000个文件,他要开辟内存1000次,这个开销将会很大。

渲染过程跟这个类似,在每次调用Draw Call之前,CPU需要向GPU发送很多内容,包括数据、状态和命令等。在这一阶段,CPU要完成很多工作。一旦CPU完成了这些准备工作,GPU就可以开始本次的渲染。GPU渲染能力是很强的,渲染200个还是2000个三角网格没啥区别,因此渲染速度往往快于CPU提交命令的速度。如果需要执行的数据很多,也就是说Draw Call 数量很多,CPU就会把大量的时间花费在提交Draw Call上,造成CPU的过载,这个是需要我们避免的。

如何减少Draw Call的数量,这个在Unity开发中常见,减少Draw Call的方法很多,介绍一下批处理的方法。在前面提到过,复制文件的问题,CPU的时间都花费在准备Draw Call的工作上了。优化的想法就是把很多小的DrawCall合并成一个大的Draw Call,这就是批处理思想。

需要注意的是,合并网格是需要CPU在内存中执行的,合并的过程是需要消耗时间的。因此,批处理技术更加适合于哪些静态的物体,当然动态的如果合并的话,也需要在CPU中执行,我在以前的博客中也有介绍。要注意的是不要每一帧都去重新进行合并再发送给GPU,这对空间和时间都会造成一定的影响。

要做到减少Draw Call需要注意以下两点:

1、避免使用大量很小的网格,如果不可避免需要使用很小的网格结构,可以考虑合并,当然,模型的材质也可以考虑通用。

2、避免使用过多的材质,尽量在不同的网格之间共用同一个材质。

3、遇到有相同动作的物体,可以使用合并动态网格的方式进行优化,效果还不错。

在本文最后把动态网格合并的代码给读者展示如下:

public class CombineOpMesh : MonoBehaviour {
void Start()
{
CombineToMesh(this.gameObject);
}
public void CombineToMesh(GameObject _go)
{
SkinnedMeshRenderer[] smr = _go.GetComponentsInChildren<SkinnedMeshRenderer>();
List<CombineInstance> lcom = new List<CombineInstance>();
List<Material> lmat = new List<Material>();
List<Transform> ltra = new List<Transform>();
for (int i = 0; i < smr.Length; i++ )
{
lmat.AddRange(smr[i].materials);
ltra.AddRange(smr[i].bones);
for (int sub = 0; sub < smr[i].sharedMesh.subMeshCount; sub++ )
{
CombineInstance ci = new CombineInstance();
ci.mesh = smr[i].sharedMesh;
ci.subMeshIndex = sub;
lcom.Add(ci);
}
Destroy(smr[i].gameObject);
}
SkinnedMeshRenderer _r = _go.GetComponent<SkinnedMeshRenderer>();
if (_r == null)
_r = _go.AddComponent<SkinnedMeshRenderer>();
_r.sharedMesh = new Mesh();
_r.bones = ltra.ToArray();
_r.materials = new Material[] { lmat[0] };
_r.rootBone = _go.transform;
_r.sharedMesh.CombineMeshes(lcom.ToArray(), true, false);
}
}

实现原理是: 首先去遍历每个对象的SkinnderMeshRenderer,然后将其所有的动态对象组合成一个大的对象并且将骨骼动画赋值给他,这样,我们就实现了动态对象的优化。

实现效果图对比如下,首先展示的是没有合并的动态动画的Draw Call数量:

然后再挂上文提到的合并脚本后的效果如下所示:

具体操作方式如下所示:

Unity3D优化技巧系列一相关推荐

  1. Unity3D优化技巧系列三

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  2. Unity3D优化技巧系列二

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  3. Unity3D优化技巧系列七

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  4. Unity3D优化技巧系列八

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  5. 系列笔记 | 深度学习连载(5):优化技巧(下)

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 深度学习中我们总结出 5 大技巧: 本节继续从第三个开始讲起. 3. Early stoppi ...

  6. 深度学习训练的时候gpu占用0_26秒单GPU训练CIFAR10,Jeff Dean也点赞的深度学习优化技巧...

    选自myrtle.ai 机器之心编译机器之心编辑部 26 秒内用 ResNet 训练 CIFAR10?一块 GPU 也能这么干.近日,myrtle.ai 科学家 David Page 提出了一大堆针对 ...

  7. php7.0 java 性能,php7代码性能常见优化技巧

    目录概述 php7代码性能常见优化技巧 参考文档 概述 这是关于php进阶到架构之php7性能优化学习的第一篇文章:php代码性能常见优化技巧.第一篇:php代码性能常见优化技巧 php7代码性能常见 ...

  8. oracle 测试sql执行时间_从 TPCH 测试学习性能优化技巧

    一. 目标 TPCH是由TPC(Transaction Processing Performance Council)事务处理性能委员会公布的一套针对数据库决策支持能力的测试基准,通过模拟数据库中与业 ...

  9. 白鹭引擎王泽:重度H5游戏性能优化技巧

    9月15日,无惧17级台风"山竹",320名开发者齐聚广州贝塔空间共同探讨"怎样做一款赚钱的小游戏".针对众多开发者关心的重度H5游戏性能优化技巧,我们整理了现 ...

最新文章

  1. 网页按钮跳转位置_RPA工具BizRobo!之运用网页数据处理
  2. 表达式中常用到的运算符
  3. 为什么回归问题用MSE?
  4. 有关 VS winform 开发问题
  5. 暑假学习打卡【2】——北理工乐学第一周第二周作业
  6. immunedeconv估算免疫细胞比例
  7. java 摄像头_javacv调用摄像头拍照
  8. 硬盘的老化测试软件,硬盘检测工具使用方法
  9. Python学习总结报告
  10. 浅谈网游服务器的承载
  11. transform 实现 附加鼠标悬浮效果,照片旋转,六面体,3D效果
  12. Camera2 YUV_420_888转NV21
  13. Windows 10 D盘操作需要管理员权限
  14. iOS 的 (签名验签)Code Signing 体系
  15. 机械革命极光Pro 评测
  16. 国际上进行盲源分离研究的主要学者及其研究方向
  17. 判断字符串是否是对称字符串
  18. 麦克风阵列声源定位实现
  19. 计算机复制教程,教你如何使用电脑复制粘贴快捷键
  20. 【小知识】linux下ls与ll的区别

热门文章

  1. python 地图偏移_python 地图经纬度转换、纠偏
  2. ios app安装的四种方式
  3. 使用NOWSMS搭建自己的彩信中心- -
  4. win10平板模式_win10隐藏的9种功能 效率提升10倍
  5. 合肥工业大学宣城校区Java技术实验二 基于GUI的网络通信程序设计
  6. 云盘构建LVM linux 持续更新
  7. 安装rpm 树莓派4_树莓派|在树莓派中开启激动人心的 Perl 之旅
  8. 文科生与理科生_戏谈
  9. Python自动化测试笔记
  10. 天刀论剑显示服务器,天涯明月刀手游论剑机制大改革 论剑pc与移动端分开匹配...