[Unity优化] Unity中的批处理优化与GPU Instancing

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Unity优化] Unity中的批处理优化与GPU Instancing相关的知识,希望对你有一定的参考价值。

参考技术A

我们都希望能够在场景中投入一百万个物体,不幸的是,渲染和管理大量的游戏对象是以牺牲CPU和GPU性能为代价的,因为有太多Draw Call的问题,最后我们必须找到其他的解决方案。

在本文中,我们将讨论两种优化技术,它们可以帮助您减少Unity游戏中的Draw Call数量以提高整体性能:批处理和GPU Instancing。

开发者在日常工作中遇到的最常见的问题之一是性能不足,这是由于CPU和GPU的运行能力不足。一些游戏可以运行在PC上,但是在移动设备上不行。游戏运行时运行是否流畅受Draw Call数量的影响很大。有几个解决方案能帮助您解决这个问题。最常见的是批处理,包括Static Batching和Dynamic Batching。

Static Batching可以让引擎降低任何尺寸网格的Draw Call,如下图所示:

要让场景中的物体使用Static Batching,需要将其标记为Static,并在Mesh Renderer中共享相同的材质,因为Static Batching不会在CPU上做顶点转换,所以它通常比Dynamic Batching更有效。不过它会使用更多的内存,例如你的场景中有相同物体的多个副本,Unity会将它们组合成一个大网格并可能会增加内存使用。Unity将尽可能多的网格结合到一个静态网格中,并将其作为一个Draw Call提交。这种方法的缺点是:标记为Static的物体在其生命周期中不能移动。

Dynamic Batching启用时,Unity将尝试自动批量移动物体到一个Draw Call中。要使物体可以被动态批处理,它们应该共享相同的材质,但是还有一些其他限制:

顶点数量: Dynamic Batching场景中物体的每个顶点都有一定的开销,因此批处理只适用于少于900个顶点属性的网格物体。举个例子,如果你的着色器使用顶点位置,法线和一个UV,那么你可以动态批处理多达300个顶点;而如果你的着色器使用顶点位置,法线,UV0,UV1和切线,那么只有180个顶点。值得注意的是,属性计数限制可能会在将来更改。

镜像信息: 如果物体包含的Transform具备镜像信息,例如A物体的大小是(1f, 1f, 1f),而B物体的大小则是(-1f, -1f, -1f),则无法做批处理。

材质 :如果物体使用不同的材质实例,即使它们本质上相同,也不会被批量处理。而Shadow Caster Rendering是个例外。

渲染器: 拥有光照贴图的物体有其他渲染器参数,例如光照贴图索引或光照贴图的偏移与缩放。一般来说,动态光照贴图的游戏对象应该指向要批量处理的完全相同光照贴图的位置。

不能使用Multi-pass着色器的情况: 几乎所有的Unity着色器都支持多个灯光的正向渲染模式(Forward Rendering),这要求额外的渲染次数,所以绘制 “额外的每像素灯”时不会被批处理;Legacy Deferred(Light Pre-Pass)渲染路径不能被动态批处理,因为它必须绘制物体两次。

Dynamic Batching通过将所有物体的顶点转换为CPU上的世界空间来工作,所以它只能在渲染Draw Call的工作量小于CPU顶点转换工作量的时候,才会起到提高性能的作用。当用游戏机或如Metal这样的现代API,Draw Call的开销通常低得多,Dynamic Batching就无法提高性能了。了解到以上限制后,如果明智地使用批处理,可以显著提高您游戏的性能。

提高图形性能的另一个好办法是使用GPU Instancing。GPU Instancing的最大优势是可以减少内存使用和CPU开销。当使用GPU Instancing时,不需要打开批处理,GPU Instancing的目的是一个网格可以与一系列附加参数一起被推送到GPU。要利用GPU Instancing,您必须使用相同的材质,且可以传递额外的参数到着色器,如颜色,浮点数等。

Unity从5.4版本开始支持GPU Instancing。 唯一的限制是在游戏物体上要使用相同的材质和网格。 目前支持以下平台:

Windows DX11/DX12 和 SM 4.0 或更高/OpenGL 4.1 或更高

OS X and Linux:OpenGL 4.1 and above

