业务背景
AnnieX作为字节一方游戏统一容器,服务字节内部电商、直播、UG等跨端场景业务。在字节一方游戏互动场景,有大量的一方游戏业务对容器有特定的流量、端能力和游戏优化的诉求。因此我们不断深入互动游戏业务特点,为字节游戏提供完善游戏端能力和流量运营能力,同时提供游戏互动场景极致优化能力,提升游戏业务的整体性能。
养羊赚钱
种树赚钱
萌宠旅行家
新周中/周末
今年是抖音春节活动的第7年,我们跨端容器已经具备丰富的端能力的同时,也提供了一些对于跨端容器常规的一些优化手段,例如:资源预加载、网络预请求等手段,并且在往年春节活动中取得了不错的优化效果。而今年,我们希望能够基于游戏场景,逐渐深挖游戏互动场景的特点,为春节等游戏场景去提供更加丰富和专业的游戏优化能力。
春节-神龙寻宝
春节-守卫现金
春节-摇福签
春节-神龙探宝
互动套件介绍
因此我们基于24年抖音春节活动,在容器侧提出了互动套件的建设,希望为游戏互动场景提供优化解决方案。24年春节我们完成游戏引擎预热和游戏资产公共离线库的相关优化,在24年春节得到了不错的优化效果;同时我们完成了游戏资源(解)压缩的探索和更高效的物理引擎探索,希望能够在25年春节完成压缩和物理引擎的落地,从而丰富游戏互动玩法的同时,提升游戏的整体性能。
资产离线公共库是一个重要的资源管理工具,管理了游戏互动业务所需要的游戏引擎资源。通过对游戏主包拆包和引擎资源的统一管理,资产离线公共库能够有效地帮助降低游戏包的大小,从而提高游戏的运行效率。此外,资产离线公共库还能够通过复用本地离线公共库的引擎版本,进一步降低游戏包下载的整体耗时和带宽成本。
引擎预热是一种提供了与游戏引擎优化相关的能力的技术。其原理是在游戏启动过程中,对游戏引擎进行预热,以便游戏逻辑执行过程中更快地加载游戏引擎包,从而提高游戏加载速度。通过提前准备游戏引擎,在游戏启动时,就可以更快地进入游戏世界,减少等待时间。这种技术可以为玩家带来更好的游戏体验。
资源的压缩与解压缩可以极大地提高资源利用率,有效降低资源包体积和网络加载时长。资源的压缩处理是在引擎编辑器的打包构建阶段做的资源预处理,而资源的解压缩则被放在了互动容器中,在游戏载入过程中实时进行处理。
物理引擎,目前前端游戏开发者主要依赖js或者wasm版本的物理引擎,在游戏性能和体验上存在较大的优化空间,因此我们探索在容器侧提供更高效的物理引擎方案,从而为游戏开发者和设计师提供更好的物理引擎解决方案,丰富游戏互动玩法体验。
本文主要介绍24年在春节场景落地的引擎预热和资产公共离线库相关的优化手段。同时介绍、探讨我们游戏资源(解)压缩方案的探索和在探索中遇到的问题。
名词解释
引擎预热&资产离线公共库
业务痛点
基于游戏业务已有的数据进行分析,我们发现游戏互动场景有以下2个特点:
- 依赖游戏引擎:在游戏中,游戏互动场景的创建和运行需要依赖于游戏引擎的加载和初始化逻辑。这个过程会消耗大约200ms~300ms的时间,这可能会对游戏的性能产生一定的影响。因此,为了提高游戏的响应速度和流畅度,游戏开发者需要尽可能地优化引擎加载和初始化逻辑的执行效率。
- 游戏加载耗时和用户异常流失率呈正比:启动性能对于游戏的用户体验至关重要,因为如果游戏启动没有优化好,用户需要等待很长时间才能看到游戏画面。这样很可能会导致许多用户在游戏加载完成之前就退出游戏。根据已有的游戏数据分析,游戏加载时间在1.5秒后,每0.5秒左右就会有4%左右的用户异常流失。因此,游戏开发者需要高度重视游戏启动性能的优化,以确保游戏能够快速启动,让用户尽快进入游戏世界。
因此我们针对游戏互动场景完成了引擎预热的方案,整体在24年春节场景落地,双端取得了不错的加载收益。
整体思路
在优化之前,在容器加载游戏主包完成之后,才会开始渲染游戏首屏页面和执行游戏业务逻辑。当游戏的业务逻辑在执行的过程中,必须等待容器完成游戏引擎资源的加载和初始化,这部分在线上存在一定的耗时。因此,为了解决这个问题,我们在容器侧和前端工具链完成了一系列改造,以提高游戏页面的加载速度。
具体地来说我们在容器打开游戏页面路由阶段,就完成了游戏引擎的预加载,并在跨端框架完成初始化之后,充分利用JS线程空闲的时间,完成游戏引擎的预热,并将预热好的实例挂载在全局对象中,这样当游戏页面真正执行的时候,就能直接通过全局对象中的获取到游戏引擎实例,从而完成后续的游戏业务逻辑。
技术细节
开发工具链改造
前端工具链方面,在构建工具中接入speedy-split-chunks插件,在游戏打包的过程中,将游戏引擎包从游戏主包中拆分出去,并将引擎库发布到静态资源分发平台公共离线库下;封装split-engine-adapter组件,完成引擎预热的前端代码插桩,并在引擎预热时,提供兜底逻辑,保障业务的稳定性。在剔除游戏引擎包同时,并将剔除出来的引擎包和对应的meta文件部署到静态资源分发平台公共离线库,通过meta文件来维护离线公共库的游戏引擎资源版本,供多个游戏互动业务使用,从而降低带宽成本。
另一方面,我们的split-engine-adapter插件可以在请求加载引擎资源文件时,完成对requireModule函数的hook。在这个过程中,如果发现全局对象上不存在的引擎实例,那么就会通过requireModule请求拆包引擎包作为兜底措施。通过这种方式,可以避免因引擎实例不存在而导致的错误,从而提高系统的稳定性和可靠性。
预热游戏引擎
客户端通过前端拆包的meta文件,进行公共库离线库资源版本管理。当客户端页面schema中打开引擎预热的能力时,客户端会在页面路由时,根据meta查找对应的引擎版本,提前去预加载游戏引擎。由于离线公共库存在客户端本地,为了防止替换客户端本地引擎JS文件,整体加载过程中会对游戏引擎资源进行验签,从而达到避免JS注入攻击的风险。同时,在跨端容器创建的过程中,将游戏引擎文件在JS线程进行预热,最终将游戏引擎实例挂载到全局对象上,以确保游戏引擎的正常运行。
当前端业务逻辑执行时,可以通过全局对象访问游戏引擎实例。如果引擎预热失败,前端业务会通过兜底的逻辑,请求游戏引擎资源进行使用,从而提升整体方案的稳定性和可靠性。
业务收益
在游戏互动场景中,AnnieX互动容器通过提供引擎预热和依赖资产公共离线库托管游戏引擎的方案,实现了游戏引擎的快速加载和初始化,从而提升了游戏业务的整体性能。这一方案成功地帮助完成了24年抖音春节活动的落地,游戏主包大小降低了28.05%。通过复用公共离线包引擎资源,节约了数万元网络带宽的成本。
在游戏加载耗时方面,双端在不同游戏的表现中存在200ms~500ms左右的优化幅度。由于iOS整体性能更佳,因此优化幅度整体上相较于Android用户优化幅度更大。在Android端中,PCT90有32.12%的优化幅度,PCT50有20.75%的优化幅度;在iOS端中,PCT90有47.24%,PCT50有33.89%的优化幅度。
资源压缩
业务痛点
3D资源往往文件体积较大,在需要网络传输的场景,对这些资源的压缩通常能够有效提升资源的加载速度,节约网络流量传输成本。
目前,常用的3D资源文件格式主要有glTF、fbx、obj等,这些资源通常包含了3D资源的几何体、骨骼、动画、材质等等所有数据,因此围绕着24年抖音春节活动,我们开始了基于游戏3D资源的压缩和解压缩的探索和调研。
整体思路
这里主要介绍AnnieX互动容器对于几何体和动画数据的压缩算法及其具体实现方式,分别是:量化(Quantization)、稀疏访问器(Sparse Accessor)和网格优化(meshopt)。
技术细节
几何体的压缩与解压缩
在SAR Creator中,几何体(Geometry)除了包含了顶点的坐标(Position)、纹理坐标(UV)、法向量(Normal)、切向量(Tangent)、颜色(Color)等数据之外,还包含了组成三角形的顶点索引(Index)、骨骼(Skeleton)和混合变形(Blend Shape)等数据。
由于这些不同的数据有着不同的格式和特性,所以往往需要分别应用不同的压缩格式,用以在尽可能保证数据精度的同时尽量提高压缩效率。
这里介绍几种SAR Creator中使用到的压缩算法:量化、稀疏访问器和meshopt。
量化(Quantization)
量化是一种常用的简单且高效的资源压缩方式,通常用于顶点数据的压缩,是一种有损压缩。
量化是将32位浮点值转换为16位或8位的整数进行存储的过程,可以达到50%甚至25%的压缩率。
转换公式
详情
由上述转换说明易知,量化是一个有损的压缩,其压缩和解压缩均只需要对所有原始数据做当且仅当一次乘法/除法运算,算法复杂度为O(n),开销较低。
量化对原始浮点数的范围有一个前提要求,其范围必须在0到1之间(可转换到无符号整数)或-1到1之间(可转换到有符号整数)。当原始浮点数的值不在这个范围内的话,需要预先做一次归一化处理,使其落在需要的范围内,才能继续进行量化处理。
本质上,量化是建立了一个浮点数和整数的对应关系,整数的存储位数越大,对应的浮点数精度越高。理论上可以实现任意位数的整数跟浮点数的转换。
在SAR Creator中,因为不同的数据对于精度的要求不同,故在实际应用时对于不同类型的数据使用了不同位数的整数进行存储,具体如下:
- position:14
- uv:12
- normal:10
- tangent:12
- color:8
- blend shape:8
以上不同类型数据的存储位数的配置选择参考自业界经验,可以在尽量保证数据精度的同时提高压缩率。在对精度要求较高的情况下,可以对该配置进行调整优化,一般最大值不会超过16位。
稀疏访问器(Sparse Accessor)
这里的稀疏访问器来自于glTF规范中的稀疏访问器(Sparse Accessor),特别适合用来存储混合变形(BlendShape,以下简称BS)数据,因为BS数据中往往存在较多的“0”。
在glTF规范中,Sparse Accessor会把一个较大的vector2/3/4的数组(Buffer),拆分成3个部分:非0值数组(Values Array)、索引下标数组(Indices Array)和原始长度值(Count)。通过这样仅存储非0值及其下标的方式,结合一个原始长度,就可以很方便地还原出原始数据。
在SAR Creator中会基于glTF的Sparse Accessor的格式再进一步做优化调整,仅会存储非0的vector2/3/4的非0通道的数据,而不是glTF规范中必须是Vector2/3/4中所有通道的数据均为0才不去存储,这样可以进一步减少序列化后存储数据的文件体积大小。同时,由于SAR的AssetBundle格式对于数组存储采用了特殊的方式,所以这里还可以少存一个Int32的Count值。
转换格式
转换格式示意图如下:
由上图可知,稀疏访问器是无损压缩,且其压缩和解压缩的流程简单,算法复杂度为O(n),但实际的解压缩过程并不需要对所有的数据进行处理,只需要创建跟原先相同大小的buffer,按下标将所有的非零值填进去即可,故而开销很低。
详情
另外,由于稀疏访问器仅在当BS数据的0值较多时,才会有较高的压缩效率,否则反而会降低压缩效率。SAR Creator默认会自动判断应用了稀疏访问器后是否会减少资源文件体积,来决定是否采用稀疏访问器,不需要手动开启。
附:真实业务(虚拟形象)中的glTF文件为例,仅采用Sparse Accessor能减少53%左右的文件体积,如果采用Sar的Asset Bundle中优化后的数据格式,可以减少78%左右的文件体积。
值得一提的是,稀疏访问器可以跟量化一起叠加同时应用在几何体数据上,两者互不影响,互不冲突,最终的精度损失仅为量化的损失。
网格优化(meshopt)
meshopt是glTF规范中的一个扩展:EXT_meshopt_compression,该扩展可以对glTF文件中的几何体和动画数据进行优化与压缩。在SAR Creator中,几乎全量移植了这个扩展的具体实现,使得SAR Creator的Asset Bundle中的几何体和动画数据同样支持了meshopt压缩。
由于meshopt压缩和解压缩的算法较为复杂,SAR Creator在实现时应用了wasm的方式(同时用asm.js做兜底),这里使用了开源项目meshoptimizer,它提供了对Mesh进行优化、压缩相关算法的C/C++接口,也可以编译为wasm使用。
压缩流程
meshopt在压缩前需要先优化顶点和索引数据,用于提高压缩效率,同时也可以提高解压缩后的渲染效率(GPU缓存)。meshopt提出了三种不同的模式(mode:attribute、triangles、indices)的和三种不同的过滤器(filter:octahedral、quaternion、exponential),针对不同的数据类型应用不同的模式和过滤器,可以最大化压缩率。
对于几何体的meshopt压缩,SAR Creator实现时的具体流程如下:
- 优化索引顺序
- 优化索引顺序,可以最大限度地利用附近的顶点数据,从而尽量复用在GPU硬件中的缓存。
- 优化顶点顺序
顶点顺序应按照顶点在索引数据中出现的顺序进行顺序排列,以而可以最大化索引数据的压缩率。
需要注意的是:有时候更高的压缩率反而会降低渲染效率,需要取好平衡点。
- 压缩顶点数据
- 顶点数据需要先进行量化(quantized),才能进行后续的压缩处理;
- 量化后的顶点数据需要用attributes模式来进行二进制比特流存储;
- 对于normal/tangent的数据,使用octahedral的filter可以进一步提高压缩率,对于position或其他数据,可以使用exponential的filter进行处理;
- 压缩索引数据
索引数据用indices模式进行存储,也可以用triangles模式进行存储;
其中,triangles模式仅支持三角形列表的存储,indices模式可以支持其他拓扑类型的索引数据存储;
压缩BS数据
BS数据可以完全当作顶点数据来进行处理,也需要先进行量化(quantized),之后再以attributes模式进行二进制比特流存储;
BS数据因为是增量(relative/delta)的值,故也可以选用比顶点数据在量化后更小的比特位来进行存储;
解压缩流程
对于几何体的meshopt解压缩,则需要反过来进行,互动容器中实现的具体流程如下
- 解压缩顶点数据
- 按attributes模式进行解压缩;
- 对于采用了对应filter的数据,还需要再进行filter的解压缩;
- (可选)如果有必要,在进行反量化转换(de-quantized)处理;
- 解压缩索引数据
直接按压缩时采用的indices或triangles模式进行解压缩即可;
解压缩BS数据
同顶点数据的解压,不再赘述;
详情
对于几何体数据应用meshopt压缩,往往可以带来10%-20%的压缩率,可以极大减少资源体积。但由于其复杂度较高,需要使用wasm加速压缩和解压缩的过程。
对于不支持wasm的环境,SAR Creator使用了asm.js作为兜底方案。实际测试结果显示,asm.js会比wasm慢2-4倍。在后续版本的SAR中,可能会直接使用C++的方案加速运行时对meshopt压缩后的资源进行解压缩,加速资源加载和解析的整个流程。
另:SAR Creator对于几何体数据的meshopt压缩实现,目前仍在feature分支,未经过全面测试,仅上线了对于动画数据的meshopt压缩。
动画的压缩与解压缩
动画(Animation)数据是由一系列的关键帧(keyframe)组成的。所以动画数据是由两部分组成的:关键帧的时间数组(times)和关键帧的值数组(values)。
网格优化(meshopt)
SAR Creator中对于动画(Animation)数据的压缩,也是移植自上述meshopt扩展。
压缩流程
对于动画数据的meshopt压缩,SAR Creator实现时的具体流程如下:
- 对动画数据进行重采样
- 对动画数据的压缩,最有收益的是进行重采样(resample),这样可以减少存储的关键帧数量,进而减少动画数据的文件存储体积;
- 具体实现使用开源库:keyframe-resample-wasm,它提供了WebAssembly版本的重采样方式,会比纯js的版本快一些,而且这个库的重采样过程是无损的。
- 压缩关键帧的times数据
如果times数据是均匀的,仅使用attribute模式而不使用任何过滤器已经能取得比较高的压缩率了;
为了保证最终的动画不变形,一般不建议对关键帧的times数据进行filter处理,也可以继续使用具有最大尾数位数(23)的exponential过滤器进行处理,从而保证最小的精度损失;
最后使用attributes模式进行存储;
压缩关键帧的values数据
旋转的数据可以使用16-bit进行量化处理,并应用quaternion过滤器进行处理;
位移和缩放数据可以应用exponential过滤器应用相同的指数进行处理;
最后使用attributes模式进行存储;
解压缩流程
对于动画数据的meshopt解压缩,互动容器中的实现具体流程如下:
- 解压缩关键帧的times数据
- 按attributes模式进行解压缩;
- 如果压缩时采用了exponential的过滤器(filter),还需要再进行exponential的过滤器解压缩;
- 解压缩关键帧的values数据
按attributes模式进行解压缩;
旋转的数据还需要用quaternion过滤器进行解压缩;
位移和缩放数据还需要用exponential过滤器进行压缩时相同指数的解压缩;
详情
对于动画数据的meshopt压缩,同样也是有损的,往往有40%左右的压缩率,同样采用了wasm的方式加速压缩和解压缩的过程。
附:真实业务(招财神龙)中的动画资源文件为例,有38.50%的压缩率,可以减少1.9MB的资源包体积。
另:招财神龙业务中,考虑中低端机在跨端框架下wasm的兼容性问题,开发时直接默认启用了asm.js的降级方案,但在后来的性能测试环节中发现meshopt解压缩的asm.js在iOS的JSC运行时下效率较低,会严重增加资源的解析耗时,最终上线时并没有启用动画数据的meshopt压缩。
未来规划
24年春节我们完成了引擎预热、资产公共离线库的落地,取得了不错的游戏优化效果。但在业务落地的过程中,我们也发现一些存在的问题,例如引擎预热逻辑命中率双端平均只达到81.33%,整体上还存在优化的空间;另一方面随着资产公共离线库的托管的引擎类型和版本越来越多,体积越来越大,我们需要加强对离线公共库的版本管理,识别和控制ROI整体较低的引擎和版本,从而更好的控制离线公共库的带宽成本。
而在资源压缩方面,我们完成了相关方案js和wasm版本的流程的调研和探索,以24年春节玩法招财神龙为例,我们取得了不错的资源体积优化效果,降低整体游戏包的大小。但在资源压缩方面,由于低端机和部分iOS手机上对于网格优化的解压缩效率低下,甚至部分机型适用wasm会有崩溃的情况存在,所以虽然完成了整体的技术方案落地,但考虑到稳定性并没有在线上启用。后续会进一步优化这一方面的兼容性问题,解压的性能问题上也会直接采用C++而非wasm的方案,将资源压缩方案完整在正式的游戏业务落地,同时覆盖更多的游戏业务场景。
另外,在3D游戏场景下,一般意义上的资源还包括图片、视频、字体、脚本以及特定编辑器导出的资源(如lottie、spine等),对于这些资源文件的压缩也有着明显的意义,我们将在后续进一步探索相关资产的压缩,从而优化整体游戏的资产大小。