1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Unity - 性能优化 - 包体 内存 - 偏静态资源的优化

Unity - 性能优化 - 包体 内存 - 偏静态资源的优化

时间:2022-02-09 07:40:30

相关推荐

Unity - 性能优化 - 包体 内存 - 偏静态资源的优化

文章目录

静态资源优化 - AssetPostprocessorTexture 压缩Model 网格、动画 压缩音频压缩纹理的优化经验尺寸通道发布出来的包资源再次分析如何工具快速定位静态资源问题运行时的品质调整References

如果 产品 制作出来不经过优化,那不叫 产品,准确的说是:DEMO

此文偏 静态资源的优化 内容

如果是 unity 动态画质设置,可以参考目录中:运行时的品质调整

传送门链接,可以参考我之前写的:Unity - 画质设置

之前(.1月初左右的时候)做了一些项目分析,发现某个项目 性能方面 惨不忍睹

下面更多的是简单的调用 Unity API 来做的优化

还有其他更多的是优化经验上的数值

初步判断可以做的优化点:(下面是简述版本,详细的就不说了,免得影响可读性)---- 资源压缩:- 纹理(格式,尺寸)- 减少 内存 包体- 模型(点面数量这个之前说 扒出来的资源,不好改,其实 *.obj, *.fbx 都可以放到 3D MAX 修改后重新导出即可,但是也要工作量) - 减少包体- 动画精度压缩,动画帧删减(可能会导致部分动作精度不足,会产生小量的抖动) - 减少 内存 包体- 烘焙(烘焙纹理精度降低,方向光光贴删除,一般需要近距离的才会用到,也可以减少用到烘焙的地方的纹理大小) - 减少 内存 包体---- 画质分档(低中高):- 分档规则:- 不同项目的分档规则都是不同的(CPU 通用算力、内存大小,Graphics API 的最高支持版本,GPU算力、显存大小,系统版本,如:Android 多少版本)- 具体要看使用到什么版本的功能(压缩格式,Graphics API 版本特性,对内存的需求量,等)- 在游戏第一次运行的时候,进去游戏前,就要给玩家设备定档- 在定档完毕自动帮玩家选择 低中高 对应的画质级别- 纹理:- 纹理默认 mipmap 级别 - 低中高:1/4 mipmap;1/2 或 1,看情况;1/1 原始 mipmap 值0; - 提升缓存命中率- 渲染分辨率:- 低中高(75%, 85%, 100%)- 减少 片段着色器压力,和 绘制 内存、显存、带宽的占用- 阴影:- 低中高:无;低质量,阴影距离小;原始质量,阴影距离原始值 - 减少:内存、显存 占用- 抗锯齿:- 低中高:无;无;x2;- 减少:内存、显存 占用- 后效:- 低中高:全无;删减部分;全部保留; - 减少:内存、显存 占用- Shader LOD:(但是这个会增加 shader 变体,看情况而选择是否需要优化,如果本身 shader 效果简陋、消耗能接受,就不需要处理这步)- 低中高:最大限度的删减 部分光照、或是逐片段挪到逐顶点光照 效果;删减部分 光照效果;保留所有效果- 分档后有什么优缺点:- 优点:- 让部分机型的兼容性、运行流畅性 提高- 缺点:- 工作量增加(美术资源、程序代码都要增加,这些都需要时间)- 包体额外增加,shaderlab LOD, model LOD, texture mipmap 的增加大概会让包体增加 1/3- 整体来说是利大于弊的(毕竟 硬性指标:兼容性、流畅性 摆在那里)一般低端机,可以再 15~24 FPS,中端机再 30 FPS,高端机要:30~60 FPS(极高的:60FPS+,我们没有极高画质,所以就算了)---- 缓存策略:- 现在发现 GUI 实例都没有删减,那么就会可能导致 内存占用巨大- 可以看下列方式来优化:- 按 LFU, FIO, LRU 都可以(未确定哪种)- FIFO(First In First out):先见先出,淘汰最先近来的页面,新进来的页面最迟被淘汰,完全符合队列。- LRU(Least recently used):最近最少使用,淘汰最近不使用的页面- LFU(Least frequently used): 最近使用次数最少, 淘汰使用次数最少的页面- 可以参考:三种常见的缓存过期策略LFU,FIFO,LRU说明 /weixin_42449534/article/details/100541192- 不单指 GUI 要这么做缓存策略,其他 缓存也是一样可以这样管理的---- 部分资源的 制作调整:- 现在的 shaderlab 变体方式:- 我们这个项目的所有 shader 都放在: Always Include Shaders 列表中,那么就有个问题,所有变体都会添加进去- 这种方式会导致 shader 变体不可优化,会在发包的时候,生成 并 添加所有的变体在 包体中(增加包体)- 运行是还会增加导致 shader 变体数量过多而 导致 内存占用变多(增加内存占用)- 需要优化的方式:- 将 母包需要的 shader 直接放 Always Include Shaders- 将 其余非母包资源 都应该独立在一个 shaders.ab AB 包中(如果项目体量巨大,还得 shader 资源分包加载)- 然后在程序运行后,初始化时加要先加载 shaders.ab 资源、---- GC 优化(内存垃圾回收)- (跳过)- 影响流畅性- 目前我们的 GC 间歇性卡顿也是有点严重的,用 profiler 分析即可知道- 本想使用 unity 的 increasement gc,但是发现这个就项目使用的是 unity 还没有这个功能- 代码大改就算了,目前时间不允许- 这个在平时制作时就需要比较严格的 code review 中可以避免大部分的 GC 问题,或时 后期项目 时间允许情况下,并且添加人力集中时间去优化才行---- 代码写法、算法问题(暂时不处理)- 特效(暂时不处理)- GUI 纹理图集的合理划分(暂时不处理)---各个部分优化时的建议每个部分不要等到全部的量处理完才去验证,而是要在每个具有 代表性的最简优化后,马上测试这样才能及时发现问题,将问题影响范围压缩到最小---另外在项目阶段接近尾部时,才做优化的话,尽量按项目需求来选择,而不是所有都要做,这个都需要时间前面制作有多 “快”(挖坑) 后续就需要多少 代价 来填补前面 挖的坑坑大到一定程度才考虑优化,那么无异于重新制作项目,所以平时在制作项目时,尽量不要以为快就是好要质量和速度中选一个能接受的,尤其现在大浪淘沙的游戏发展期更加要权衡好 速度 与 质量最后的节奏就是每个功能在开发的时候,都要让参与管理的伙伴了解,这里头开发的速度、质量的影响,让大家选一个合理的,能接受的节奏来开发这个经验,我在多个项目都有过这些教训,这对以后制作任何一个项目都是有帮助的

