畅销 SLG《乱世王者》深度优化方案
《乱世王者》是由腾讯天美工作室出品的一款战争策略手游,2017 年 8 月开启测试,数天之内便取得了 App Store 中国区畅销榜前三的成绩,充分体现了市场、玩家对于这款游戏的认可。
在尊重和传承经典 SLG 玩法的前提下,游戏做出了不少突破:游戏构建了一个大世界,为玩家呈现更加真实、热点的游戏环境和生态;游戏融入了极具乐趣的养成模式,使得游戏策略性更强;游戏内加入了如 AR 寻宝玩法、 自定义头像等酷炫新玩法。
6 月 22 日,来自腾讯的游戏高级工程师肖程祺受邀出席了 Cocos 上海沙龙,向开发者分享了《乱世王者》的深度优化方案,包括《乱世王者》项目中地形处理和优化思路,深入讲解了游戏针对纹理内存优化的解决方案,并分享了项目对引擎渲染层所做的一些优化。
肖程祺
在获得程祺同意后,Cocos 将其优化方案整理成文,同更多开发者进行分享,具体内容如下:
《乱世王者》深度优化方案分享
腾讯 肖程祺
《乱世王者》是基于 Cocos2d-x 开发的一款战争策略类游戏,需要在游戏玩法创新的同时,在美术品质和性能方面也做出突破。
一、SLG 地图的微创新和优化
美术上想要增加细节表现力,让地形的细节更加丰富,同时需要增加 3D 透视的感觉。
实现成果:
1、丰富的细节
地形细节丰富的代价:
资源量膨胀,地形纹理是原来的5倍。
地形类型新增:沙漠,雪地,每个地形的变化增加。
tile 数量从 100 块左右提升到 500 多块。
渲染压力提升,渲染层数从 2 层变 5 层,拖动地图的时候重新组织数据的开销增加。
面临的问题有:
内存增长:纹理扩大了 5 倍
地图层数增加后,加载和拖动地图更新的效率成倍降低
贴图数量增加后,需要自己去实现 batch
我们的优化方案:
(1)提升贴图的利用率,减少地图纹理的内存
重新排列 tile,贴图利用率从 50%提升到 85%
效果:
5 张贴图最终变成 3 张
(2)建立更高效的数据组织,提升加载效率
地图数据离线处理后,以 protobuff 的数据格式存储,并使用 zstd 无损压缩进行处理,加载速度和大小都得到保证。
(3)合并地图批次,减少不必要的顶点数据
每一层地图的都根据 TextureID 排序后绘制,由于顶点和 UV 数据已经存储在 VBO 里面,只需要根据裁剪结果修改 indexbuffer 即可。5 层一共 3 张贴图,意味着 drawcall 数量不会超过 15 个。
2、3D 透视关系效果
(1)处理3D透视关系
场景用 3D 相机绘制
地图上的物体“站”起来
所有物件绕x轴旋转
至垂直于摄像机的fov中线的角度
(2)解决大物件的视角偏差
边缘位移:
对于皇城和虎牢关这种比较“宽大”建筑单位,在拖动大地图的过程中会出现边缘位移的现象,这个效果也是非常影响美术表现的。
我想到的解决方案是,将大的建筑单位拆分为多个部件,并对每个部件分别作 billboard 处理。
以皇城为例,考虑到部件之间的关联性,使用了如下的拆分方案,可以看到边缘位移问题得到了很好的解决。
(3)解决 3D 物体的透视带来的偏差
为了表现更加丰富的怪物细节以及更流畅的动画,美术希望直接在现有的场景中加入 3D 模型。
由于我们改用了 3D 摄像机,同时大地图场景单位都是 2D 资源,加入 3D 模型后两者的透视效果不一致。
在透视相机下,左右移动相机透视问题严重
容易想到的解决方案:先将 3D 模型按照指定角度渲染到 RT 上,再将 RT 上的纹理和其他 2D 元素一样渲染到场景中。但是 Cocos2d-x 对多照相机和多渲染路径的支持不够友好,这种方案需要对引擎作比较大的改造,而且会增加更多的显存占用。
我们采用的是第二个方案:在每帧绘制 3D 模型前,计算出其缩放和位移,然后再使用正投影照相机参数绘制 3D 模型到屏幕。整个过程只需要一次绘制,没有额外的纹理占用,而且对引擎的改造也是最小的。
渲染 3D 物体前自己计算正交投影矩阵
上述这些效果提高了《乱世王者》大地图的美术品质,与同类 SLG 游戏对比,我们游戏获得了更多的关注。
二、纹理内存的深度优化
《乱世王者》内存中资源的大致占比为:
用 4 步搞定内存大户:纹理
SmashTexture
FontTexture
压缩纹理的优化
Texture Pool
1、Smash Texture
Smash Texture 解决的问题:
UI 制作过程中需要使用 atlas 来尽可能合并渲染批次
因为使用 atlas 需要一套规范避免一个界面加载过多的 atlas(维护成本很高)
一旦出现一个元素多次重复出现且用到多张不同的纹理,需要额外的机制去避免 drawcall 爆炸般增加
png 使用的 deflate 压缩率不算很高,安装包体积压力山大。有些人选择使用 jpg 等有损压缩来牺牲美术品质
(1)Smash Texture Pipeline
通过 tinypng 减色,增加压缩率(如果减色效果不好,则不做减色处理)
打包成合图后,供在编辑器中使用
转换成 smash
drawcall的降低
未作任何其他改造的情况下,批量使用 smash texture 后,当前界面的 drawcall 从 64 降低到了35。
(2)减少 IO:Block Cache
加入 Cache 机制, 达到上限时使用 LRU 算法清理,每次清理固定数量
特定时机,清理 Cache,分帧清理,不卡顿
回收操作只需要回收分配信息,不需要对贴图显存操作,速度快
现网版本用户行为上报的 cache 命中率
(75%左右)
(3)提升Smash Texture的使用率
原本 smash texture 里面的每一行是 32 像素统一高度,利用率比较低(比如说 36 像素高的图片,最后还会留下 6 像素左右的空间。填充到 32 像素高的 smash texture 行里会存在浪费),因此我们按照下图,重新划分了高度行,容纳更多的 UI 图元。
经过优化,smash texture 的使用率可以从 80%提升到 90%。
(4)总结 Smash Texture 的优缺点
优点:
内存峰值可控:所有使用 smash texture 的贴图共享一张纹理
增加了 UI 利用 dynamic batching 自动合批的概率
最大程度还原 UI 的品质
相对 PNG,他有着更小的文件尺寸(zstd 压缩)
缺点:
对于大型背景图不太适合,容易填满。背景图需要额外拆分。
smash texture 目前只支持离线预处理,动态下载的图片还不支持。
2.文字系统的内存优化
用一张 2048x2048 大的 A8 格式纹理解决所有字体需求,当贴图不够用的时候尝试清理整张贴图。
不同的字号,字形,格式生成唯一的 key 在这张统一贴图中进行索引。
提升贴图利用率:改良装箱算法
基于 maxrect 做优化,将纹理利用率提升到 95%+。
提升贴图利用率:减少字号数量
统计了文字的字号使用频率,根据字号分布,我们取了 5 个字号,其他字号向下取到最接近的预定义字号上,缩放后渲染,大于最大字号的时候则用最大字号放大后渲染。
3.压缩纹理
压缩纹理的一些痛点:
压缩纹理优化:
对于 1/2 方图的 atlas,分离 pvrtc 的 rgb 和 a 通道,合并成方图后,再压缩。
对于不符合 2 的 n 次幂的不规则的单图(背景图为主),通过计算出最小的 2 的 n 次幂包围尺寸,切分成多块来优化文件大小,解决 pvrtc 必须方图的不友好设定。
4.Texture Pool
把所有贴图统一管理生命周期
根据设备内存大小,分别设置贴图 GC 的阈值
当贴图使用量超过 GC 阈值的时候,根据引用计数释放没用到的纹理
添加运行时查看工具,运行时也可以查看不正确的贴图格式设定
内存管理的小结:
所有模块必须有上限,避免随着迭代内容不断增加而持续上升
避免浪费
三、解决 UI batch 的痛点
即便 Smash Texture,即便 Font 在一张纹理上,我们还是面临着一些 darwcall 明显可以优化的地方。
期望的渲染顺序:
通过 TriangleCommand 底层的 dynamic batching,自动合并批次。
Cocos2d-x的默认渲染顺序:
充分利用 TriangleCommand:
将所有 Cocos2d-x 默认的控件以及自定义控件都尽可能改造成 TriangleCommand 实现,使得支持 Dynamic Batching。
CCProgressTimer
LayerColor
利用 GlobalZ 来解决合批的问题:
对于 2D 元素,GlobalZ 的排序可以很好的让相同材质的物件进行 batch
所以我们可以离线设置好 GlobalZ(存储在csb里面)
根据遮挡关系和纹理自动计算
人工设置
自动递增
新的问题:
当 UI 有多层弹窗的时候,上下两层的控件由于 GlobalZ 都从 1 开始,所以会出问题。
改动最小的解决方案:
改动后的情况:
解决了多层 UI 层级问题
解决了同层 UI 利用 GlobalZ 能尽可能 Batch 到一起
程祺演讲 PPT:「《乱世王者》深度优化分享」
获取方式:在公众号后台回复「乱世王者」
四、结语
程祺:Cocos2d-x 使用上手难度比较低,实际使用项目也很多,在开发的过程中也存在一些普遍的性能和优化痛点,希望这次为大家带来的经验分享,能帮大家在优化的道路上走得更加顺利。我们的腾讯天美上海 T1 工作室正在招聘 Cocos2d-x开发,对游戏开发感兴趣且有 Cocos2d-x 使用经验的朋友,欢迎加入我们,简历投递邮箱:cloudxiao@tencent.com。
C 姐:非常感谢程祺带来的分享!并祝项目更加顺利!
C 姐:Cocos 引擎一路走来,收到了很多来自用户的宝贵建议,通过不断的优化和改进,在 Cocos Creator 中,上述许多性能问题都已得到解决。包括:
支持 Texture Packer 的 Polygon outline 裁剪,使用 Polygon outline 裁剪后的图集将对每个小图按透明区域精确裁剪为不规则的多边形,将达到类似 Smash Texture 节省图片空间的效果,对渲染性能也有一定提升。该功能无需在 Creator 中进行额外操作,导入图集后即可自动开启。
从 Cocos Creator 2.0.9 开始,支持将 Label 设为 CHAR 模式,即可实现类似的优化方案。能够以“字”为单位将文本缓存到全局共享的位图中,相同字体样式和字号的每个字符将在全局共享一份缓存。提升纹理利用率的同时,也大大提升了文本刷新时的性能。详见文档[文本缓存类型]
从 Cocos Creator 2.1 开始,集成了压缩纹理支持,编辑器可设置不同平台所需格式,构建时将会自动进行压缩,支持 etc/pvr 的 alpha 通道分离。详见文档[压缩纹理]
从 Cocos Creator 2.0 开始,默认会在原生和 Web 平台上启用动态合图,该功能支持运行时根据渲染的先后顺序动态对纹理进行合并,降低 drawcall,用户无需对 z 进行手动指定。当 Label 启用了 BITMAP 模式后,会同时进入合批。详见文档[动态合图]
参考文档:
[文本缓存类型]
https://docs.cocos.com/creator/2.1/manual/zh/components/label.html#%E6%96%87%E6%9C%AC%E7%BC%93%E5%AD%98%E7%B1%BB%E5%9E%8B%EF%BC%88cache-mode%EF%BC%89
[压缩纹理]
https://docs.cocos.com/creator/2.1/manual/zh/asset-workflow/compress-texture.html
[动态合图]
https://docs.cocos.com/creator/manual/zh/advanced-topics/dynamic-atlas.html
Cocos 技术派 | Cocos Creator 2.0 摄像机的灵活运用
Cocos 技术派 | 重度小游戏《三国封魔传》技术实现方案
Cocos Creator v2.1.1 新增 3D 场景编辑
点击[阅读原文]进入 Cocos 社区一起交流!