当前位置:   article > 正文

unity-优化总结_unity场景顶点数多少最好

unity场景顶点数多少最好

title: unity-优化总结
categories: Unity3d
tags: [unity, 性能, 优化]
date: 2017-08-09 10:05:18
comments: false

关于 unity 项目相关优化经验的墨迹


前篇

总结起来,主要的性能瓶颈在于:

  • CPU
    • 过多的Draw Calls
    • 复杂的脚本或者物理模拟
  • 顶点处理
    • 过多的顶点
    • 过多的逐顶点计算
  • 像素(Fragment)处理
    • 过多的fragment,overdraws
    • 过多的逐像素计算
  • 带宽
    • 尺寸很大且未压缩的纹理
    • 分辨率过高的framebuffer

模型优化 - 资源大小

优化使打出来的 assetbundle 更小,有利于减少包体大小 与 热更是的大小

贴图格式

测试过 3d模型 的 tga 与 png, 打出来的ab大小有点不同 , 在没有透明度的情况下 (模型一般都没有透明度需求), 尽量使用 tga 24b

贴图格式ab大小
tga_24b_noalpha
tga_32b_noaplha
png_32b_hasalpha
模型嵌入媒体

这个实在 3dmax 导出模型时, 如果勾选了 **嵌入媒体 **,会把贴图打进fbx中, fbx大小会变大. 导入unity 中是会自动生成一个 .fbm 文件夹, 里面就包含贴图文件.
经实测, 打出的ab资源, 不够是否嵌入媒体, 大小都是一样的


关于模型自带材质球

打包实测

  • 使用默认 fbx 里面的材质球, 打出来的ab最大

  • 不导入 fbx 的材质球, 还是很大

  • 使用自定义的材质球 及 shader, 最小

    此时不导入材质球也是最小的, 不需要remap了

  • 总结: 其实就是使用了引擎内置的shader引起的. 所以必须保证使用的是 自己的shader (把引擎内置shader提取出来). 再者使用工具自动检测, 以防止使用引擎内置的shader.

    最优的设置是 fbx 中引用自定的材质球, 同时不导入材质


动态合批

假设有 10 个相同的物体, 使用不同的 材质球 (即使材质球的贴图是同一张贴图), 会有 10 个批次

如果10个物体都使用同一个材质球, 则会被unity合到一个批次渲染. 即使中间有别的物体插入, 也不会打断这个合批, 因为有 zbuff.

需要注意的地方: 在游戏运行时, 如果去get模型的材质球 Renderer.material , 这个api会造成生成新的一个 材质球 返回, 因此会生成新的一个批次. 正确的做法是, 如果有多个物体都有需求用到 别的材质球, 可以新建一个 新的材质球, 别的物体都使用这个材质球, 这样只要使用这个材质球的模型就会在同一个批次绘制完成
别人的采坑参考: http://www.u3dnotes.com/archives/2267


减少要渲染的对象数量

1. 手动减少场景中物体的数量

这是一个最直观且有效的方法,比如在多人游戏中,我们可以减少可见玩家的数量,如果不影响游戏性和玩家体验,那这是就是一个即方便又快捷的方法。

2. Occlusion Culling(遮挡剔除)

遮挡剔除的原理就是当一个物体被其他物体遮挡住,不在摄像机的可视范围内时不对其进行渲染。具体方法如下所示:
把所有物体选中,Inspector面板中的static下拉菜单中勾选Occlusion Static 和 Occludee Static。

参考总结: unity-遮挡剔除OcclusionCulling.md

3. 摄像机Clipping Planes

我们可以通过摄像机的Clipping Planes 的Far裁剪远端,从而降低摄像机的绘制范围,如同所示:

为了降低性能损耗同时保证游戏质量,Far的值应该合理控制,不要造成不好的游戏体验,或者我们可以用雾来掩盖不被渲染的远端。

Lod 优化

可见情况下, 距离远的可以用模型精读低的替代, 减少顶点从 cpu 到 gpu 的传输量. 代价是 内存,包体 会增大

参考总结: unity-LOD优化.md


减少渲染对象的渲染次数

使用 lightmap, 关闭实时阴影