以上就给项目组的一些建议

然后我自己着手做了一些优化:

粗略记下好几点,便于后续自己查阅

Profiler Unity Profiler - Profiler overview Memory Take Sample - 可以快速定位及优化 99% 以上的 unity 资源问题 CPU 可以定位 70% 左右的 CSharp 源码级别的性能问题,因为 一些 黑箱 API 就无法优化GPU 不建议使用,使用其他的 Profiler 工具更佳 GPU 的抓帧可以参考我另一篇提及的工具:Application/Graphics Analyser - 应用程序、图形分析工具集 - 现在发现抓个帧都贼麻烦了,需要各种权限才可以抓

其中 unity 资源、性能问题基本上好几个大块:

纹理 - 可以使用脚本扫描输出日志,但是不精准,只能模糊的定位,如果提量不大的项目,可以使用 Unity Profiler -> Memory -> 左下角 Simple 改 Detail -> Take Sample: Editor,然后一个个过目即可

格式

对应平台选择不同的格式,可以参考 unity 官方文档:Recommended, default, and supported texture compression formats, by platform

尺寸

UI 的还注意是否可以 3/4/6/9 宫格来减少尺寸部分特效为了效果制作了很大尺寸的,就很不划算了,效果 性能要权衡好,有些时候,把 尺寸压小一些,特学同学也时可以接受的范围,就放心的压缩特效纹理尺寸,不然 overdraw 很严重

Mipmap - 当指定使用 最高清的 Mipmap 层级时,可以有效的减少内存、显存,提升缓存命中率,可以通过设置:QualitySettings.masterTextureLimit来达到效果,另外 UI 纹理不要开启 mipmap,可以节省包体,内存,显存

