前言
关于unity动态合批,一直被人诟病的是他的使用条件太过苛刻,上限900左右个顶点数,只能用来合批一些小物件。
毕竟在Update上面每帧计算900个顶点数据,对Cpu是一个不小的消耗,所以我们不妨设想一些,如果我们把顶点的运算放到GPU上面,我们是不是就可以无视顶点上限的问题了。
其实我最近一直在做关于性能优化的事情,关于合批上面的事情也做了很多,发现其实很多情况下,静态合批的情况下,也需要做一些运动等,但是unity自带的动态合批可用性实在是小的可怜,在各方面的问题下,我们依然还是决定自己做一个条件更少,适用性更广泛的合批。来优化我们项目的一些功能。
效果
话不多说,先看一下测试场景对比效果。
Shader合批前后的 Batches和对比
支持运动,运动前后的帧率对比
对比
相比于unity,他是把定点的运算都放到了cpu的update里面,每帧去更新顶点的位置,因为对cpu的消耗十分巨大,所以顶点的最大数量始终在900左右。但是cpu对于大量的计算能力,始终不如GPU,所以我们要做的就是把顶点的计算都放到GPU里面,这样对于CPU的消耗几乎为零。所以基于Gpu的,顶点的数量可以说是 没有限制的,但是也不能大于uint32最大值(也就是10亿左右)。
下面就是一些基于Shader的动态合批的一些基本原理。
mesh合批实现原理
对于合批的具体实现其实并不难,无疑是吧mesh的顶点,三角形数据,顶点光线等数据统一合并到一个大mesh中,这些可以自行百度了解。
但是实际项目中我们可能同时合并很多个mesh,这是大量的mesh顶点数据需要在主线程中,会出现严重的掉帧现象,所以我们需要把顶点,三角形数据,顶点光线的计算放到子线程中,这里肯定会有很多小伙伴会问,不是unity的资源不能在子线程中访问嘛。
所以我们需要分两步进行操作,首先在主线程把顶点,三角形数据,顶点光线等数据都收集数组中,第二步,然后把这些数组统一在子线程里面计算,如下图
此图是在子线程中进行数据运算的过程。到这里,算是完成了合批的这一部分,这时候会有小伙伴问道,这个shader好像并没有什么关系,只是把mesh的运算放到了子线程了而已, 哼哼,下面关于顶点的运动就必须放到shader里面去了。
基于shader 的顶点运算
unity动态合批之所以顶点数量要求在900之内的原因,是因为把顶点的运算全都放到Cpu里面了。如果我们把顶点的运算放到Gpu 里面,那个对顶点数量的的要求将会彻底解除。
呢么我们改如何去吧顶点的计算放入GPU呢,下面是两个猜想。
设想1. 我们把通过ComputeShader,每帧将mesh的顶点,法线,传入GPU中,在GPU中计算过之后,在通过ComputeShader传过来。之后把数据重新赋给mesh。这样就可以实现他的运动了。缺点是每帧需要占用大量的宽带。并且需要三次数据的传递。
设想2. 我们不改变mesh,只改变图形在屏幕中的显示。我们在合批mesh的时候,向顶点传入对应物体的序号,合批完成之后,我们只需要每一帧把改变物体的坐标矩阵传给shader中,呢么就可以实现物体的显示变换。每帧只需要传送每个子物体的矩阵即可,并且不需要CPU和GPU之间来回传递。
毫无疑问,无论是哪一方面,第二种都要比第一种要好一些。
既然是基于shader的,所以我们必须要专门为合批写一个 shader,CombShader里面的一些代码如下
这里需要借鉴 骨骼动画的原理(原理类似),记录顶点的对应的bound。在GPU中找到bound对应的矩阵进行矩阵变换,就可以得到mesh在世界空间的坐标。
将子物体对应的transfrom传给GPU
把子物体的mesh数据都合并到父对象上面,只留下子物体对应的transform组件来记录更新矩阵数据。这样一来就是对animation动画的完美契合。并不需要把animation数据重新生成一份矩阵数据到本地。只需要每帧把需要改变的子物体矩阵传到GPU即可。
通过上面两步就可以实现Shader的模拟运动。
最终效果
最终效果
可以看到,原本800多个物体,全都合在两个mesh中了,Batchs从870+减到了17左右,顶点数在40万左右的数量,因为是shader的图形在动,所以动画动起来也对帧率几乎是没有影响。
使用条件
1.作为合批,避免不了的是,合批的对象使用的材质必须是一样的。(贴图可以不一样)
2.顶点数不能超过uint32最大值。
3.贴图的读写权限要开启。
结束
这个功能还是输入刚开始阶段,还存在一些问题,就比如在Update每帧要遍历子节点获取物体位置矩阵,增加了GC的频率。所以运动的时候偶尔会出现掉帧的现象。有兴趣的小伙伴可以研究一下怎么样更加高效的数据传递。
功能有不足之处或者有问题可以加我哦:2364823151
网友评论