当前位置 博文首页 > unity工具人的博客:unity3D 移动平台性能优化

    unity工具人的博客:unity3D 移动平台性能优化

    作者:[db:作者] 时间:2021-07-20 09:37

    unity3D 对于移动平台的支持无可厚非,但是也有时候用3D 开发出来的应用、游戏在移动终端上的运行有着明显的效率问题,比如卡、画质等各种问题。自己在做游戏开发的时候偶有所得。对于主要影响性能的因素做个总结。

    主要因素有:

    1. Saved by batching 值过大 ---- > 这个值主要是针对Mesh的批处理,这个值越高,应用就越卡

    2. Drawcall 值过大 ---- > Drawcall 值过大,所需要的 GPU 的处理性能较高,从而导致CPU的计算时间过长,于是就卡了

    3. 点、面过多 ---- > 点、面过多,GPU 根据不同面的效果展开计算,并且CPU计算的数据也多,所以效果出来了,但是卡巴斯基

    由于 Saved by batching 和 Drawcall 值过大所引起的卡的问题我所做的优化方式有:

    1. 对于模型 :Mesh 合并,有个不错的插件(DrawCall Minimizer —> 直接上Asset Store 下载即可,免费的,而且有文档,很容易上手)

    2. 对于UI : 尽量避免使用Unity3D自带的 GUI 换用 NGUI或者EZGUI;因为这两个UI插件对于UI中的图片处理是将UI图片放置在一个 Atlas 中,一个 Atlas 对应一个Drawcall

    3. 对于灯光: 可以使用 Unity3D 自带的 Lightmapping 插件来烘焙场景中的灯光效果到物体材质上

    4. 对于场景: 可以使用 Unity3D 自带的 Occlusion Culling 插件把静止不动的场景元素烘焙出来

    5. 对于特效:尽量把材质纹理合并

    对于Unity3D 在移动终端上支持的Drawcall 数到底多少,主要是跟机子性能有关的,当然也不是说值小性能就一定没问题(本人亲测,也有17就卡的,主要是模型材质纹理过大所引起的),目前我做的是70左右的,还OK,挺正常的

    由于点、面过多所导致的性能问题,最好用简模,用四面体来做复杂的模型,但是面、点也别太多,至于Unity3D 到底支持多少点、面的说法各异,我也搞不懂,总之少些肯定OK

    检测方式:

    一,Unity3D 渲染统计窗口

    Game视窗的Stats去查看渲染统计的信息:

    1、FPS

    fps其实就是 frames per second,也就是每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

    2、Draw calls

    batching之后渲染mesh的数量,和当前渲染到的网格的材质球数量有关。

    3、Saved by batching

    渲染的批处理数量,这是引擎将多个对象的绘制进行合并从而减少GPU的开销;

    很多GUI插件的一个好处就是合并多个对象的渲染,从而降低DrawCalls ,保证游戏帧数。

    4、Tris 当前绘制的三角面数

    5、Verts 当前绘制的顶点数

    6、Used Textures 当前帧用于渲染的图片占用内存大小

    7、Render Textures 渲染的图片占用内存大小,也就是当然渲染的物体的材质上的纹理总内存占用

    8、VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存

    9、VBO Total 渲染过程中上载到图形卡的网格的数量,这里注意一点就是缩放的物体可能需要额外的开销。

    10、Visible Skinned Meshes 蒙皮网格的渲染数量

    11、Animations 播放动画的数量

    注意事项:

    1,运行时尽量减少 Tris 和 Draw Calls

    预览的时候,可点开 Stats,查看图形渲染的开销情况。特别注意 Tris 和 Draw Calls 这两个参数。

    一般来说,要做到:

    Tris 保持在 7.5k 以下,有待考证。

    Draw Calls 保持在 20 以下,有待考证。

    2,FPS,每一秒游戏执行的帧数,这个数值越小,说明游戏越卡。

    3,Render Textures 渲染的图片占用内存大小。

    4,VRAM usage 显存的使用情况,VRAM总大小取决于你的显卡的显存。

    二,代码优化

    1. 尽量避免每帧处理

    比如:

    function Update() { DoSomeThing(); }

    可改为每5帧处理一次:

    function Update() { if(Time.frameCount % 5 == 0) { DoSomeThing(); } }

    1. 定时重复处理用 InvokeRepeating 函数实现

    比如,启动0.5秒后每隔1秒执行一次 DoSomeThing 函数:

    function Start() { InvokeRepeating(“DoSomeThing”, 0.5, 1.0); }

    1. 优化 Update, FixedUpdate, LateUpdate 等每帧处理的函数

    函数里面的变量尽量在头部声明。

    比如:

    function Update() { var pos: Vector3 = transform.position; }可改为

    private var pos: Vector3; function Update(){ pos = transform.position; }

    1. 主动回收垃圾

    给某个 GameObject 绑上以下的代码:

    function Update() { if(Time.frameCount % 50 == 0) { System.GC.Collect(); } }5. 优化数学计算

    比如,如果可以避免使用浮点型(float),尽量使用整形(int),尽量少用复杂的数学函数比如 Sin 和 Cos 等等

    6,减少固定增量时间

    将固定增量时间值设定在0.04-0.067区间(即,每秒15-25帧)。您可以通过Edit->Project Settings->Time来改变这个值。这样做降低了FixedUpdate函数被调用的频率以及物理引擎执行碰撞检测与刚体更新的频率。如果您使用了较低的固定增量时间,并且在主角身上使用了刚体部件,那么您可以启用插值办法来平滑刚体组件。

    7,减少GetComponent的调用

    使用 GetComponent或内置组件访问器会产生明显的开销。您可以通过一次获取组件的引用来避免开销,并将该引用分配给一个变量(有时称为"缓存"的引用)。例如,如果您使用如下的代码:

    function Update () { transform.Translate(0, 1, 0); }通过下面的更改您将获得更好的性能:

    function Awake () { myTransform = transform; } function Update () { myTransform.Translate(0, 1, 0); } 8,避免分配内存 您应该避免分配新对象,除非你真的需要,因为他们不再在使用时,会增加垃圾回收系统的开销。您可以经常重复使用数组和其他对象,而不是分配新的数组或对象。这样做好处则是尽量减少垃圾的回收工作。同时,在某些可能的情况下,您也可以使用结构(struct)来代替类(class)。这是因为,结构变量主要存放在栈区而非堆区。因为栈的分配较快,并且不调用垃圾回收操作,所以当结构变量比较小时可以提升程序的运行性能。但是当结构体较大时,虽然它仍可避免分配/回收的开销,而它由于"传值"操作也会导致单独的开销,实际上它可能比等效对象类的效率还要低。 9,使用iOS脚本调用优化功能 UnityEngine 命名空间中的函数的大多数是在 C/c + +中实现的。从Mono的脚本调用 C/C++函数也存在着一定的性能开销。您可以使用iOS脚本调用优化功能(菜单:Edit->Project Settings->Player)让每帧节省1-4毫秒。此设置的选项有: Slow and Safe – Mono内部默认的处理异常的调用 Fast and Exceptions Unsupported –一个快速执行的Mono内部调用。不过,它并不支持异常,因此应谨慎使用。它对于不需要显式地处理异常(也不需要对异常进行处理)的应用程序来说,是一个理想的候选项。 10, 优化垃圾回收 如上文所述,您应该尽量避免分配操作。但是,考虑到它们是不能完全杜绝的,所以我们提供两种方法来让您尽量减少它们在游戏运行时的使用: 如果堆比较小,则进行快速而频繁的垃圾回收 这一策略比较适合运行时间较长的游戏,其中帧率是否平滑过渡是主要的考虑因素。像这样的游戏通常会频繁地分配小块内存,但这些小块内存只是暂时地被使用。如果在iOS系统上使用该策略,那么一个典型的堆大小是大约 200 KB,这样在iPhone 3G设备上,垃圾回收操作将耗时大约 5毫秒。如果堆大小增加到1 MB时,该回收操作将耗时大约 7ms。因此,在普通帧的间隔期进行垃圾回收有时候是一个不错的选择。通常,这种做法会让回收操作执行的更加频繁(有些回收操作并不是严格必须进行的),但它们可以快速处理并且对游戏的影响很小:if (Time.frameCount % 30 == 0 { System.GC.Collect(); } ```但是,您应该小心地使用这种技术,并且通过检查Profiler来确保这种操作确实可以降低您游戏的垃圾回收时间 如果堆比较大,则进行缓慢且不频繁的垃圾回收 这一策略适合于那些内存分配 (和回收)相对不频繁,并且可以在游戏停顿期间进行处理的游戏。如果堆足够大,但还没有大到被系统关掉的话,这种方法是比较适用的。但是,Mono运行时会尽可能地避免堆的自动扩大。

    cs