另外 如果 3D 场景的镜头不会有镜头远景的效果(比如:正交投影,不会有大小控制;或是透视投影下的镜头不会前后移动),那么同样不需要 Mipmap

烘焙贴图

尺寸 及 效果的权衡部分场景如果不是需要近距离的观察物体的,可以不需要 DirectionMode,这样可以减少 dir 贴图

冗余资源

可以使用 UPR Asset Checker 来分析是否又冗余,一般判断原理是:字节大小、尺寸,如果都相同,很大程度上是同一个纹理的图像,再不够精准也可以使用图像像素的数据来做 MD5 判断,Asset Checker 都帮我们做了这些分析

模型

Readable/Writeable - 一般 CPU 层不需要读写网格数据的话,注意不要开启 R/W - 低版本的 unity 甚至默认是开启的,如果开启了,会再 CPU 主存,GPU 显存都会占用各一份Optimize Mesh - 尽量开启(High, Medium, Low, Off),减少不必要的顶点 和 索引缓存的数据,减少包体和内存、显存BlendShape - 没有表情动画的都去掉Cameras - 一般去掉Lights - 一般去掉Visibility - 一般去掉Animation Compress - 一般OptimizeGenerate Lightmap UVs - 如果不参与烘焙的不要勾选Normals, Tangents - 法线,切线的确定不需要的都可以去掉,如:UI 上的特效网格

模型动画

适当的压缩 关键帧,抽帧,都可以减少包体,内存、显存,可以参考:Unity - Mesh、Animation Compress of the Model - 模型的网格、动画压缩,减少包体大小

音频

下面的经验数值参考:《Unity性能优化》第壹节——静态资源优化(1)——Audio导入设置检查与优化音频在内存与CPU上的性能考量: 小于 200kb,使用 Decompress on load 再加载时,一次性解压到内存,节省 CPU 中途解压数据的消耗,但是,但内存会占用变大,比如:Vorbis 编码的音频,再解压到内存,内存会翻 10倍,具体参考官方文档说明:Audio Clip大于 200kb 时长超5秒,使用 Compress in Memory,一次性加载到内存的数据都是压缩过的,所以需要实时解压播放时长比较长的使用 Streaming 方式,可节省内存,因为是按流式加载,每播放一段音频缓存的数据,才从硬盘中读取,并解压,所以才比较省内存,但是,CPU会额外占用在另一个线程的处理:流式文件数据读取 + 内存解压 音频资源大小考量: 一般手游的话,采样率使用 22 MHZ (22050 Hz) 就够了,而不必使用 44MHZ (44100Hz),这样文件也会小很多ForceToMono:如果左右声道的数据都是一样的,但是又占用了双声道,那么内存必然浪费,这种音频,设置一下 ForceToMono 设置为单声道 即可,资源大小,与内存都可以减少;但是 这个音频的双声道是否一致,可以使用 UPR 的 Asset Checker 来分析,人耳来分析就太费时间了,也费耳朵

AA

可以通过 AA,查看抓帧的性能消耗最简单的在低端机(用过一台低端测试机:CPU: 1.4 GHz, 4 Cores, RAM: 1.8 GB, GRAM: 512 MB),测试 开启和 关闭 AA 后的性能对比是相当明显的

Physics

能用 SphereCollider,BoxCollider 等简单的 Collider 就不用 MeshCollider特别是动态对象的 Collider 也是非常消耗性能的,所以重点关注一下,动态对象的 Collider 是否足够简单

合批 (优先级从上往下)

合批这里 指 DC,也有 SetPassCall 的合批,具体理解区别可以参考我之前写的:Unity - DrawCall, Batch, SetPassCall区别能 instancing 尽量 instancing batching部分网格数据体量(点、面 数量)小的,材质一致,图集一致的,静态不动的物体,尽量 static batch 这部分会占用额外的内存,如果内存非常吃紧,就再考量考量 然后才是 dynamic batching 需要相同的材质一般用于顶点数小,顶点属性少的比较适合,如:粒子会消耗 CPU 计算,因为会申请 大的 VERTEX BUFFER,并将:positionOS -> positionWS,然后再设置到 VERTEX BUFFER 中的 POSITION