未开启阴影

开启阴影

会额外多次很多批次

  • 渲染深度图
  • 渲染阴影图级联

实时阴影的代打方案

对于静态物体, 烘焙光照图

动态物体, 比如角色, 只用 投影到地面的假阴影, 和 平面阴影


合批优化

参考总结: unity-合批优化.md


UI打图集

尽量同一个ui上的使用到的图片, 打到一个图集中, 特别是 listview 这种, 如果item 与 item 之间断掉合批的话, 那么 dc 将会比较高.


模型优化

fbx模型 选项设置

  • Mesh Compression : High
  • Read/Write Enabled : false
  • Optimize Mesh : true

工具: 资源检查 -> 模型优化 -> 所有模型优化

shader提取

使用到unity内置的shader,提取 (从官网下,路径加上 ITS/ 方便识别 是否是内置shader) 出来到项目中,打成一个ab

工具: 资源检测 -> 检查Shader All

bundle 压缩

最高压缩率方式 LZMA

BuildAssetBundleOptions options = BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.ChunkBasedCompression;
  • 1

常用压缩方式 LZ4

BuildAssetBundleOptions options = BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.None;
  • 1

使用 LZMA 包体最小,但解压速度也会变慢一点。


美术规范制定

  1. 定好 max 导出资源的 模型面数, 贴图大小 (2的n次幂) , 命名要求, 导出单位, 局部坐标系
    1. 单位同一为 厘米
    2. 导出时 Y轴向上, max 是右手坐标系, unity 是左手坐标系, 调整局部坐标系 y轴朝向, z轴为模型正方向. 这样导出到 unity 才能这两个坐标轴一致.
  2. max 导出的资源 不要嵌入贴图, 不然在unity会自动 .fbm 文件夹及 贴图资源, 之前遇到过 不知打包还是排在手机上会有问题. 模型与贴图 一同给过来, 材质球也要按照相关命名要求.
  3. 贴图按照一定 规格分辨率导出, 在 unity 中编写工具 hook 住导入函数, 检测导入资源的大小.
  4. 角色模型及动作,
    1. 模型 与 动作 分开导出, 定好命名规范
    2. 不同动作导出必须分开导出, 且每个动作的导出在max中要求为第0帧开始, 因为如果max在中安 0-4, 5-10 这样指定帧导出动作片段的话, 导出的 fbx会比较大.
  5. 定好 场景 lightmap 分辨率大小
  6. ui 方面出图
    1. 大部分不需要要求按2的n次幂, 因为可以打图集
    2. 小部分不需要打图集的需要按 2的n次幂出图
    3. spine 动画也是, 在 h5 项目中, 因为使用的 egret, 所以如果粒子动画改变颜色的话, 会打断合批. 当然还有其他操作会打断合批. 所以要求 spine 做资源是要注意这些事项, 不然几个例子动画可以就会占用 几十个批次.
  7. 模型的面标上光滑组, 导入unity中时, 可以减少顶点数量

美术工作流程

  1. 主要就是在 unity 中编写插件工具, 自动生成所需的 prefab 资源

  2. 根据美术的需求编写相关工具.

  3. 比如我们的关卡场景编辑, 我会先编好一个模板, 然后写个插件工具, 让不同关卡需要不同模型时, 直接往插件上拖动, 然后直接生成一个新关卡, 然后再稍微细调一下.

    场景需要的必要元素, 也编写了工具去检查, 防止编辑人员的疏漏.


GPU渲染优化

  • 优化GPU渲染问题主要从三个方面来进行,分别是顶点,填充,带宽。我们需要明确这三个方面的概念。

**1.顶点处理。**顶点处理是指GPU需要渲染网格中每一个顶点的工作。
顶点处理的消耗受两方面影响:必须渲染的顶点数量,以及在每个顶点上要进行的操作数量。

**2.填充率。**填充率是指GPU在屏幕上每秒可以渲染的像素数。如果我们的游戏受到填充率的限制,意味着我们的游戏每帧尝试绘制的像素数量超过了GPU的处理能力。

