如何优化资源文件过多导致的安装变慢?

如何优化资源文件过多导致的安装变慢?

本期我们聚集了这些话题:安卓平台下资源文件过多导致安装变慢、ShaderVariants变体打包AssetBundle丢失、Graphcis API切到Auto时特效异常...


这是第129篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间10分钟,认真读完必有收获。

UWA 问答社区:answer.uwa4d.com
UWA QQ群:793972859(原群已满员)


资源管理

Q1:我们项目中资源文件有16000多个,导致在安卓手机上安装很慢。为此,我们分析了下其他游戏,发现他们会把所有的文件整合到一个大的文件,然后读取。

我们模仿写了个简单的文件整合,把所有的文件写入到一个大文件,然后通过记录的Pos和Length,Seek读取数据,发现很卡。想请教下,针对这种文件数量过多的,有什么好的解决方案么?

我们的资源大部分对应为Bundle,打成大文件,记录文件头和长度是可以的,但这里要注意两点:
1、不要释放句柄,避免重复开销;
2、public static AssetBundle LoadFromFile(string path, uint crc, ulong offset);Unity提供了AssetBundle读取时offset的接口,可以使用;
3、自维护表头文件提前读取,建立映射关系;
4、支持多文件表头映射,这样可通过分包提高iO速度。
感谢郑骁f@UWA问答社区提供了回答

不知道题主说的安装过程很慢,是apk的安装过程,还是安装好之后第一次启动游戏的资源拷贝过程?
我们游戏的AssetBundle文件的数量大约也有1万多个,也算是一个量级吧。我们在一些安卓设备上apk的安装时间是不快,但是因为这个只是给我们测试的时候带来一些时间上的影响,没有纳入玩家体验的统计上,所以目前并不知道具体花费多久。在安装好apk之后,我们没有选择解压,所以这块是没有时间消耗的。
整合到一个大文件,是一种常见的做法,不过我个人感觉是如果可以在C++底层直接做这件事情是最好的,相当于一套虚拟文件系统,而在C#层做这件事情,我觉得会比较绕,从而可能导致很多无意义的内存占用以及CPU消耗。一方面个人不太建议这来做,另外一方面题主也可以在Profiler里看下具体这块的消耗是在哪里,也许有提升和改进的空间。
如果想用一个大文件,基于offset的读取又没有发现优化空间,也可以考虑压缩成一块/几块大的文件,然后第一次运行的时候进行解压,不过感觉还不如安装时间稍微长一些。
另外,题主考虑下目前的包体有多大,尽量进行一些合理的合并,在控制Patch大小的情况下,也可以减少AssetBundle的数量。如果是1GB包体,接近2万个文件,平均每一个也就50KB,算很小了,合并到1万个AssetBundle文件左右,也就是100KB的均值,都是可以接受的。
当然,也感谢题主提了这样一个问题,让我注意到apk的安装时长和文件数量可能存在关系。
感谢贾伟昊@UWA问答社区提供了该问答

该问答来自UWA问答社区,欢迎转至社区交流:
https://answer.uwa4d.com/question/5b97b348670c1a61c64d6d65


Shader

Q2:我们项目中的Shader使用了Shader_feature,会产生多个变体。在编辑器模式下面运行一切正常。但是打包到Android上面,会出现Shader丢失的情况。 推测原因是,当AssetBundle打包的时候,我把Shader独立设置了名字,模型在收集的时候,没有管Material上面是否有宏的设置,导致这个Shader本身是被引用到了,但是模型却用的是变体导致丢失。

也尝试过其他办法,比如把Shader放入Always Included Shaders中,这样会导致编译漫长,包体变大,因为Shader所有的变体都会独立被编译一次放入apk包中,这样确实能解决问题。其次,也尝试过使用ShaderVariantCollection,版本是Unity 5.6.4p4,感觉没有效果,我在Collection中设置了需要的参数,然后将它和Shader一起打包,上真机测试后Shader还是会丢失,感觉就是完全没有生效。

最后尝试了一种办法,自己收集使用这个变体Shader的所有Material,然后把所有Material和Shader打成一个AssetBundle,这样就不会丢失,但是又有一个问题,如果我一个Material需要设置一个Keyword,需要一个新的变体Shader,如果当前打包的Material中没有产生这个Shader,那么同理也会丢失。
希望遇到过这个问题,或者有解决方案的朋友不吝赐教。

UWA:关于ShaderVariantCollection,这个问题里有一些讨论:
https://answer.uwa4d.com/question/5af11b260e95a527a7a81cf0
简单说,就是高版本(Unity 2018)能正常使用,其他情况下使用Dummy Material比较稳定。
至于“如果我一个Material需要设置一个Keyword,需要一个新的变体Shader,如果当前打包的Material中没有产生这个Shader,那么同理的也会出现丢失”这个问题,似乎也没有什么好办法,因为这种方式的缺点就是维护起来比较麻烦。
该回答由UWA提供

ShaderVariantCollection不仅可以自动收集,也可以手动设置Keyword,所以,首先要确认所有变体和美术使用到的所有变体,建议让美术使用前就限制好,避免后续发生问题。另外进行Shader代码分离,Lightmap相关Shader和角色分离,无Light和有Light分离,1个Light和2个Light分离,尽量通过代码减少变种,限制美术使用Shader的范围。
感谢郑骁@UWA问答社区提供了回答

该问答来自UWA问答社区,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5ba08012f31d7a33f3469a37


OpenGL