包体大小

上面多多少少会涉及到一些关于优化包体大小的点,其他的可以参考 unity 官方文档:Reducing the file size of your build

内存

Memory in Unity

没整的部分

AB 分类 - 没去整,因为惨不忍睹ShaderLab 变体删减 - 没去整,也是惨不忍睹,全都放 always includes 列表里(所以后面大部分的 shader lab 变体优化我都时直接针对单个 shader 直接减去不并要的变体,具体可以参考:Unity Shader - Built-in管线下优化 multi_compile_fwdbase、multi_compile_fog 变体)缓存机制 - 惨不忍睹,没什么机制,特别时 UI 模块 全都常驻内存

经过上面的操作,就删减了近 400~500 MB 的内存消耗

静态资源优化 - AssetPostprocessor

通常游戏资源中,占用 包体、内存 最大的部分都是:

纹理音频模型动画、网格

下面就 针对这 三中类型的资源来处理

Texture 压缩

在 AssetPostProcessor 中对纹理处理:

// jave.lin : 纹理压缩static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths){if (importedAssets.Length > 0){OnHandleTexImportProperties(importedAssets);}if (movedAssets.Length > 0){OnHandleTexImportProperties(importedAssets);}}void OnPreprocessTexture(){OnHandleTexImportProperties((TextureImporter)assetImporter);}public static void OnHandleTexImportProperties(string[] assetPaths){foreach (var assetPath in assetPaths){var importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;if (importer != null) OnHandleTexImportProperties(importer);}}public static void OnHandleTexImportProperties(TextureImporter importer){var assetPath = importer.assetPath;if (assetPath.StartsWith("Assets/xxxxx")){SetTexAndroidSetting(importer, TextureImporterFormat.ETC_RGB4Crunched, 50, 512);}else if (assetPath.StartsWith("Assets/xxxxx")){SetTexAndroidSetting(importer, TextureImporterFormat.ETC2_RGBA8Crunched, 50);}else if (assetPath.StartsWith("Assets/xxxxxxx")){SetTexAndroidSetting(importer, TextureImporterFormat.Automatic, 50, 512);}else if (assetPath.StartsWith("Assets/xxxxx")){SetTexAndroidSetting(importer, TextureImporterFormat.Automatic, 50, 512);}else if (assetPath.StartsWith("Assets/xxx/Scenes/xxx")){if (assetPath.Contains("comp_dir")){SetTexAndroidSetting(importer, TextureImporterFormat.ETC2_RGBA8, 50, 512);}else if (assetPath.Contains("comp_light")){SetTexAndroidSetting(importer, TextureImporterFormat.ETC_RGB4, 50, 512);}else if (assetPath.Contains("shadowmask")){SetTexAndroidSetting(importer, TextureImporterFormat.ARGB16, 50, 512);}}else if(assetPath.StartsWith("Assets/xxxx")){SetTexAndroidSetting(importer, TextureImporterFormat.Automatic, 50, 256);}}private static void SetTexAndroidSetting(TextureImporter importer, TextureImporterFormat format, int compressionQuality, int maxTexSize = -1, TextureImporterType? texType = null, bool? sRGB = null){try{TextureImporterPlatformSettings originalSettings = importer.GetPlatformTextureSettings("Android");var new_format = format == TextureImporterFormat.Automatic ? originalSettings.format : format;var new_maxTextureSize = maxTexSize == -1 ? originalSettings.maxTextureSize : maxTexSize;if (originalSettings.name != "Android" ||originalSettings.overridden != true ||(texType.HasValue && (importer.textureType != texType.Value)) ||originalSettings.format != new_format ||pressionQuality != compressionQuality ||originalSettings.maxTextureSize != new_maxTextureSize ||(sRGB.HasValue && (importer.sRGBTexture != sRGB.Value))){originalSettings.name = "Android";originalSettings.overridden = true;importer.textureType = texType.HasValue ? texType.Value : importer.textureType;originalSettings.format = new_format;pressionQuality = compressionQuality;originalSettings.maxTextureSize = new_maxTextureSize;importer.sRGBTexture = sRGB.HasValue ? sRGB.Value : importer.sRGBTexture;importer.SetPlatformTextureSettings(originalSettings);importer.SaveAndReimport();}}catch (System.Exception er){Debug.LogError(" SetTextureAndroidSetting Error : assetPath : " + importer.assetPath + "\n" + er);}}