**3.显存带宽。**显存带宽是指GPU读写其专用内存的速度。如果我们的游戏速度受限于显存带宽,通常可能是我们使用的纹理太大,以至于GPU无法快速处理。

  • 对于GPU性能的优化问题,我们可以通过Profiler分析,并关注GPU时间,来锁定引起性能问题的原因,从而进行优化。
优化显存带宽
vertex, frag 计算优化

可以通过减少不必要的物体绘制, 也就是 减少要渲染的对象数量

frame debug 调试器

通过 unity 内置的 帧调试器, 可以看出每一帧的绘制情况, 要运行时去调试. 因为运行时才会 static batch.

比如 cube 和 cylinder 期望是需要 动态合批 的, 但是实际上没有合批, 调试提示也很明显了, dynamic batch 开关没有打开.


纹理压缩。

  1. 许多贴图采用的Format格式是ARGB 32 bit所以保真度很高但占用的内存也很大。在不失真的前提下,适当压缩贴图,使用ARGB 16 bit就会减少一倍,如果继续Android采用RGBA Compressed ETC2 8 bits(iOS采用RGBA Compressed PVRTC 4 bits),又可以再减少一倍。把不需要透贴但有alpha通道的贴图,全都转换格式Android:RGB Compressed ETC 4 bits,iOS:RGB Compressed PVRTC 4 bits。
  2. 当加载一个新的Prefab或贴图,不及时回收,它就会永驻在内存中,就算切换场景也不会销毁。应该确定物体不再使用或长时间不使用就先把物体制空(null),然后调用Resources.UnloadUnusedAssets(),才能真正释放内存。
  3. 有大量空白的图集贴图,可以用TexturePacker等工具进行优化或考虑合并到其他图集中。

mipmap

如果我们的场景包含距离摄像机很远的物体,我们可以通过使用miapmap来缓解显存带宽的问题,mipmap的主要作用便是模型的贴图会根据摄像机距离模型的远近而调整不同质量的贴图显示,以达到优化目的。

这里可以发现图片大小发生了变化,这是因为我们使用MipMap技术之后,会对此贴图生成八张精度质量不同的贴图,所以内存占用变大。

这种方式是以 内存 换 显存

  • MinMaps 正确使用姿势

    有远近区分的物体才需要开启,如 场景中的3d物体,特效等

    没有远近区分的不需要开启,如 ui


蒙皮(Skinned Meshes)优化

SkinnedMeshRenderer 组件用于处理骨骼动画,常用在角色动画上。与蒙皮相关的任务可以在主线程或者独立的工作线程中执行,具体取决于游戏设置和目标硬件平台。

蒙皮渲染的开销比较高,下面一些 对蒙皮渲染进行优化的手段:

减少SkinnedMeshRenderer组件的数量。导入模型时,模型可能带有SkinnedMeshRenderer组件,如果游戏中该模型并不会使用骨骼动画,就应该将SkinnedMeshRenderer组件替换为MeshRenderer。在导入模型时,可以选择不导入动画,请参考模型导入设置。
减少使用SkinnedMeshRenderer的对象的Mesh顶点数,参考蒙皮渲染器手册。
使用GPU Skinning。在硬件平台支持并且GPU资源足够的条件下,可以在Player Setting中启用GPU Skinning,将蒙皮任务从CPU转移到GPU。

更多优化内容请参考 角色模型优化手册


资源管理

编写工具检测 ab资源 是否有冗余

管理好 ab及其加载出来的资源和场景对象上的引用, 在合适的时候适当触发gc回收掉. 比如 要加载一个内存占用大的资源时, 可以尝试释放已经不存在于场景上的资源.

ab模式下查看内存ab占用, 查找出哪些没有在用却又一直占据着内存的资源.


音频资源

重点优化对象,播放时长较长的音乐文件需要进行压缩成.mp3或.ogg格式,时长较短的音效文件可以使用.wav 或.aiff格式。

减少码率.


profiler使用

打自定义tag

查看某个 tag 包裹下的 cpu 消耗.

参考总结: unity-打tag技巧.md


自定义调试工具

参考总结: q6调试工具总结.md

对应的文档: q6调试工具文档.md


框架设计层面。

