Draw Call的含义
在Unity中,Draw Call(绘制调用)是指渲染引擎向图形硬件发送渲染命令以绘制一个或多个图形对象的过程(Cpu向GPU发起的一种在屏幕上绘制内容的请求。)。每个Draw Call都代表了一次绘制操作,渲染引擎将要求图形硬件绘制一个或多个三维物体或图形元素。理解和优化Draw Calls对于提高游戏性能和渲染效率非常重要。
2.为什么DrawCall多了会影响帧率
CPU每次调用DrawCall,都需要向GPU发送许多数据、状态,在发送前CPU需要从硬盘读取到显存的数据、设置的渲染状态以及调用DrawCall命令操作。一旦CPU执行完应用阶段,GPU即开始执行本次渲染。GPU渲染的速度比CPU提交命令的速度快的多。所以性能就会被CPU的提交速度所影响,如果DrawCall数量过多,CPU就会在提交命令上花费大量时间。
如何减少DrawCall
使用Draw Call Batching
使用Draw Call Batching也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。
Static Batching 静态批处理
只要物体不移动,并且拥有相同的材质,静态批处理就允许引擎对任意大小的几何物体进行批处理操作来降低描绘调用。明确指出哪些物体是静止的,并且在游戏中永远不会移动、旋转和缩放,只需要在检测器(Inspector)中将Static复选框打勾即可,如下图所示:
优点:降低DC,且只需要执行一次,可以是不同Mesh;
缺点:占用内存,不能部分剔除。
适合:静态大Mesh。
Dynamic Bathing 动态批处理
动态批处理需要在Project Setting→Playe →Other Setting 中将Dynamic Bathing勾选上,Unity会自动将使用相同材质的物体合并处理,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。
但动态批处理需要满足以下条件:
- 顶点属性最大限制900的可移动物体,
- 使用lightmap的物体不行进行批处理
- 使用多通道的shader也不会进行批处理
- 缩放比不同的物体不会批处理
优点:降低DC,可以是不同Mesh,可以部分剔除。
缺点:每帧都要执行,消耗CPU,且占用内存。如果该步骤消耗的CPU,大于降低DC所带来的的提升,则反而影响性能。限制较多(非负缩放、没有光照贴图或使用相同的光照贴图位置、material单pass、不能接受实时阴影)
适用:动态的大量小Mesh。
Enable GPU Instancing
在使用大量重复的物体时,需要将该物体的材质的 Enable GPU Instancing 勾选上,这样Unity会将Mesh相同的物体合并处理,常用于树木,植被,粒子等。使用GPU Instancing 对shader有要求,需要在shader中添加支持GPU Instancing 批处理的命令,但是如果shader中有多pass也会影响Enable GPU Instancing的合批操作。
#pragma multi_compile_instancing
通过把纹理打包成图集来尽量减少材质的使用
现在场景里面有3张图片,对应的Bathces是5,Bathces就是DC,Camera占了2个DC,3张图片占了3个DC。打图集有两种方法
方法一:2020.1之前(已废弃)
依次点击Editor>Project Settings>Editor将三张图片打成图集。
选中Project面板中的图片,定义Packing Tag,相同Packing Tag的图片就会打成一个图集,这是老版本Unity设置图集的方式。
方法二:
先下载一下,点击Windows->Package Manager下载一下
选择Edit -> Project Settings -> Editor,将SpritePacker的Mode切换为你所需要的模式。我这里选择了Sprite Atlas V1 - Always Enabled。
在Project视图点击鼠标右键,Create->2D->Sprite Atlas
将要打包的图片或文件夹,放置到图集文件的Objects for Packing中。然后单击PackPreview按钮,可以看到图集预览。
这个时候的Draw Call也变成3了
减少使用反光、阴影等
光照产生阴影同样会产生大量的DrawCall,类似模型的渲染,在不需要阴影的情况下关闭灯光的阴影效果,降低阴影质量。对需要阴影和不需要阴影的物体拆分shader,分别使用会产生阴影和不会产生的材质shader。
同样可以在物体的meshrender 组件中选择是否产生和接受阴影
mesh合并
减少Cpu需要提交的模型信息次数。
使用Gpu Instance一次绘制多个相同的物体
【GPU Instancing】:CPU只发送一次Mesh给GPU,GPU自己去复制实例化。为了让Mesh有不同的状态,甚至播放动画,CPU需要同时提供一份额外数据(比如变换矩阵)。
优点:解放CPU。
缺点:需要是相同的Mesh,对平台和API有要求(Windows要求DirectX11以上、OpenGL Core 4.1+/ES 3.0+)
适用:大量相同的Mesh,比如植被
优化shader 减少pass通道
一个pass通道会多产生一个绘制信息,并且对动态合批和gpu instance产生影响,尽可能的在一个pass通道中处理好模型的表现效果。
注意OverDraw
unity的绘制顺序是先绘制不透明物体,再绘制透明物体,如果有透明物体则会导致一些像素点被重新绘制增加调用次数并可能打断一部分合批操作,如果多个透明物体重叠则更加复杂。OverDraw就是上面所表达的意思。常常是游戏的性能问题所在。注意透明材质的使用,合理的设置渲染顺序,尤其是特效这部分常常是开发者所忽略的,特效包含大量的半透明效果。
UI层Draw Call的优化
首要知道Draw Call在UI层合并的原理,当用相同材质和渲染属性的元素连续渲染时,其DrawCall会进行合并。为达成合并条件,需要创建一个图集。Unity提供了Sprite Packer工具,可以将多个Sprite打包成Atlas,这可以有效减少Draw Call数量。
文字会单独占用一个Draw Call,但连续渲染的文字会自动合并为一个Draw Call,UGUI在不影响渲染出来的内容时,会自动将文字的渲染顺序排列到最后执行。(即无遮挡情况下,UGUI将会自动将文字和图片分开渲染,使得文字不打断图片与图片之间的Draw Call合并)
避免使用Mask组件,Unity的Mask组件可以导致额外的Draw Call(有2个Draw Call)。如果可能,尽量避免在UI元素上使用Mask,或者使用Mask时确保它们是必要的。
合理使用图集(Atlas) ,因为执行一个Draw Call时,需要载入纹理(即图片或图集),当图集内包含的图片过多且大部分不需要用到时,则会导致加载了不必要的内容产生多余的内存损耗。因此合理使用图集,可以提高项目的性能。(打图集有一定的规则)
还有就是UI的动静分离,Unity 会将使用相同材质和纹理的 UI 元素合并到一个 Draw Call 中。但如果 UI 元素频繁变化(如位置、颜色、大小),会导致合批失效,增加 Draw Call,通过将静态 UI 和动态 UI 分离,可以避免动态 UI 打断静态 UI 的合批,从而减少 Draw Call
场景层Draw Call的优化
与UI层合并的原理一样,当用相同材质和渲染属性的元素连续渲染时,其Dra
使用静态批处理(Static Batching),将那些不需要在运行时修改的物体标记为“静态”,以启用静态批处理。这会将多个静态物体合并为一个批次。这个选项适用于静态物体,如墙壁、地板等。
使用动态批处理,动态批处理需要在Project Setting→Playe →Other Setting 中将Dynamic Bathing勾选上,Unity会自动将使用相同材质的物体合并处理,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。
使用GPU Instancing,GPU Instancing是一种渲染技术,允许你绘制多个相同网格但使用不同的数据,而不会增加Draw Call数量。这对于绘制大量相似的物体非常有用,比如树木、草。你需要在Shader中启用GPU Instancing,并使用合适的材质。
使用Level of Detail(LOD), 对于远处的物体,使用不同级别的多边形模型(LOD)可以减少Draw Call数量。
使用遮挡剔除(Occlusion Culling),在摄像机中勾选遮挡剔除可以在运行时排除不可见的物体,减少Draw Call数量。参与遮挡剔除的物体需要设置为静态,并通过菜单栏中的 “Window” > “Rendering” > “Occlusion Culling” 生成遮挡剔除数据。
减少透明物体, 透明物体通常会导致额外的Draw Call,因为它们需要按深度进行排序。减少透明物体的数量可以降低Draw Call数量。
使用合并材质球(Material Balling), 这是一种手动操作,通过创建一个材质球,将多个材质的属性合并到一个材质球中,然后将这个材质球应用到多个物体上。这可以减少不同材质球之间的切换。通常来说,此操作由TA部门进行实现。
附:使用URP时,Unity会将Skinned Mesh Renderer 合并为一个批次(Batch),把多个DrawCall所需的数据一并上传到GPU,再一次性绘制,已达到优化性能的目的,此情形下,可无需使用Animation Instancing合批方案。
四、容易出错的地方
1、修改Render.material导致不能动态合批:
(1)Unity提供了两个获取Material的方法接口,分别是material及sharedMaterial。当对物体的material进行任何修改时,Unity会对Render里Materials列表第一个预设的Material进行实例化,并将返回实例。Unity这么做的目的是不影响其他物体,而仅仅修改这个实例。
(2)如果调用sharedMaterial,Unity就不会帮我们实例化,直接返回原本的材质球。但是会让所有使用该sharedMaterial的模型响应相同的修改。如果这不是你想要的,可以创建两个材质球,根据不同的情况替换材质。
2、动态合并的限制是单个模型900顶点:
Unity文档中所说的动态合并的顶点限制是900,指的是单个模型,而不是合批的模型总和。合批的模型总和上限是65535。同理,如果Shader使用了顶点坐标、法线、单个UV,那么限制是300个顶点;如果使用了顶点坐标、法线、UV0、UV1和切线,则限制是180个顶点。这两个限制针对的也是单个模型。
总结
简而言之,我们将项目中的材质球数量尽可能的减少和重用,尽可能的减少打断渲染的次数。以及根据项目需求,减少一些不必要的物体的渲染(减面或剔除),可以达到优化Draw Call的目的。
请注意,合并Draw Call是一种权衡。尽管它可以减少Draw Call数量,但过度合并可能导致内存占用过高或不足以应对特定情况下的渲染需求。因此,在应用这些技术时需要进行测试和性能优化,以确保达到最佳的性能和画质平衡。