Model 网格、动画 压缩

// jave.lin : 模型、动画压缩static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths){if (importedAssets.Length > 0){OnHandleModelImportProperties(importedAssets);}if (movedAssets.Length > 0){OnHandleModelImportProperties(importedAssets);}}public void OnPostprocessAnimation(GameObject go, AnimationClip clip){try{CompressAnimationClip(clip);}catch (System.Exception e){Debug.LogError("CompressAnimationClip Failed !!! animationPath :" + assetPath + "error: " + e);}}public static void CompressAnimationClip(AnimationClip clip, bool removeScaleAnima = true, string precision = "f4"){if (removeScaleAnima){var editorCurves = AnimationUtility.GetCurveBindings(clip);if (editorCurves != null){foreach (EditorCurveBinding editorCurve in editorCurves){string name = editorCurve.propertyName.ToLower();if (name.Contains("scale")){AnimationUtility.SetEditorCurve(clip, editorCurve, null);}}}}AnimationClipCurveData[] curves = AnimationUtility.GetAllCurves(clip);for (int ii = 0; ii < curves.Length; ++ii){AnimationClipCurveData curveDate = curves[ii];if (curveDate.curve == null || curveDate.curve.keys == null){continue;}Keyframe[] keyFrames = curveDate.curve.keys;for (int i = 0; i < keyFrames.Length; i++){Keyframe key = keyFrames[i];key.value = float.Parse(key.value.ToString(precision));key.inTangent = float.Parse(key.inTangent.ToString(precision));key.outTangent = float.Parse(key.outTangent.ToString(precision));keyFrames[i] = key;}curveDate.curve.keys = keyFrames;clip.SetCurve(curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve);}}public static void OnHandleModelImportProperties(string[] assetPaths){foreach (var assetPath in assetPaths){var importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;if (importer != null) OnHandleModelImportProperties(importer);}}public static void OnHandleModelImportProperties(ModelImporter importer){var assetPath = importer.assetPath;if (assetPath == "Assets/xxxx/xxxx.fbx"){OnHandleModelImporterProperties(importer, false, ModelImporterMeshCompression.Medium, null, ModelImporterAnimationType.None);}else if (assetPath.StartsWith("Assets/xxxxx/Mode")){OnHandleModelImporterProperties(importer, false, ModelImporterMeshCompression.High, null, ModelImporterAnimationType.None);}else if (assetPath.StartsWith("Assets/xxxx/Role")){OnHandleModelImporterProperties(importer, false, ModelImporterMeshCompression.High, ModelImporterIndexFormat.UInt16, ModelImporterAnimationCompression.Optimal);}}private static void OnHandleModelImporterProperties(ModelImporter importer,bool? readable = null,ModelImporterMeshCompression? meshCompression = null,ModelImporterIndexFormat? indexFormat = null,ModelImporterAnimationType? animaType = null,ModelImporterAnimationCompression? animaCompression = null){var changed = false;// jave.lin : model 属性if (meshCompression.HasValue && importer.meshCompression != meshCompression.Value){importer.meshCompression = meshCompression.Value;changed = true;}if (indexFormat.HasValue && importer.indexFormat != indexFormat.Value){importer.indexFormat = indexFormat.Value;changed = true;}if (readable.HasValue && importer.isReadable != readable.Value){importer.isReadable = readable.Value;changed = true;}if (importer.optimizeMesh != true){importer.optimizeMesh = true;changed = true;}if (importer.importBlendShapes != false){importer.importBlendShapes = false;changed = true;}if (importer.importVisibility != false){importer.importVisibility = false;changed = true;}if (importer.importCameras != false){importer.importCameras = false;changed = true;}if (importer.importLights != false){importer.importLights = false;changed = true;}if (animaType.HasValue && importer.animationType != animaType.Value){importer.animationType = animaType.Value;changed = true;}if (animaCompression.HasValue && importer.animationCompression != animaCompression.Value){importer.animationCompression = animaCompression.Value;changed = true;}if (changed){importer.SaveAndReimport();}}