Q3:之前项目Android Graphics API一直是选择的强制OpenGL ES 2.0,近期想做优化就试着选择成了Auto。切完后,在大部分手机上都跑的正常,测试后性能也稍有提升,但现在遇到一个奇怪的现象。现象如下:

1)在OPPO R9, MEIZU m2e 两台手机上会发现特效会出现粒子乱闪、粒子被放大、粒子消失等多种奇怪现象,而且只有特效有问题,其他的画面表现都正常;

2)同样的资源,切换回之前强制的OpenGL ES 2.0下就是好的;

3)出问题的特效都是放到UI上的特效,包括纯UI面板特效,以及用RenderTexture方式实现的UI模型上绑的特效,同样的模型特效在场景上播放是正常的。

我们用的是Unity 5.3.6版本,想问下有没有人遇到过类似问题,或者哪方面有可能导致这个问题。

https://en.wikipedia.org/wiki/OpenGL_ES
OES_vertex_half_float是OpenGL ES2.0的一个扩展
感谢凯奥斯@UWA问答社区提供了回答

现在很多机型包括高端机都出现了精度问题,建议在可以使用float的情况下,使用float再逐步优化。
感谢郑晓@UWA问答社区提供了回答

结下帖,经过认真思考反复观察,终于发现了出问题特效的共同点:位置出现了问题,然后跑去看特效Shader顶点计算部分,发现大部分特效顶点的属性都只给了half精度,再对比了下场景特效和UI特效的区别,发现UI特效的世界坐标都比场景特效大很多,于是第一直觉是half精度在这些手机上出问题了,改half为float,重新打包验证,问题解决。
感谢题主提供了回答

该回答来自UWA问答社区,欢迎大家转至社区进一步交流:
https://answer.uwa4d.com/question/5b972198670c1a61c64d6d44


Shader

Q4:Shader中,实际计算未使用的参数,是否会进入GPU,假如在一个如下Shader中。Property里面定义了一个贴图_MainTexture,并且有赋值。然后SubShader里面也定义了参数_MainTexture,但是实际运算流程并没有使用这个贴图。

是不是CPU仍然会将该贴图推入显存,造成CPU和显存消耗?如果保持Property存在,但是不在SubShader中定义Sampler2D,是不是就可以避开这个消耗?

Shader "Test/DummyTex"
{
    Properties
    {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        
        Pass {  
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata_t {
                float4 vertex : POSITION;
            };

            struct v2f {
                float4 vertex : SV_POSITION;
            };

            fixed4 _Color;
            sampler2D _MainTex;
                
            v2f vert (appdata_t v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
                
            fixed4 frag (v2f i) : COLOR
            {
                fixed4 col = _Color;
                return col;
            }
            ENDCG
        }
    }
}

UWA:题主可以尝试将Unity的Shader编译成OpenGL或者DX的Shader看一看,我们以OpenGL ES 2.0为例。点击下Shader的Inspector面板里面的Compile and Show Code按钮,就可以显示了。我把题主给出的Shader编译GLES 2.0版本如下:

   //////////////////////////////////
  //                              //
  //      Compiled programs       //
  //                              //
  //////////////////////////////////
//////////////////////////////////////////////////////
No keywords set in this variant.
-- Vertex shader for "gles":
Shader Disassembly:
#version 100

#ifdef VERTEX
attribute vec4 _glesVertex;
uniform highp mat4 unity_ObjectToWorld;
uniform highp mat4 unity_MatrixVP;
void main ()
{
  highp vec4 tmpvar_1;
  tmpvar_1.w = 1.0;
  tmpvar_1.xyz = _glesVertex.xyz;
  gl_Position = (unity_MatrixVP * (unity_ObjectToWorld * tmpvar_1));
}


#endif
#ifdef FRAGMENT
uniform lowp vec4 _Color;
void main ()
{
  gl_FragData[0] = _Color;
}


#endif

可以看到由于Unity的Shader代码中没有使用纹理采样,最终生成的GLSL也没有纹理采样的变量(被优化掉了)。所以在编译、使用这个GLSL代码的时候也无需给它指定一个纹理单元的变量。不会有纹理内存的开销。
另外,纹理在GPU中显存开销取决于有没有Shader使用它,并不是所有Shader使用纹理都会造成新的纹理内存开销。例如:ShaderA使用纹理A,之后ShaderB也使用了纹理A,那么纹理A并不需要上传到GPU两次,而是只需要一次就可以了。

该回答由UWA提供,欢迎大家转至社区进一步交流:
https://answer.uwa4d.com/question/5b9b55e5670c1a61c64d6dce


资源管理

Q5:请问Unity3D引擎运行时,如果我们使用的是采用了某个压缩格式的纹理,则纹理是在载入时解压并按照位图的RGBA32位的格式保存在内存,还是在内存中也是压缩格式的数据,并在提交渲染时由GPU自行解压并采样纹素。为何经常看到文章说GPU硬件支持某些图片或者视频流的压缩格式呢?这个处理流程大概是什么样的?

其实这是两个概念,图片压缩和纹理压缩(术语可能使用的不准确,请大神指正)。jpg和png这种是图片压缩,ETC、PVRTC、ASTC这种是纹理压缩。图片文件从硬盘读取到内存,解压缩成为纹理,再传给GPU,GPU再进行解压缩变成具体的RGB色彩值。如果没有开Read&Write,纹理会从内存中卸载掉。
感谢凯奥斯@UWA问答社区提供了回答

该问答来自UWA问答社区,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5b9c7a20f31d7a33f34699e9

今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
官方技术QQ群:793972859(原群已满员)