xiaozongpeng 发表于 2021-12-29 17:51

Unity 2D 项目的优化(一)Draw Call

最近开发一款中型的2D 手游项目,遇到了一个大坑,即性能优化问题,PM 今天找我了解近况,谈话时他说想知道关于我如何优化这个大坑的理论知识,正好那我就顺手写一些内容。
希望我写出来的东西能够为入行不久的朋友们提供借鉴和帮助。
人菜,所以有些地方可能说的不对,请多包涵。
先说问题,目前我优化的部分分为两个:一个是内存;一个是DrawCall。本章主要着重于 Draw call 方面的优化进行讲解,同时会附带一些关于 Unity 渲染相关的知识。
Draw call 是什么?

    通常渲染引擎在渲染物体时,会用到 Texture(贴图),当然还有其他数据。而在2D 项目中存在着无数的 Texture。无论引擎是通过哪种渲染 API 实现绘制,都会有一个 Draw 的过程,这个过程要求 CPU 将需要的 Texture 数据传输到 GPU。CPU 和 GPU 分属于两个不同的硬件模块的嘛,所以导致传输数据的过程成是性能损耗的一个十分严重的部分。这也是老人们常说的优化 Draw Call 的根本原因。
如何优化?

合并图集是我们优化 Draw Call 的一个最佳手段。因为通常引擎会带有一个 Dynamic Merge (动态合批)的过程,即不同顶点数据物体如果含有相同的 Material (Shader,Texture 等 Parameters 相同)那么引擎会自动合并 Vertices 数据,然后统一调用一次 Draw API 渲染图像。这样本来需要多次 DrawCall 的过程就被压缩了,理所当然的渲染性能就上去了。
顺便一提,实际上在 Unity 中,查看渲染性能的话有两个指标:Draw Call (SetBatch) 和 Set Pass Call
SetBatch 是基本指标是渲染批次。Set Pass Call 我理解的是 Shader 中 Pass 调用的过程,即次数越多,处理数据传输数据的次数就越多。 Set Pass Call 优化指标优先级是重于 SetBatch 的。Pass Call 是执行多次 shader 中写好的渲染方案,包括顶点和片元着色器,直接点,建议优先优化 Set Pass Call。
实战问题:

项目中需要地编同学对地图场景进行设计,等到中后期东拼拼西凑凑的阶段已经结束,开始优化阶段时,项目就出现了场景物体过多,过大,无法将所有资源压缩到一张图集的问题。
Unity 的动态合批是需要连续渲染的对象拥有相同的 Material 才能够执行的。不同图集的对象自然无法称之为相同 Material。因此 Draw call 直线飙升,达到了 1000 以上,GG。
解决过程:


[*]地编同学彻夜修改资源分布和引用,因为引擎只会渲染出现在 Camera 中的物体,那么只要保证在 Camera 最远距离下同屏对象的种类和数量控制在一定范围内,是可以起到优化的作用的。但是!这治标不治本, 有用但没有完全有用,Draw Call 确实下降了,但是,不同图集对象的相互打断批次问题依旧存在。优化并没有达到可以接受的范围。苦了我们的地编同学。
[*]程序这边是我负责,我来详细唠唠。
一开始的我想利用调整不同图集的渲染队列顺序来处理这个问题,因为相同 Material 只会在一个 Queue 中进行所以就可以直接合批啦。但是出现了渲染问题。如下图:


可以看到渲染面片因为不同的渲染顺序出现了透明像素填充错位的问题。究其原因是 Mesh 填充 Pixel 的时候会从 Render Buffer 里面拿数据,填充上了之后就不会再回过头渲染之前已经渲染过的对象,不然就是重复渲染了嘛,所以就出现了显示错误。
之后我打算辅助使用 Depth Test 和 Alpha Test 发现填充过的像素已经是有 Alpha 值的了,AlphaTest 失败。
而 Depth Test ,因为 Depth Buffer 是根据 Vertex 持有的 ZBuffer 通过插值计算的,而 2D 面片的所有顶点深度在 Depth Buffer (ZBuffer)中是一致深度,Unity 的 Depth Buffer 是根据 Camera 生成的 Depth Texture 获取数据的,因此无法直接在 Shader 中进行修改。 yue 了。(实际上还想过用 Alpha Test 配合重写 Depth 值,但是因为无法修改 Depth Buffer 所以放弃了)
Ok,那么现在我们只能放弃使用修改渲染顺序的方案了。
下面是我这次使用的方案,Texture2D 的 API PackTexture。通过这个方法,我们可以生成一个 2048 * 2048 格式自拟的 Texture2D 对象,将所有场上 2D 渲染对象的 Sprite Texture 加入到图集中,PackTexture 会自动为我们进行等比的压缩。一开始打算在 Runtime 时进行动态合并,但是考虑到 CPU 压力,改为了 Static 方案。直接合并一个新的压缩图集。
最终效果是成功的将一个地图中引用到的大部分小物体都整合了起来,Draw Call 也从峰值的 300 降到了 70-80(大部分留给特效了),Set Pass Call 现在是 50 左右。
既不干扰美术的修改,也不会影响到现有的运行逻辑。这里暂时就不附上图片了, 手上这台电脑没有项目。
PS:


[*]特别大的物体,或者只会存在一个的可替换物是不加入图集的,因为考虑到物体压缩的像素损失,和最终优化的及格标准,进行折中是最好的方案。具体操作在这里就不做赘述了,看经验和项目需求吧。
2. Tilemap 也是可以合并的,在 Tilemap 的 API 中 InitTilemap 可以替换 Sprite,同样可以替换到图集中的 Sprite 减少 Draw Call.
3. 项目期初就应该和地编同学商量好资源的使用范围和局限,无论如何优化,都不如前期规范好,避免后期手忙脚乱的。
Ok 写的已经蛮多的了,有什么问题请留言评论,我会及时回复的。
最后的最后,我想在这篇文章中纪念一位了不起的老师。浅墨。
这是个特殊的日子,同行的路上我们失去了一位行业的领路人,先行者,抱薪者。在我从业至今寥寥的岁月里,我看过很多浅墨老师的技术分享,甚至在他离开的前不久,我仍然在翻看他曾经贡献的大纲与书籍。他是无私的,高尚的,也是充满热情和激情的,是每一位游戏从业技术人员的榜样。他怀着遗憾离开,我们身处的行业中依旧没有他梦想中的宏伟的蓝图模样,今天我希望铭记他的恩情与贡献,我希望记住他的梦想,希望将来有一天,ZG 的游戏市场,能够不再是畸形的,贫瘠的。我要为游戏存在的意义,交出自己的答卷。
                                                                                                                                             ——2021.12.14

七彩极 发表于 2021-12-29 17:54

纯路人,高中毕业准备选数媒一同投身Unity游戏开发。看完这篇分享,自己对渲染优化的知识又多了一些。有你们这些前辈引路,实在有幸![抱抱]

TheLudGamer 发表于 2021-12-29 17:57

希望你可以在游戏开发中找到自己的乐趣所在,加油
页: [1]
查看完整版本: Unity 2D 项目的优化(一)Draw Call