音频压缩

// jave.lin : 音频压缩void OnPostprocessAudio(AudioClip clip){AudioImporter importer = (AudioImporter)assetImporter;OnHandleAndroidAudioImporterProperties(importer, clip);}public static void OnHandleAndroidAudioImporterProperties(AudioImporter importer, AudioClip clip){var changed = false;const string platform = "Android";var originalSettings = importer.GetOverrideSampleSettings(platform);if (pressionFormat != AudioCompressionFormat.Vorbis){// 手机端建议用 pressionFormat = AudioCompressionFormat.Vorbis;changed = true;}if (originalSettings.quality != 0.2f){// 压缩到的品质 .2F 就够了originalSettings.quality = 0.2f;changed = true;}if (originalSettings.sampleRateSetting != AudioSampleRateSetting.OverrideSampleRate){// 设置 采样率 的设置方式为:覆盖设置 的方式originalSettings.sampleRateSetting = AudioSampleRateSetting.OverrideSampleRate;changed = true;}if (originalSettings.sampleRateOverride != 22050){// 覆盖 采样率 的频率,采样频率越低,那使用数据量就越少,文件压缩就越小,手游一般 22050 就够了originalSettings.sampleRateOverride = 22050;changed = true;}// jave.lin : // 按照上面的优化建议,200 KB 以下的使用 DecompressOnLoad 就可以了// 然后我看了一下 AudioClip 里面没有文件原始大小的信息,只有描述// 然后我观察了一下多个文件,大概 44 MHz 的采样率下的文件大小为规律为:60 秒 约等于 1 MB// 所以 200 KB 左右的,大概在 12 秒时长左右// 1 mb ~~= 60 s// 200 kb ~~= 12 s// 大概小于 200 KB 的if (clip.length < 12){// 使用 DecompressOnLoad 即可if (originalSettings.loadType != AudioClipLoadType.DecompressOnLoad){originalSettings.loadType = AudioClipLoadType.DecompressOnLoad;changed = true;}// 可以预先加载if (importer.preloadAudioData != true){importer.preloadAudioData = true;changed = true;}}// 200 KB ~ 300 KB 范围内的else if (clip.length > 12 && clip.length < 20){// 使用 CompressedInMemoryif (originalSettings.loadType != pressedInMemory){originalSettings.loadType = pressedInMemory;changed = true;}// 也可以预加载if (importer.preloadAudioData != false){importer.preloadAudioData = false;changed = true;}}// 时长大约 20 秒的,使用 流式加载else if(clip.length >= 20){// 使用 Streamingif (originalSettings.loadType != AudioClipLoadType.Streaming){originalSettings.loadType = AudioClipLoadType.Streaming;changed = true;}// 不要预加载if (importer.preloadAudioData != false){importer.preloadAudioData = false;changed = true;}}if (changed){importer.SetOverrideSampleSettings("Android", originalSettings);}// 强制设置为 单声道,减去 双声道的数据占用(根据项目实际情况确定是否需要有双声道的)if (importer.forceToMono != true){importer.forceToMono = true;changed = true;}if (changed){importer.SaveAndReimport();}}

可以将原来 321个 音频文件(含部分 BGM,大部分的 effect 音频)

从原来的:Imported Size : 12.0 MB压缩到4.8 MB,如下图:

内存优化,在简单的处理了资源部分,大部分是纹理资源:尺寸、格式问题,冗余纹理问题

再加上少部分的,网格压缩、音频压缩

从原来登录后占用:700 MB+ 减少至:300 MB

还没有做 程序上的缓存优化(这个项目内存对象很多都是常驻方式,实在是太无语)

经过对纹理、模型、音效、(Shader 变体就不整了,时间来不及)、部分冗余资源的删减

最后包体减少了近 200 MB(而且还是有部分冗余资源没有删除干净,这些不好处理,因为部分资源是动态加载的,可能配置在 excel表、写在lua 脚本、写在cs 脚本中,如果删除干净了,估计只有 350~450 MB 左右的 APK)

后面再经过我和策划将一些冗余资源再删除一遍

然后将一些未 9宫格化得,统统处理一遍

又减少了近 100MB

包体:

