操作方法:
在unity场景的右侧属性面板中直接勾选static属性,那么就可以在游戏引擎中完成静态批处理
原理:
满足条件:使用相同的材质球,模型是禁止的
标明为 Static 的静态物件,如果在使用相同材质球的条件下,在Build(项目打包)的时候Unity会自动地提取
这些共享材质的静态模型的Vertex buffer和Index buffer。根据其摆放在场景中的位置等最终状态信息,将这
些模型的顶点数据变换到世界空间下,存储在新构建的大Vertex buffer和Index buffer中。并且记录每一个子
模型的Index buffer数据在构建的大Index buffer中的起始及结束位置
盗一张图:从下面这张图中,我们可以清楚的看到,可以静态批处理的模型,他们的顶点都被存储到了一个更大的顶点buffer中,并且是一次性传给了GPU,同时也把各自的索引存储到了一个更大的buffer数组中,也是一次性传给了GPU,但是记录下了各自的起始位置和长度,
再看一张图:对于绘制每一个模型的时候,还是正常绘制,发送渲染状态,调用绘制命令,drallcall的数量并没有减少,但是却可以极大减少cpu和gpu的计算
原因:
1:从矩阵的角度说
静态批处理在进行顶点合并到统一的大buffer中之前,还对顶点做了一些变换,根据缩放旋转平移以及继承关系,算出世界模型矩阵,然后把顶点buffer中顶点乘以这个矩阵,然后再存入到那个统一的大顶点buffer中,就是把顶点变换到了世界坐标系下,也就是说,我们在shader中不需要对顶点进行PxVxMxpos操作了,只需要PxVxpos了,减少了一次矩阵的运算,那么在cpu端,由于不需要每一帧都去计算世界模型矩阵然后把世界模型矩阵传给GPU了,所以也省去了自身关于模型矩阵的计算维护,但是第一次还是需要的
2:从状态机的角度来说
GPU是一个状态机,不改变状态就是最好的,切换状态耗时,这个状态就包括很多了,可以进行批处理的,他们的状态几乎都是一致的,比如顶点数据(位置,uv,法线,索引),混合,texture等,对于顶点数据来说,可以通过起始位置和长度来取数据,所以他们的合并也可以达到状态的一致,静态批处理下的模型都是紧挨着一次一次绘制的,所以对于GPU来说,它的状态几乎没有改变,那么也就不会有什么性能损失,这就好像合并了drawcall了一样,
静态批处理的弊端
举一个常见的树的例子,如果森林中有1000棵树,这些树模型公用一个mesh,如果我们不开启静态批处理的话,那么在资源包中就只有一份mesh数据,在加载到内存中也只有一份,在每一帧渲染的时候,在内存发往GPU的时候,可能会发1000份,那么显存中就有1000份顶点数据,这很明显drawcall数量过多,性能大幅降低;如果我们给这1000棵树开启静态批处理的话,那么构建的时候,每一个树模型都会在资源包中持有一份mesh数据,在游戏运行时候,内存中一下子多了999份mesh数据,只是这些数据被全部转换到了世界坐标系下,然后再一次性的合并到一个更大的顶点buffer中,发往GPU中;
问题1:对于内存中一下子多了999份,这个是可以理解的,因为在shader那边不要顶点位置乘以模型世界矩阵了,所以必须要确切的知道顶点在世界坐标系下的位置,所以预先对每一个静态模型做了一份转换到世界坐标系下的数据存在本地,当然如果你的模型的数量是动态生成的,那就不会增大资源包
总结:
静态批处理不会降低drawcall,但会极大的减少状态切换,而GPU状态切换是特别耗时的,如果我们只是增加drawcall,但每一次绘制的时候,gpu的状态几乎不切换,那么性能将没啥影响,可静态批处理会将所有可以批处理的模型的网格数据全部合并到一张更大的顶点和索引buffer中,这将增大CPU端的内存,当这个buffer发往GPU以后,就贮藏在GPU中了,cpu这边可以删除,我们说的增大内存应该指的是第一次在cpu这边合并,然后发往GPU,第一次cpu端的内存确实会增大,可也只是一帧的时间,最好真正增大的是GPU
在每一次需要渲染那些模型的时候,会从这个更大的buffer中取起始点和长度,然后发送到GPU,他们的绘制顺序一定是紧挨着的!比如你有8个模型可以静态批处理,那这8个模型一定是紧挨着的,且在第一次加载的时候,存在总的buffer中的顶点数据是已经乘以世界坐标系的,只需要乘以一次,后面这些数据是需要乘以v*p就好了
动态批处理
每一帧将可以合批的网格进行合并,然后一次将顶点和索引buffer发送给GPU,这个降低了drawcall,但由于进行了网格合并操作,这个会增加cpu端的性能消耗,所以处理的顶点数目不易过大