移动:OpenGL ES 3.0 或更高/Metal

PlayStation 4

Xbox One

如果您想要进行进一步的优化,例如减少管理场景物体的开销,您也可以使用Graphics.DrawMeshInstanced方法。 您只需要传递您的网格,材质和附加属性来绘制您的物体。现在的限制是一次最多1023个实例。在Unity 5.6中,我们添加了Graphics.DrawMeshInstancedIndirect的新方法,可以用来指定需要渲染的实例数量。

要创建支持GPU Instancing的基本标准表面着色器,可以在您的项目里面点击:

Create->Shader->StandardSurfaceShader(Instanced)。

然后,在材质属性中选择新创建的着色器。

虽然实例化的物体共享相同的网格和材质,但您可以使用MaterialPropertyBlock API为每一个物体设置单独的着色器属性。

如果一个游戏对象被标记为“Static”并且打开了Static Batching,那么这个游戏对象就不能进行GPU Instancing,检视器中会出现一个警告框,提示“静态批处理”标志可以在播放器设置(Player Settings)中取消。如果游戏对象支持Dynamic Batching,但是它使用的某个材质可以进行实例化,那么这个游戏对象将不会被批处理,并且将被自动实例化。

当使用Forward Rendering渲染模式,受多个灯光影响的物体无法有效地实例化。只有Base Pass可以有效地利用实例化,而不是添加的Pass。此外,使用光照贴图或受不同光或Reflection probe影响的物体无法实例化。如下图所示,您可以在Frame Debug中发现和GPU Instancing相关的Draw Call被标记为“Draw Mesh(Instanced)”。

GPU Instancing是一个非常强大的功能。在Unity 5.6中,您可以使用Graphics.DrawMeshInstancedIndirect绘制大量网格。在Mac Pro中,我们能够画出约68万个具有不同颜色的移动立方体并保持稳定的60帧每秒的帧率。

下图是一个示例场景,超过6千个包子在天空中围绕一个大碗飞翔,它们都投射和接收阴影。由于使用了GPU Instancing,几乎没有性能开销。这里的包子模型使用了StandardSurface Shader(Instanced)。

在本文中,我们描述了用于优化渲染性能的两种最流行的技术:批处理和GPU Instancing。我们向您展示了如何在实践中使用它们并讨论可能的应用。正因为有诸如批处理和GPU Instancing等优化技术的存在,我们能够绘制大量的对象并保持稳定的性能。

Unity3D图形渲染优化渲染管线优化图形性能优化

主要内容也可以参考:

最简单的优化建议:

1.PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU。
2.如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unlit目录下的。它们更高效。
3.尽可能共用材质。
4.将不需要移动的物体设为Static,让引擎可以进行其批处理。
5.尽可能不用灯光。
6.动态灯光更加不要了。
7.尝试用压缩贴图格式,或用16位代替32位。
8.如果不需要别用雾效(fog)
9.尝试用OcclusionCulling,在房间过道多遮挡物体多的场景非常有用。若不当反而会增加负担。
10.用天空盒去“褪去”远处的物体。
11.shader中用贴图混合的方式去代替多重通道计算。
12.shader中注意float/half/fixed的使用。
13.shader中不要用复杂的计算pow,sin,cos,tan,log等。
14.shader中越少Fragment越好。
15.注意是否有多余的动画脚本,模型自动导入到U3D会有动画脚本,大量的话会严重影响消耗CPU计算。
16.注意碰撞体的碰撞层,不必要的碰撞检测请舍去。

1.为什么需要针对CPU(中央处理器)与GPU(图形处理器)优化?

CPU和GPU都有各自的计算和传输瓶颈,不同的CPU或GPU他们的性能都不一样,所以你的游戏需要为你目标用户的CPU与GPU能力进行针对开发。

2.CPU与GPU的限制

GPU一般具有填充率(Fillrate)和内存带宽(Memory Bandwidth)的限制,如果你的游戏在低质量表现的情况下会快很多,那么,你很可能需要限制你在GPU的填充率。

CPU一般被所需要渲染物体的个数限制,CPU给GPU发送渲染物体命令叫做DrawCalls。一般来说DrawCalls数量是需要控制的,在能表现效果的前提下越少越好。通常来说,电脑平台上DrawCalls几千个之内,移动平台上DrawCalls几百个之内。这样就差不多了。当然以上并不是绝对的,仅作一个参考。