最后我将 项目的包体 从最开始的780 MB压缩到了350 MB(减去了一倍多的包体亮,如果做成分包加载还可以将母包制作的更小)

内存:

之前登录到主城后,仅仅纹理占用的内存占用了:800MB±

经过静态资源的优化后,仅仅纹理占用的内存只有 100 MB± (还有很多优化空间,比如:还是由常驻的对象太多,-_-!, 但是这些涉及太多的业务多级代码 和 底层资源管理、加载的代码调整,然后主要负责的人员又离职了;所以就没去整了,因为 快要上线了,给 的优化 时间是有限的,因此权衡下来,还是不要大改;让我想起一张图:能看着运行,就不要修改,就算想水管交错的,但是刚刚好 BUG 之间互补抵消了,-_-!)

纹理的优化经验

尺寸

本身图像颜色很糊的

可以压缩小一些也不会又太多影响

上面的烟雾 256x256,还带有噪点问题

可以使用 photoshop ,先高斯模糊,再将尺寸压缩到 128x128,这样不会又噪点,效果也不会丢失,包体、内存都会更小

总结:

图像本身的糊的,并有明显的噪点、和色阶的问题,都可以高斯模糊一下,再压缩

这些是制作特效纹理的一些优化经验

通道

类似这种,黑色底的贴图

导出 jpg/jpeg 就好了(只要 RGB 三个通道,不需要 A(alpha) 通道)

这样程序这边就可以写脚本统一处理 纹理压缩格式

处理方式未:

jpg/jpeg 等用 RGB 三通道的数据png/tga 等等 RGBA 四通道的数据

这样内存、包体都可以更小

另外,静态资源的优化,在 unity 中有多种方式来处理(多种工具),本质一样的,都是对 assets importer 的设置

AssetPostprocessor - 需要有专人对资源维护(目录划分,资源规格,等)AssetGraph - unity 后续出的插件,就是基于 PlayableGraph 类 开发的插件,也是比较美观,推荐可以使用这种方法,我暂时没去使用过Presets - 的配置批量设置,暂时没使用过

文章是以前写的,后续(/11/30)在别的公司,别的项目也有类似的静态资源压缩处理参考如下:

优化前后对比:

效果图:优化前的细节是比较高的,优化后细节丢失的部分可以接受

优化前:25 MB

优化后:1.704MB

压缩率:6.816%

压缩比:93.186%

发布出来的包资源再次分析

这里以 apk (android) 为例,分析 apk 内的一些资源大小的步骤如下:

将 xxx.apk 的后缀修改为:xxx.zip解压到 xxx 文件夹再 打开 assets 目录,并再 assets 目录下过滤:*.* 的通配符然后使用 大小 的降序来排序

然后从大到小的资源包分析里面的使用是否有不合规格的资源

使用 AssetStudioGUI 来分析,可以自行去下载:AssetStudio

如下图:

如何工具快速定位静态资源问题

虽然上述的方法可以手动定位热点问题

如果手动定位的热点问题处理得差不多了

那么可以使用工具来分析定位,以免 漏网之鱼

一般市面上 unity 引擎比较主流的有:UWAUPR

两个工具个有千秋

比如下面是 UPR 为例:UPR/Download,打开网页,下载 UPR Asset Checker

如何使用 UPR Asset Checker 可以查看 UPR 文档:UPR/AssetChecker/Docs

使用根据得到静态资源的分析报告后,即可逐条酌情处理

运行时的品质调整

上面基本是基于静态资源的优化处理

运行时的画质调整,可以参考之前写的一篇:Unity - 画质设置

References

优化移动游戏性能 | 来自Unity顶级工程师的性能分析、内存与代码架构小贴士Unity移动端游戏性能优化 | 来自Unity顶级工程师的图形与资源相关建议Unity游戏内存分布概览看懂 Unity Memory Profiler理解Unity中的优化(一):性能分析理解Unity中的优化(二):内存Profiling with Instruments - 在 mac 下 profilerAnalysis - .3 Memory in UnityProfiler overviewProfiling toolsLog filesUnderstanding optimization in UnityAsset loading metrics Android GPU Inspector フレーム プロファイラによりグラフィックスをオプティマイズする

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。