unity-优化总结
关于 unity 项目相关优化经验的墨迹
前篇
- Unite 2019 |《球球大作战》优化之路(上)
- Unity 性能优化(力荐) - https://www.cnblogs.com/Jason-c/p/8137193.html
- Unity3D性能优化——渲染篇 - https://zhuanlan.zhihu.com/p/40900056
- Unity性能优化 – 脚本篇 - https://wuzhiwei.net/unity_script_optimization/
- 【Unity技巧】Unity中的优化技术 - https://blog.csdn.net/candycat1992/article/details/42127811
- Unity性能优化(三)-图形渲染优化 - https://blog.csdn.net/qq_21397217/article/details/80401708
总结起来,主要的性能瓶颈在于:
- 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
粒子优化
- Unity 特效drawcall优化 - https://blog.csdn.net/zgl159040290/article/details/78844200
- 粒子的动态合批问题 - http://gameinstitute.qq.com/question/detail/28322
UI 优化
UI 打图集
尽量同一个ui上的使用到的图片, 打到一个图集中, 特别是 listview 这种, 如果item 与 item 之间断掉合批的话, 那么 dc 将会比较高.
UI 去 mipmap
导入unity中的贴图默认会勾选上 generate mip maps, 会增大贴图在内存中的大小及包体大小. 这个一般使用来场景中的物体在 近大远小 的情况下做锯齿处理的.
但是 ui 如果没有 离ui摄像机远近问题需求 的话, 这个是不需要开启的.
优化方式是: 在导入是hook住导入方法, 检测是否是丢到 ui 资源路径下, 是的话自动干掉这个勾选.
模型优化
fbx模型 选项设置
- Mesh Compression : High
- Read/Write Enabled : false
- Optimize Mesh : true
工具: 资源检查 -> 模型优化 -> 所有模型优化
shader提取
使用到unity内置的shader,提取 (从官网下,路径加上 ITS/ 方便识别 是否是内置shader) 出来到项目中,打成一个ab
工具: 资源检测 -> 检查Shader All
bundle 压缩
最高压缩率方式 LZMA
:
1 | BuildAssetBundleOptions options = BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.ChunkBasedCompression; |
常用压缩方式 LZ4
:
1 | BuildAssetBundleOptions options = BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.None; |
使用 LZMA
包体最小,但解压速度也会变慢一点。
美术规范制定
- 定好 max 导出资源的 模型面数, 贴图大小 (2的n次幂) , 命名要求, 导出单位, 局部坐标系
- 单位同一为 厘米
- 导出时 Y轴向上, max 是右手坐标系, unity 是左手坐标系, 调整局部坐标系 y轴朝向, z轴为模型正方向. 这样导出到 unity 才能这两个坐标轴一致.
- max 导出的资源 不要嵌入贴图, 不然在unity会自动 .fbm 文件夹及 贴图资源, 之前遇到过 不知打包还是排在手机上会有问题. 模型与贴图 一同给过来, 材质球也要按照相关命名要求.
- 贴图按照一定 规格分辨率导出, 在 unity 中编写工具 hook 住导入函数, 检测导入资源的大小.
- 角色模型及动作,
- 模型 与 动作 分开导出, 定好命名规范
- 不同动作导出必须分开导出, 且每个动作的导出在max中要求为第0帧开始, 因为如果max在中安 0-4, 5-10 这样指定帧导出动作片段的话, 导出的 fbx会比较大.
- 定好 场景 lightmap 分辨率大小
- ui 方面出图
- 大部分不需要要求按2的n次幂, 因为可以打图集
- 小部分不需要打图集的需要按 2的n次幂出图
- spine 动画也是, 在 h5 项目中, 因为使用的 egret, 所以如果粒子动画改变颜色的话, 会打断合批. 当然还有其他操作会打断合批. 所以要求 spine 做资源是要注意这些事项, 不然几个例子动画可以就会占用 几十个批次.
- 模型的面标上光滑组, 导入unity中时, 可以减少顶点数量
美术工作流程
主要就是在 unity 中编写插件工具, 自动生成所需的 prefab 资源
根据美术的需求编写相关工具.
比如我们的关卡场景编辑, 我会先编好一个模板, 然后写个插件工具, 让不同关卡需要不同模型时, 直接往插件上拖动, 然后直接生成一个新关卡, 然后再稍微细调一下.
场景需要的必要元素, 也编写了工具去检查, 防止编辑人员的疏漏.
GPU渲染优化
- 优化GPU渲染问题主要从三个方面来进行,分别是顶点,填充,带宽。我们需要明确这三个方面的概念。
1.顶点处理。顶点处理是指GPU需要渲染网格中每一个顶点的工作。
顶点处理的消耗受两方面影响:必须渲染的顶点数量,以及在每个顶点上要进行的操作数量。
2.填充率。填充率是指GPU在屏幕上每秒可以渲染的像素数。如果我们的游戏受到填充率的限制,意味着我们的游戏每帧尝试绘制的像素数量超过了GPU的处理能力。
3.显存带宽。显存带宽是指GPU读写其专用内存的速度。如果我们的游戏速度受限于显存带宽,通常可能是我们使用的纹理太大,以至于GPU无法快速处理。
- 对于GPU性能的优化问题,我们可以通过Profiler分析,并关注GPU时间,来锁定引起性能问题的原因,从而进行优化。
优化显存带宽
vertex, frag 计算优化
可以通过减少不必要的物体绘制, 也就是 减少要渲染的对象数量
frame debug 调试器
通过 unity 内置的 帧调试器, 可以看出每一帧的绘制情况, 要运行时去调试. 因为运行时才会 static batch.
比如 cube 和 cylinder 期望是需要 动态合批 的, 但是实际上没有合批, 调试提示也很明显了, dynamic batch 开关没有打开.
纹理压缩。
- 许多贴图采用的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。
- 当加载一个新的Prefab或贴图,不及时回收,它就会永驻在内存中,就算切换场景也不会销毁。应该确定物体不再使用或长时间不使用就先把物体制空(null),然后调用Resources.UnloadUnusedAssets(),才能真正释放内存。
- 有大量空白的图集贴图,可以用TexturePacker等工具进行优化或考虑合并到其他图集中。
Non Power of 2
定义:对于那些图片尺寸:长、宽不全是2的幂次尺寸的图片称为 NPOT(Non Power Of Two)格式的图片。
而对于项目中需要压缩的贴图,需要尽量做成POT。
因为常用的ETC1(For Android) PVRTC(For IOS)压缩格式图片都要求POT贴图。
这个选项的用途就是将NPOT的图片转换成POT图片。图片所占内存也会根据尺寸发生改变。
例如将上图纹按ToNearest操作
纹理大小由 (2048)1080(1024) * (1024)810(512) 变成 1024*1024
之后才能进行压缩
参考: 移动端纹理压缩格式 - https://www.cnblogs.com/zsb517/p/6297739.html
非压缩格式
格式 | 内存消耗 (bytes/pixel) | 描述 |
---|---|---|
RGBA8888(RGBA32) | 4bpp | RGBA通道各占用8位 |
RGBA4444(RGBA16) | 2bpp | 每个像素2字节 |
RGB888(RGB24) | 3bpp | 每个像素3字节,RGB通道各占用8位,无透明通道 |
RGB565(RGB16) | 2bpp | 每个像素2字节,RGB通道各占用5/6/5位,无透明通道 |
压缩格式
格式 | 压缩比 | GPU支持 | 描述 | 图片要求 |
---|---|---|---|---|
DXT | DXT1:0.3/DXT5:0.6 | Windows\Android(Nvidia Tegra and Intel Bay Trail) | 分为DXT1-DXT5这五个级别,DXT1 适用于不具有透明度或者仅具有一位Alpha的贴图,DXT3和DX5支持包含4位alpha通道的RGB纹理 | 无 |
ATC RGBA/RGB | RGBA:0.25/RGB:0.125 | Qualcomm -Adreno | 高通GPU支持格式,支持带有Alpha的RGB纹理压缩。 | 无 |
PVRTC RGBA/RGB | 2bit:0.125/4bit:0.25 | PowerVR | IOS平台都支持,支持每个像素2位或者4位的纹理,包含或者不包含alpha通道都可以;PVRTC 2-bpp把一个8×4的像素单元组压成一个64位的数据块,压缩效果比较差;PVRTC 4-bpp把一个4×4的像素单元组压成一个64位的数据块。游戏中使用4位压缩更多。 | 尺寸为2的N次幂,并且宽高相同。 |
ETC1 RGB 4Bit | 0.125 | 支持Opnegl ES2.0的GPU | OpenGL ES2.0版本支持,移动GPU均支持的一个格式,遗憾的是不支持Alpha通道。ETC1把一个4x4的像素单元组压成一个64位的数据块。游戏开发中采用最多的格式,不过麻烦的是需要对Alpha通道进行单独存储,Unity5.4.3版本之后提供了官方支持 | 尺寸为2的N次幂,长宽可不同 |
ETC2 ARGB/RGB 4bit | RGBA:0.25/RGB:0.125 | 支持Opnegl ES3.0的GPU | OpenGL ES 3.0以上才支持,补全了ETC1不支持Alpha通道,支持更高质量的压缩。虽然如此,从Android官方数据来看,还有相当大的设备是采用Opengl ES2.0;使用需要谨慎,不过随着设备更新换代,开发时间周期比较长的游戏可以考虑直接使用 | 尺寸为4的倍数 |
Unity官网对每个平台默认的纹理压缩格式以及使用建议给出了详细描述,需要注意的是:在不同移动GPU平台下选择GPU支持的压缩纹理,就可以在不需要CPU解压的情况下直接被GPU采样,节省CPU内存和带宽,也可以节省存储的体积。如果目标平台不支持设置的压缩格式,纹理将解压为RGBA32或者RGB24,浪费CPU时间和内存。
ASTC
从IOS9(A8架构)Apple 手机开始支持ASTC压缩格式 ,如果考虑放弃Apple 6代之前的手机兼容问题了,可以直接使用了。相对于PVRTC2/4而言,ASTC(4X4)的压缩比会增加到0.25,不过显示效果也会好很多,而且不需要把图片设置为方形。
Using ASTC Texture Compression for Game Assets 说明的比较详细,也给出了一些使用上的建议,即针对不同贴图类型给出不同的压缩方案。
ARM的参考资料: ASTC: The Future of Texture Compression
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格式。
减少码率.
- 相关参考:
.AIFF 适用于较短的音乐文件可用作游戏打斗音效
.WAV 适用于较短的音乐文件可用作游戏打斗音效
.MP3 适用于较长的音乐文件可用作游戏背景音乐
.OGG 适用于较长的音乐文件可用作游戏背景音乐
Default选项:
Override for android:减小安卓打包体积;
Load Type:
Streaming:流
动态解码声音。此方法使用最小量的内存来缓冲从磁盘逐渐读取并在运行中解码的压缩数据。请注意,解压缩发生在分析器窗口的音频面板的“Streaming CPU”部分中可监视其CPU使用率的单独流式线程上。注意:即使没有加载任何音频数据,流式片段也会有大约200KB的过载。
Decompress On Load:加载时解压缩
音频文件一经加载就会被解压缩。对较小的压缩声音使用此选项可避免即时解压缩的性能开销。请注意,在加载时解压缩Vorbis编码的声音比使用它压缩大约多十倍的内存(对于ADPCM编码大约是3.5倍),所以不要将此选项用于大文件。Compressed In Memory:压缩在内存中
保持声音在存储器中压缩并在播放时解压缩。这个选项有一个小的性能开销(尤其是对于Ogg / Vorbis压缩文件),所以只能用于较大的文件,因为在加载时解压缩会使用大量的内存。解压缩在混音器线程上发生,并可在Profiler窗口的音频面板中的“DSP CPU”部分进行监视。
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性能优化 – 脚本篇
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
大量的MonoBehaviour
的Update
需要执行时,在profiler中可以看到它们的耗时很高。因为在MonoBehaviour
内部调用Update
时需要做一系列检查,如下图所示
MonoBehaviour管理器,里面维护一个List,然后将这些需要调用Update
的MonoBehaviour扔进List中,并将它们的Update
函数改成其他名字,比如MonoUpdate
。然后在这个管理器的Update
函数中循环遍历所有的MonoBehaviour调用它们的MonoUpdate
。结果可以获得数量级上的提升
Transform.SetPositionAndRotation
每次调用Transform.SetPosition
或Transform.SetRotation
时,Unity都会通知一遍所有的子节点。
当位置和角度信息都可以预先知道时,我们可以通过Transform.SetPositionAndRotation
一次调用来同时设置位置和角度,从而避免两次调用导致的性能开销
Animator.Set
Animator
提供了一系列类似于SetTrigger
、SetFloat
等方法来控制动画状态机。例如:m_animator.SetTrigger(“Attack”)
是用来触发攻击动画。然而在这个函数内部,“Attack”
字符串会被hash成一个整数。如果我们需要频繁触发攻击动画,我们可以通过Animator.StringToHash
来提前进行hash,来避免每次的hash运算
1 | private static readonly int s_Attack = Animator.StringToHash(“Attack”); |
Material.Set
与Animator
类似,Material
也提供了一系列的设置方法用于改变Shader。例如:m_mat.SetFloat(“Hue”, 0.5f)
是用来设置材质的名为Hue的浮点数。同样的我们可以通过Shader.PropertyToID
来提前进行hash。
1 | private static readonly int s_Hue = Shader.PropertyToID("Hue"); |
Vector Math
如果需要比较距离,而非计算距离,用SqrMagnitude
来替代Magnitude
可以避免一次耗时的开方运算。
在进行向量乘法时,有一点需要注意的是乘法的顺序,因为向量乘比较耗时,所以我们应该尽可能的减少向量乘法运算。
1 | // 耗时:73ms |
可以看出上述的向量乘法的结果完全一致,但是却有显著的耗时差异,因为后者比前者少了一次向量乘法。所以,应该尽可能合并数字乘法,最后再进行向量乘。
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性能优化 – 脚本篇
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技术
- 先渲染不透明物体,顺序是从前到后;
- 再渲染透明物体,顺序是从后到前
粒子优化
- 关于Unity粒子系统优化 - https://blog.uwa4d.com/archives/QA_ParticleSystem-1.html
粒子系统的Draw Call动态拼合与半透明物体的动态拼合机制相当(粒子基本都是半透明材质)。而对半透明物体,由于其渲染顺序的限制(必须从后向前渲染,以保证渲染结果的正确性),动态拼合只能对渲染顺序相邻且材质相同的物体有效。而在决定半透明物体的渲染顺序时,Unity首先会按Shader中的RenderQueue进行排序;其次(相同RenderQueue时),会根据每个半透明物件到屏幕的距离,距离大的优先渲染。
因此,需要尽可能地将相同材质的粒子系统放在比较接近的深度下,才能更多地使动态拼合生效。但通常由于相机的运动、粒子系统的分散分布等原因造成粒子系统之间的穿插,能够动态拼合的数量往往都是很少的,所以我们在粒子系统模块看到的开销分布通常类似该图,主要都是未拼合粒子系统造成。
引用内置shader 导致 ab 膨胀问题
- 分析了一下打出的ab包的 manifest 文件
- 将内置shader提取出来,打到ab中,加上自定义前缀,方便区分是否是引用了系统内置shader
也可以写个工具检测是否是引用系系统内置shader
移动端阴影优化
参考: Unity移动端软阴影技术总结 - http://www.pianshen.com/article/7306130842/