往往渲染(Rendering)并不是一个问题,无论是在GPU和CPU上。很可能是你的脚本代码效率的问题,用Profiler查看下。
关于Profiler介绍: 

需要注意的是:
在GPU中显示的RenderTexture.SetActive()占用率很高,是因为你同时打开了编辑窗口的原因,而不是U3D的BUG。

3.关于顶点数量和顶点计算

CPU和GPU对顶点的计算处理都很多。GPU中渲染的顶点数取决于GPU性能和SHADER的复杂程度,一般来说,每帧之内,在PC上几百万顶点内,在移动平台上不超过10万顶点。

CPU中的计算主要是在蒙皮骨骼计算,布料模拟,顶点动画,粒子模拟等。GPU则在各种顶点变换、光照、贴图混合等。

【个人认为,具体还是看各位的项目需求,假设你项目的是3D游戏。你游戏需要兼容低配置的硬件、流畅运行、控制硬件发热的话,还要达到一定效果(LIGHTMAP+雾效),那么顶点数必定不能高。此时同屏2W顶点我认为是个比较合适的数目,DRAWCALL最好低于70。另,控制发热请控制最高上限的帧率,流畅的话,帧率其实不需要太高的。】

4.针对CPU的优化——减少DRAW CALL 的数量

为了渲染物体到显示器上,CPU需要做一些工作,如区分哪个东西需要渲染、区分开物体是否受光照影响、使用哪个SHADER并且为SHADER传参、发送绘图命令告诉显示驱动,然后发送命令告诉显卡删除等这些。


假设你有一个上千三角面的模型却用上千个三角型模型来代替,在GPU上花费是差不多的,但是在CPU上则是极其不一样,消耗会大很多很多。为了让CPU更少的工作,需要减少可见物的数目:


a.合并相近的模型,手动在模型编辑器中合并或者使用UNITY的Draw call批处理达到相同效果(Draw call batching)。具体方法和注意事项查看以下链接:
Draw call batching : 

b.在项目中使用更少的材质(material),将几个分开的贴图合成一个较大的图集等方式处理。
如果你需要通过脚本来控制单个材质属性,需要注意改变Renderer.material将会造成一份材质的拷贝。因此,你应该使用Renderer.sharedMaterial来保证材质的共享状态。
有一个合并模型材质不错的插件叫Mesh Baker,大家可以考虑试下。

c.尽量少用一些渲染步骤,例如reflections,shadows,per-pixel light 等。

d.Draw call batching的合并物体,会使每个物体(合并后的物体)至少有几百个三角面。
假设合并的两个物体(手动合并)但不共享材质,不会有性能表现上的提升。多材质的物体相当于两个物体不用一个贴图。所以,为了提升CPU的性能,你应该确保这些物体使用同样的贴图。
另外,用灯光将会取消(break)引擎的DRAW CALL BATCH,至于为什么,查看以下:
Forward Rendering Path Details:

e.使用相关剔除数量直接减少Draw Call数量,下文有相关提及。

5.优化几何模型

最基本的两个优化准则:
a.不要有不必要的三角面。
b.UV贴图中的接缝和硬边越少越好。
需要注意的是,图形硬件需要处理顶点数并跟硬件报告说的并不一样。不是硬件说能渲染几个点就是几个点。模型处理应用通展示的是几何顶点数量。例如,一个由一些不同顶点构成的模型。在显卡中,一些集合顶点将会被分离(split)成两个或者更多逻辑顶点用作渲染。如果有法线、UV坐标、顶点色的话,这个顶点必须会被分离。所以在游戏中处理的实际数量显然要多很多。

6.关于光照

若不用光肯定是最快的。移动端优化可以采用用光照贴图(Lightmapping)去烘培一个静态的贴图,以代替每次的光照计算,在U3D中只需要非常短的时间则能生成。这个方法能大大提高效率,而且有着更好的表现效果(平滑过渡处理,还有附加阴影等)。