一个相对中大型的游戏,系统非常的多。这时候合理的适时的释放内存有助于游戏的正常体验,甚至可以防止内存快速到达峰值,导致设备Crash。

目前主流平台机型可用内存:

Android平台:在客户端最低配置以上,均需满足以下内存消耗指标(PSS):

1)内存1G以下机型:最高PSS<=150MB

2)内存2G的机型:最高PSS<=200MB

iOS平台:在iPhone4S下运行,消耗内存(real mem)不大于150MB

中端机子

顶点小于10w个, dc 小于150


最简单的优化建议

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.注意碰撞体的碰撞层,不必要的碰撞检测请舍去。


Unity性能优化 – 脚本篇

参考链接: https://wuzhiwei.net/unity_script_optimization/

Unity API

GameObject.GetComponent

Unity都要去遍历所有的组件来找到目标组件。每次都去查找是不必要的耗费,我们可以通过缓存的方式来避免这些不必要的开销

GameObject.Find

所以当游戏内对象很多时,这个函数将很耗时

或者采用GameObject.FindWithTag来寻找特定标签的对象。如果能在一开始就确定好对象,可以通过Inspector注入的方式,将对象直接拖到Inspector中,从而避免了运行时的查找

Camera.main

Camera.main用来返回场景中的主相机,Unity内部是通过GameObject.FindWithTag来查找tag为MainCamera的相机。

当需要频繁访问主相机时,我们可以将其缓存以获得性能提升

GameObject.tag

GameObject.tag常用来比较对象的tag,但是直接采用.tag ==来进行对比的话,每一帧会产生 180B GC Alloc。通过GameObject.CompareTag来进行比较则可以避免掉这些GC

MonoBehaviour

大量的MonoBehaviourUpdate需要执行时,在profiler中可以看到它们的耗时很高。因为在MonoBehaviour内部调用Update时需要做一系列检查,如下图所示

MonoBehaviour管理器,里面维护一个List,然后将这些需要调用Update的MonoBehaviour扔进List中,并将它们的Update函数改成其他名字,比如MonoUpdate。然后在这个管理器的Update函数中循环遍历所有的MonoBehaviour调用它们的MonoUpdate。结果可以获得数量级上的提升

Transform.SetPositionAndRotation

每次调用Transform.SetPositionTransform.SetRotation时,Unity都会通知一遍所有的子节点。

当位置和角度信息都可以预先知道时,我们可以通过Transform.SetPositionAndRotation一次调用来同时设置位置和角度,从而避免两次调用导致的性能开销

Animator.Set

Animator提供了一系列类似于SetTriggerSetFloat等方法来控制动画状态机。例如:m_animator.SetTrigger(“Attack”)是用来触发攻击动画。然而在这个函数内部,“Attack”字符串会被hash成一个整数。如果我们需要频繁触发攻击动画,我们可以通过Animator.StringToHash提前进行hash,来避免每次的hash运算

private static readonly int s_Attack = Animator.StringToHash(“Attack”);
m_animator.SetTrigger(s_Attack);
  • 1
  • 2
Material.Set

Animator类似,Material也提供了一系列的设置方法用于改变Shader。例如:m_mat.SetFloat(“Hue”, 0.5f)是用来设置材质的名为Hue的浮点数。同样的我们可以通过Shader.PropertyToID来提前进行hash。

private static readonly int s_Hue = Shader.PropertyToID("Hue");
m_mat.SetFloat(s_Hue, 0.5f);
  • 1
  • 2
Vector Math

如果需要比较距离,而非计算距离,用SqrMagnitude来替代Magnitude可以避免一次耗时的开方运算。

在进行向量乘法时,有一点需要注意的是乘法的顺序,因为向量乘比较耗时,所以我们应该尽可能的减少向量乘法运算。

// 耗时:73ms
for (int i = 0; i < 1000000; i++)
    Vector3 c = 3 * Vector3.one * 2;
 
// 耗时:45ms
for (int i = 0; i < 1000000; i++)
    Vector3 c = 3 * 2 * Vector3.one;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以看出上述的向量乘法的结果完全一致,但是却有显著的耗时差异,因为后者比前者少了一次向量乘法。所以,应该尽可能合并数字乘法,最后再进行向量乘

