项目介绍
《云境》是一款使用Unity引擎开发的WebGL产品,有展厅,剧本,Avatar换装,画展,语音聊天等功能,运行在微信小程序和PC,移动端网页,即开即用。
当前问题和现状
当前项目面临的问题和场景现状
首次场景包体大,首次下载时间长,用户首次进入到场景耗时较长。项目场景本身偏展厅风格,非大世界,非大型游戏场景,云境的场景元素整体比较集中,当前项目场景多数多数是室内场景或者小场景,拆分出去的内容有限,美术人力资源有限,对场景做更细致的拆分处理和分块加载成本较高。我们需要一套简单适用的方案,降低场景包体大小。为了优化场景资源包大小,我们首先需要分析资源包里面的不同类型的资源占比,看哪些资源可以做优化和拆分
场景资源包分析
AssetBundle使用LZ4压缩,下面是针对三个原始场景资源包的分析
公园场景park_001.bundle,8.63MB,Mesh(6.5MB)+Texture2D(5.7MB)
剧本场景scenes_bxzf.bundle,11.3MB,Mesh(8.9MB),Texture2D(8.0MB)
某个展馆scenes_jttd.bundle,5.3MB,Mesh(439.3KB),Texture2D(8.0MB)
通过针对多个场景的资源包分析,会看到Mesh+Texture2D是一个场景资源包占比最高的两种资源类型,Texture2D基本占比在50%以上,而且针对个别场景Texture2D的资源占比会超过80%
方案确定
根据场景资源包的分析,可以得出的结论,针对云境目前的场景特点,多数场景Texture纹理贴图占比在50%~80%,如果采用Texture的处理能够很大程度上降低场景资源包的大小,收益比较明显,同时TextureStreaming的实现,相对MeshStreaming相对简单,投入产出比高,TextureStreaming不会改变场景原有的结构,可以降低美术人员的参与成本。
第一期设计一套简单,易实现,工作流完善的TextureStreaming方案,降低场景资源包中Texture的占比。
Unity引擎自身基于MipMapLevel实现的TextureStreaming,在WebGL平台该功能无法使用 所以需要自己开发一套简化版本并和平台无关的TextureStreaming功能。和Unity的TextureStreaming方案相比,解决的问题有相似的地方,比如控制Texture在运行时的内存占用,将纹理内存维持在一个范围内。但是我们设计的TextureStreaming方案需要关注场景资源包大小同时能够在运行时降低Texture内存的占用。
什么是TextureStreaming
定义的轻量级TextureStreaming功能:将使用高清纹理的场景,离线处理成极低分辨率的场景叫做Lod Scene,在运行时加载Lod Scene,根据规则,从外部加载相应的高清纹理,对低分辨率的纹理做替换。原理简单容易理解。
整体设计目标-TextureStreaming
核心目标:实现简单,工作流程清晰,全自动化
方案需要达成的目标
非侵入式,做材质和纹理贴图的全量拷贝,降低复杂度和资源引用维护成本 不改变原场景不修改原场景引用的材质,避免造成材质混乱,错误修改不修改原场景引用的纹理贴图不修改打包规则和之前的加载逻辑保留原始场景光照贴图和渲染设置,忽略对光照贴图的Lod处理 一键式处理,通过EditorSceneDescriptionConfig文件控制生成的Lod Scene内容,避免人工处理,节省人力EditorSceneDescriptionConfig需要支持的动态配置 控制单个贴图LOD分辨率,提供x16,x32,x64,x128和原始贴图尺寸 Editor离线和运行时配置分开,运行时配置数据结构简单Texture AssetBundle包大小,根据配置自动分割AB包 最小化更新,Texture AssetBundle打包更新不会造成,整个Lod Scene也被更新需要HD Texture AssetBundle和Lod Scene AssetBundle无依赖关系 运行时,支持Texture Streaming功能分场景动态开关,加载HD Scene还是Lod Scene运行时,支持Streaming流式加载(分Grid,分帧加载TextureCell)运行时,支持Texture Bundle分帧请求加载,降低可能带来的卡顿运行时,支持Material分帧修改,降低可能带来的卡顿(经过测试,暂时未发现该部分的耗时情况)运行时,Grid直接选择九宫格方式,当前Grid索引+外部8个Grid的内容运行时,使用最简单的加载规则,玩家在场景中的世界坐标作为整体输入参数运行时,场景切换HD Texture加载和卸载正常运行时,纹理切换不会卡顿方案设计和实现
资源管理
目前使用Unity Addressable进行资源运行时管理和资源打包
Texture Streaming美术处理管线
完整的处理流程图如下:
场景处理管线具体介绍
创建SceneDescriptionConfig配置
采集场景资源:材质,纹理
获取场景中,所有引用的材质球和关联的纹理贴图,为后续生成LodScene做数据准备
忽略的部分:
拷贝Material,生成LodTexture,LodScene
生成LodScene
将依赖的材质拷贝一份,生成Lod Materials将材质球依赖的所有Textures拷贝一份,生成HD Textures根据设定的HD Texture 分辨率执行Texture缩放,生成Lod Textures拷贝原始场景,生成Lod SceneLod Materials引用的Texture更换为Lod TexturesLod Scene关联的Material替换为拷贝的Lod Materials经过上述的处理,完成了Lod Scene的生成,保证对原始场景的资源无侵入处理,会带来一定的资源冗余比如Lod Textures,不过牺牲一小部分的内存,能够简化流程和实现方式,在当前项目目前是可以接受的
设置Grid参数,按照规则Grid分割场景
目前只支持规则的Grid切分,Grid的可调整的参数如下:
Grids起始位置Grids,行列大小每个Grid大小的大小获取每个Grid包含的Renders
遍历场景中的所有Renders,确定所在的Grid,确定每个Grid依赖的Renders,通过Renders确定当前Grid依赖的Lod Materials,根据Lod Materials确定当前Grid需要加载的HD Textures
创建运行时Config,建立Grid,材质和贴图的映射关系配置
建立Grid-HD Texture-Materials之间的映射关联配置
运行时配置文件
Grid配置,一个Grid关联到多个Texture Cells
Texture Cells配置
资源Addressable Key(GUID),用于Addressable通过Key执行加载对应的Lod texture引用当前HD Texture关联的材质球和纹理槽位,如果当前HD Texture加载完毕,更新关联的Material对应的纹理槽位即可自动化资源分组
Lod Scene:Lod场景
Lod Scene Material + Runtime Config:Lod场景引用的材质球和运行时配置Lod Scene HD Textures:Lod场景引用的HD Textures,按照Texture单个包体指定的大小,自动生成对应的Label做分包处理,Group使用Pack Together By Label模式打包最终管线输出产物
自动化生成的工程产物
HDTextures:存放拷贝出来的原始纹理资源LodTextures:存放处理过后的低分辨率的纹理资源Mats:存放Lod Scenes引用的材质球资源outdoors_001_gen_lod.scene:Lod 场景文件outdoors_001_gen_lod_runtime.asset:TextureStreaming运行时配置文件Addressable资源打包分组
Lod Scene-单独Group分组
Lod Materials+Runtime Config-单独的Group分组Lod Textures-同一个Group分组,但是打包的采用Packed Together By Label方式打包成多个AssetBundle包运行时逻辑
运行时的逻辑比较简单直接
接收角色位置输入->确定Gird->确定关联的TextureCells->使用Key加载->加载完毕,更新TextureCell关联的Materials
最终实现Lod Texture到HD Texture的动态更新
为了防止Grids关联的TextureCells过多,导致请求数量多,加载多,做了简单的“分帧”处理
场景切换之后需要调用Addressables.Release(tc.Value);释放Texture2D资源
TextureStreaming方案收益
场景资源包大小在使用TextureStreaming之后的大小优化对比,目前已有的18个场景,场景包体大小都控制在5.5MB以下,首次加载时间优化,按照4G网络情况理论测算缩短3~5s,贴图资源量越大,包体减小越大,收益会比较明显
基础场景实现效果
streaming展示
总结和展望
本文介绍了适用于WebGL平台的场景包体大小优化的TextureStreaming方案,相对大世界流式加载复杂完善的方案,当前的TextureStreaming方案在一定程度上解决项目的痛点,优化了WebGL应用的场景包体大小,缩短下载时间,但是该方案还有很多需要继续细化和完善的地方,比如Grid粒度“粗糙”,加载逻辑更细致的控制,后续也会持续改进。
TextureStreaming后续完善的方向
场景处理管线,细化分割粒度和规则运行时,细化加载规则,更精准的优先级,支持更多参数的输入视角,朝向运行时,根据规则预加载Grid,降低延迟运行时,支持动态卸载Grid,降低内存占用打包资源,更精确化的Texture分包,目前采用Editor模式获取纹理大小,某些情况下获取的纹理大小和实际打包压缩之后的AB包大小差别比较大,导致Texture,AB分包并不均匀针对LOD的纹理图片可以直接关闭MipMap,更快的加载速度和更小的内存,开启MipMap会带来33%的内存增大场景资源包优化其他思路
支持Mesh Streaming支持场景物件的拆分,将物件拆分出去,降低包体大小和场景对象数量LightMap的处理,进一步减少Texture2D的占用TextureStreaming方案从问题分析,方案设计,到工具开发,运行时实现,整体比较简单易理解。
只有适合项目并解决项目痛点的方案才是合理的方案。当前的方案还有许多可以继续优化和扩展的地方,后续会持续优化。
希望能够提供一些思路和帮助,欢迎交流和指正~