在移动设备上和低端电脑上尽量不要在场景中用真光,用光照贴图。这个方法大大节省了CPU和GPU的计算,CPU得到了更少的DRAWCALL,GPU则需要更少顶点处理和像素栅格化。

Lightmapping : 

7.对GPU的优化——图片压缩和多重纹理格式

Compressed Textures(图片压缩):

图片压缩将降低你的图片大小(更快地加载更小的内存跨度(footprint)),而且大大提高渲染表现。压缩贴图比起未压缩的32位RGBA贴图占用内存带宽少得多。

之前U3D会议还听说过一个优化,贴图尽量都用一个大小的格式(512 * 512 , 1024 * 1024),这样在内存之中能得到更好的排序,而不会有内存之间空隙。这个是否真假没得到过测试。

MIPMAPS(多重纹理格式):

跟网页上的略缩图原理一样,在3D游戏中我们为游戏的贴图生成多重纹理贴图,远处显示较小的物体用小的贴图,显示比较大的物体用精细的贴图。这样能更加有效的减少传输给GPU中的数据。

8.LOD 、 Per-Layer Cull Distances 、 Occlusion Culling

LOD (Level Of Detail) 是很常用的3D游戏技术了,其功能理解起来则是相当于多重纹理贴图。在以在屏幕中显示模型大小的比例来判断使用高或低层次的模型来减少对GPU的传输数据,和减少GPU所需要的顶点计算。

摄像机分层距离剔除(Per-Layer Cull Distances):为小物体标识层次,然后根据其距离主摄像机的距离判断是否需要显示。
遮挡剔除(Occlusion Culling)其实就是当某个物体在摄像机前被另外一个物体完全挡住的情况,挡住就不发送给GPU渲染,从而直接降低DRAW CALL。不过有些时候在CPU中计算其是否被挡住则会很耗计算,反而得不偿失。

以下是这几个优化技术的相关使用和介绍:
Level Of Detail :

Per-Layer Cull Distances :

Occlusion Culling :
 l

9.关于Realtime Shadows(实时阴影)

实时阴影技术非常棒,但消耗大量计算。为GPU和CPU都带来了昂贵的负担,细节的话参考下面:

10.对GPU优化:采用高效的shader

a.需要注意的是有些(built-in)Shader是有mobile版本的,这些大大提高了顶点处理的性能。当然也会有一些限制。
b.自己写的shader请注意复杂操作符计算,类似pow,exp,log,cos,sin,tan等都是很耗时的计算,最多只用一次在每个像素点的计算。不推荐你自己写normalize,dot,inversesqart操作符,内置的肯定比你写的好。
c.需要警醒的是alpha test,这个非常耗时。
d.浮点类型运算:精度越低的浮点计算越快。
在CG/HLSL中--
float :32位浮点格式,适合顶点变换运算,但比较慢。
half:16位浮点格式,适合贴图和UV坐标计算,是highp类型计算的两倍。
fixed: 10位浮点格式,适合颜色,光照,和其他。是highp格式计算的四倍。

写Shader优化的小提示:

11.另外的相关优化:

a.对Draw Call Batching的优化

b.对Rendering Statistics Window的说明和提示:

c.角色模型的优化建议
用单个蒙皮渲染、尽量少用材质、少用骨骼节点、移动设备上角色多边形保持在300~1500内(当然还要看具体的需求)、PC平台上1500~4000内(当然还要看具体的需求)。

【推荐阅读】


Imagination中文社区

权威发布有关Imagination公司CPU,GPU以及连接IP、无线IP最新资讯,提供有关物联网、可穿戴、通信、汽车电子、医疗电子等应用信息,每日更新大量信息,让你紧跟技术发展,欢迎免费注册。网址:imgtec.eetrend.com

想了解更多信息,关注后反馈给我吧! 













以上是关于[Unity优化] Unity中的批处理优化与GPU Instancing的主要内容,如果未能解决你的问题,请参考以下文章

Unity程序优化之对象池

Unity 优化篇 | 优化专栏《导航帖》,全面学习Unity优化技巧,让我们的Unity技术上升一个档次

unityUI优化笔记

Unity优化如何实现Unity编辑器中的协程

Unity 优化篇 | 优化专栏《导航帖》,全面学习Unity优化技巧,让我们的Unity技术上升一个档次

Unity优化