Coroutine

当需要实现一些定时操作时,有些同学可能会在Update中每帧进行一次判断,假设帧率是60帧,需要定时1秒调用一次,则会导致59次无效的Update调用。

用Coroutine则可以避免掉这些无效的调用,只需要yield return new WaitForSeconds(1f);即可。当然这里的最佳实践还是用一个变量缓存一下new WaitForSeconds(1f),这样省去了每次都new的开销

SendMessage

SendMessage用来调用MonoBehaviour的方法,然而其内部采用了反射的实现机制,时间开销异常大,需要尽量避免使用。

可以用事件机制来取代它。

Debug.Log

众所周知,输出Log是一件异常耗时,而且玩家感知不到的事情。所以应该在正式发布版本时,将其关闭。

Unity的Log输出并不会在Release模式下被自动禁用掉,所以需要我们手动来禁用。我们可以在运行时用一行代码来禁用Log的输出:Debug.logger.logEnabled = false;

不过最好采用条件编译标签Conditional封装一层自己的Log输出,来直接避免掉Log输出的编译,还可以省去Log函数参数传递和调用的开销。具体可以参见:Unity3D研究院之在发布版本屏蔽Debug.log输出的Log

Update

少用foreach,因为每次foreach为产生一个enumerator(约16B的内存分配),尽量改为for.


Unity性能优化 – 脚本篇

参考链接: https://wuzhiwei.net/unity_script_optimization/

C#

字符串

字符串连接会导致GC Alloc,例如string gcalloc = "GC" + "Alloc"会导致"GC"变成垃圾,从而产生GC Alloc。又比如:string c = string.Format("one is {0}", 1),也会因为一次装箱操作(数字1被装箱成字符串"1")而产生额外的GC Alloc。

所以如果字符串连接是高频操作,应该尽量避免使用+来进行字符串连接。C#提供了StringBuilder类来专门进行字符串的连接。

IL2CPP

I2LCPP是Unity提供的将C#的IL码转换为C++代码的服务,由于转成了C++,所以其最后会转换成汇编语言,直接以机器语言的方式执行,而不需要跑在.NET虚拟机上,所以提高了性能。同时由于IL的反编译较为简单,转换成C++后,也会增加一定的反汇编难度。

IL2CPP的C++代码虽然是自动生成的,但是其中间的某些过程也可以被人为操纵,从而达到提升性能的目的。


Lua

local

Lua的默认变量都是全局变量,必须要加上local修饰才能变成局部变量。

局部变量相对于全部变量有以下几点好处: 1. 读写更快 2. 可以避免不经意的全局变量名污染 3. 在作用域结束时,会被自动标记为垃圾,避免了内存泄漏

所以,虽然Lua的默认变量声明都是全局变量,我们还是应该将其用local修饰为局部变量。

如lua中有 update 等频繁的调用函数, 里面有 频繁调用的 全部变量, 内置库, 可以考虑在当前 lua 文件(也就是一个环境中), 用一个 local 变量 作为函数的 upvalue, 去引用 全局变量, 内置库. 减少去全局搜索的消耗.

table

Lua中的表内部分为两部分:hash部分和array部分。当创建一个空表时,这两个部分都会默认初始化空间为0。随着内容的不断填充,会不断触发rehash。rehash是一次非常耗时的操作,所以应尽量避免之。

如果同时需要创建较多的小表,我们可以通过预先填充表以避免rehash。

string

与C#类似,在Lua中的字符串连接的代价也很高昂,但是与C#提供了StringBuilder不同,Lua没有提供类似的原生解决方案。

不过我们可以用table来作为一个buffer,然后使用table.concat(buffer, '')来返回最终连接的字符串


深度测试提前:Early-Z技术

硬件实现, 软件可以控制打开或关闭, 这个unity自动会处理

参考总结: graphic-前向渲染管线浅析.md 中的 深度测试提前:Early-Z技术

  1. 先渲染不透明物体,顺序是从前到后;
  2. 再渲染透明物体,顺序是从后到前
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/94518
推荐阅读
相关标签
  

闽ICP备14008679号