Unity中的DrawCall"/>
Unity中的DrawCall
一:绘制原理
为了将物体绘制到屏幕上,引擎必须向图像API(例如OpenGL、Direct3D)发送一个DrawCall指令,每一次发送DrawCall指令的过程为一个渲染批次(Batch),而这个过程分为两大部分:设置渲染状态(setPass)和调用DrawCall(Batches),其中设置渲染状态属于比较重的分工,对于加载到游戏中的资源和对象等,CPU需要计算其顶点相关的矩阵,渲染所用的贴图,渲染所用到的材质和shader,渲染所用到的灯光等,如果每个物体的材质和贴图等都不一样,此时CPU的主要工作就是设置这些物体的渲染状态后调用DrawCall,从而造成CPU性能的开销,简单来说也就是CPU向GPU发送指令并由GPU进行绘制
二:DrawCall过多导致的结果
——程序卡顿
——耗电量快
——设备发热
三:什么是批处理?
因为CPU每一次发送DrawCall指令都会造成CPU的性能开销(DrawCall次数越多,CPU进行计算的量越大),而CPU的处理速度比GPU慢多了,所以可以将绘制的压力移交给GPU
Unity可以将一些物体进行合并,从而用一次DrawCall来渲染他们。这一操作,称为批处理
例如渲染一千个三角形,如果把它们按一千个单独的网格进行渲染则需要请求1000次DrawCall,而如果直接渲染一个包含了一千个三角形的网格则只需要请求1次DrawCall,这样减轻了CPU的计算开销在性能上会有很大的提升
四:渲染统计窗口
默认一个空3D工程,会有两个Batches,一个是天空盒,一个是相机
——Batches:相当于DrawCall次数
——Saved by batching:通过批处理节省的DrawCall次数
——SetPass calls:设置渲染状态的次数
只有在相机视野内的会被统计
在Unity中可以通过UnityStats类去查看DrawCall等数据,一般在移动端DrawCall尽量保持在200以下
五:3D场景优化
——静态批处理
静态批处理默认是开启的,Project Setting—Player—Static Batching
将需要静态批处理的物体设置为Static,游戏运行时会自动为它们合批,将所有静态物体的Mesh Filter自动合并成一个新的Mesh
可以使用工具类动态设置合并的对象:StaticBatchingUtility.Combine,这样就不需要勾选Static
注意:
——需要有相同材质,如果不同只会合并mesh,batches还是会增加
——静态合批的最大顶点数是65535,如果顶点数超过了它,Unity就会自动合并出多个Mesh
——运行游戏后合并过的Mesh对象是不可以发生位移的,但是可以移动父物体节点
——打包时会把合并的mesh存储起来,所以需要额外的内存去存储,并且运行时需要加载这个合并后的大Mesh,所以使用时需要注意,例如一个很大场景,使用静态批处理后包体中就会多一份合并的Mesh,运行时也会加载整个大场景的Mesh,但是游戏运行后只有一小部分出现在摄像机内,那么整个大的Mesh都需要参与渲染
——动态批处理
动态批处理默认是关闭的,需要手动开启:Project Setting—Player—Dynamic Batching
条件限制:
——必须使用相同材质(相同材质的物体之间的差别只在于顶点数据不同,可以将顶点数据合并在一起,再一起发送给GPU)
——Mesh不能超过900个顶点(带uv的模型不能超过300个顶点)
——transform的scale属性不能有负值
——多个pass的shader会破坏批处理
——GPU Instancing
GPU Instancing是指使用相同的材质和网格来渲染多个物体,但每个物体的属性(位置、颜色等)不同。这种方式适用于需要大量重复的物体,比如草地、树木等
勾选Material中的属性Enable GPU instacing,只有在支持GPU Instancing的shader才能选择
条件限制:
——必须使用相同网格和相同材质
——SRP Batcher
只在UPR或SRP项目中有效,带有如MeshRender、TrailRender、LineRender、ParticleSystem、SpriteRender组件的mesh支持合批。带有SkinMeshRender和 布料模拟的mesh是不能合批
——减少实时光照和阴影效果
实时阴影会导致drawcalls大幅上升,建议关闭实时阴影,使用lightmap满足你想要的阴影效果
——代码动态合并网格
静态批处理会增加额外的内存消耗去存储合并后的Mesh,但原先没有用的Mesh仍保留在内存中,所以可以通过代码动态合并网格后将原先的网格销毁或隐藏
using UnityEngine;/// <summary>
/// 合并网格(挂载到要合并网格所有物体的父物体身上)
/// </summary>
public class CombineMesh : MonoBehaviour
{private void Start(){Combine();}/// <summary>/// 合并网格/// </summary>public void Combine(){MeshFilter[] filter = GetComponentsInChildren<MeshFilter>();MeshRenderer[] renderer = GetComponentsInChildren<MeshRenderer>();Material[] mat = new Material[renderer.Length];CombineInstance[] combine = new CombineInstance[renderer.Length];for (int i = 0; i < renderer.Length; i++){mat[i] = renderer[i].sharedMaterial;//获得共享材质combine[i].mesh = filter[i].sharedMesh;combine[i].transform = filter[i].transform.localToWorldMatrix;filter[i].gameObject.SetActive(false);}GameObject mesh = new GameObject("CombinedMesh");mesh.AddComponent<MeshFilter>().mesh.CombineMeshes(combine);mesh.AddComponent<MeshRenderer>().sharedMaterial = mat[0];}
}
六:UI优化(包含2D)
——2D的UI物体尽可能的安排同一个图集的物体在一起,如一个完整界面使用到的图片尽量打成一个图集,多个界面公用的图片打成一个图集
——UI元素打断合批必须是有重叠,所以注意不能合批的对象尽量不要重叠
——渲染顺序会打断合批,例如三个SpriteRenderer分别为A、B、C,A和B用同一个材质,C用单独的材质,正常来说,DC应该为2,因为A和B会合批,但是如果A的Order in Layer为1,B的Order in Layer为3,C的Order in Layer为2,那么渲染顺序就变成了A、C、B,C打断了合批导致DC仍为3
SpriteRenderer渲染顺序:Camera的depth>Z轴深度>Sorting Layer>Order in Layer
UI渲染顺序:
UI渲染顺序:Camera的Depth>Sorting Layer>Order in Layer>Plane Distance(Z轴深度)>层级顺序
七:其他打断批处理的因素
——调用Render.material也会创建一个新的material打断合批,应该使用render.shareMaterial保证材质共享
更多推荐
Unity中的DrawCall
发布评论