MMORPG手游项目优化分享总结

🗨️字数统计=5.1k字 ⏳阅读时长≈18分钟

最近为了全面提升沉淀自己的项目经验,以及想吸收其它优秀MMOPRG项目的技术,观看了一系列UWA讲座,大佬云集,技术以及思维的碰撞产生了大量的灵感,也促使我对MMORPG游戏有了更深的理解,在此针对大世界MMO手游《剑侠世界3》优化分享的讲座进行总结。

讲座详细信息

主办单位:UWA
讲座:大世界MMO手游《剑侠世界3》优化分享
主讲人:江志强 — 西山居《剑侠世界3》项目组 客户端主程
如有侵犯,请联系我。

MMORPG手游现状

大世界、实时天气、上天入地、捏脸易容、超远视距、布料模拟、大规模植被、多人群战、高精度渲染、适配机型广、体积渲染、内存占用小。

剑世3项目基础参数

引擎:Unity 2019.4.x 源码编译
管线:基于URP7.x改造
发布平台:Android/iOS/Windows/Mac M1
API:OPGLES3.0或以上,vukan版本制作种
内存要求:2G或以上
机型:OPPO A5/华为 mate 9/iPhone 6S/以上
分辨率:移动端最高画质默认960p
包体:首包1.4G,整包2.6G,后台边下边玩补充下载

1.场景相关

一、场景相关优化

1.场景静态处理
2.场景流式加载机制
3.取代LODGroup,独立LOD线程
4.光照烘培&TextureStreaming
5.PVS遮挡剔除
6.地形&植被
7.移动逻辑体素格子
8.远景优化思路

场景基础数据(以主城-江南道场景为例)

主体大小2K,外围远景4K
总制作面数2000万面以上(3级LOD,不含植被Instanceing)
4万个以上Render
50万棵以上植被
手机最高可视距离3.5公里以上
模型Mesh制作3级LOD(按1/0.6/0.3面数比例制作)

1.场景静态处理
①分层分块分LOD相同材质静态合并Mesh

相同层(远景/地形/植被/建筑框架/地砖/中景/近景/其它)
相同区域(32*32米格子内,不同层的分块大小不一样)
相同参数(是否参与烘培,是否投射阴影,是否接受阴影,相同材质,相同LOD数量)
自动合并&手动合并

②不可见面剔除

背面剔除(可移动区域外)
地下剔除(地形以下)
闭合模型重叠面剔除(石头堆叠)

③带顶点动画物体合并

质心坐标合并到UV3

④合并后同步源参数

Layer,Tag,ContriGI,LightmapScale,Shadow Caster…
未参与合并的Renderer保持不变
打组机制,A和B存在子集renderer被合并,那么要保持A,B同时显示或隐藏

⑤导出Mesh/Prefab文件供后面使用

Combine_LO0.prefab,Combine_LO1.prefab,Combine_LO2.prefab
Combine_LO0.mesh,Combine_LO1.mesh,Combine_LO2.mesh

⑥资源标记(提前标记)

画质分级(高配加载1/2/3/4Level,中配加载1/2/3Level,低配加载1/2Level)
是否投射阴影/接受阴影/参与水体反射/水下物件等
第3级LOD中贡献不明显的Renderer提前标记为运行时关闭
其它逻辑标记等

2.场景流式加载机制
①流式加载需求

内存/打包依赖/增量更新大小/DrawCall/远近区分降级等

②基础设定

场景按画质分级(Level1,2,3,4)
分层标记(远景层,地形层,植被层,建筑框架,地砖,中景,近景,其它)
每层会限定 最小最大加载距离,最小最大裁剪距离
每层会有一个单独配置,来设定物体包围盒大小评估权重范围和加载/切换 LOD0,LOD1,LOD2 的距离
运行时按距离裁剪掉大部分对画面贡献不大的物件

③序列化场景为数据文件

序列化后场景.unity为空文件

  • 没有Renderer,只保留一些场景设置类脚本,灯光,环境参数等

按物件裁剪距离分3层序列化(减少运行加载数据文件内存占用)

  • 裁剪距离在1024以上(tile_0_xy.asset,多块数据->多个文件)
  • 裁剪距离在512以上(tile_1_xy.asset,多块数据->多个文件)
  • 裁剪距离在512以下(tile_2_xy.asset,多块数据->多个文件)
  • 预先生成四叉树结构化存储,分层可减少深度,不同层更新频率不一样
  • 数据量尽量简化,减少大小
    • 只记录LOD0,position,scale,rotation,tag,layer,lightmapindex等还原必须数据
    • 原始Unity文件大小230M以上,序列化后为空场景 500K+10M序列化分块数据文件
④运行时策略

使用cullinggroup优先加载视距内的tile

分帧异步加载卸载资源/对象

  • 严格控制帧耗时
  • 使用只能缓存池(自动收缩)
  • 同时只加载一级LOD,切换时主动Unload前一级别的Mesh
  • 必要时才更新(位置变更等,不同层更新频率不一样等,优先更新近处LOD)
  • 支持边下边玩机制(资源下载完后才加载)
  • 模板Prefab复用机制

按距离进行切换LOD/裁剪(四叉树用于加快视锥内查找)

⑤面熟预算机制

同屏加载面数过多时,可自动裁剪掉对画面贡献不大的中远景(屏占比)

计算频率控制

⑥动态降级策略(带宽/ALU)

实时阴影范围内:

  • directional on + recvshadow on + normal on + lightmap on + shadowmask on

实时阴影范围-300米:

  • directional off + recvshadow off + normal on + lightmap on + shadowmask on

300米外:

  • directional off + recvshadow off + normal off + lightmap on + shadowmask on

更远距离:

  • directional off + recvshadow off + normal off + lightmap off + shadowmask on

天气/植被等特殊计算部分也按此距离进行降级

增加变体数量,对SRP Batcher不友好,具体项目评估

⑦加速场景切换速度

跨场景传送

  • Iphone11可控制时间在1s一下完成进入主城
  • 提前预加载目标场景(任务追踪提前加载下一场景,传送固定CD时预加载)
  • 切换场景时控制切换耗时,超时3s以上直接进入
    • 跨图仅优先加载目标点周围一定距离
    • 同步方式加载周围地形层,地砖层,保证地面正确
    • 其它物件使用异步加载(跨图读条中不等待是否加载完成)

同场景远距离传送

  • 人为引入传送CD:3秒,传送不需要界面遮罩盖住
  • 传送CD中即预加载目标落点资源(方案同上)
  • 打断传送CD后,自动超时释放
3.取代LODGroup,独立LOD线程

原生LODGroup计算量稍大
原生LODGroup更新频率不可控
不建议大规模使用LODGroup(可小范围,或者多个prefab组合为一个LodGroup)
使用单独线程来进行计算(可控制计算量/频率等)
距离裁剪/屏占比裁剪

4.光照烘培&TextureStreaming
①Directional烘培模式 + ShadowMask 间接烘培 + 实时直接光照

原始场景烘培贴图张数(约55张2048大小贴图lightmap,共3*55=165张)

②光照贴图分层分区域烘培

渐进式烘培下控制:LightmapParameters.bakedLightmapTag
相同Tag会烘培到一张图内
光照贴图总数会增加(每张贴图大小分布依赖Tag设置)

③光照贴图流式加载

用时加载,不用时卸载,卸载中远距离物件不使用dir贴图
节省一半以上原始光照贴图内存

④昼夜方案

提取黑夜点光源lightmap与白天差异值到shadowmap的空闲的gba通道
白天GI * 弱化系数 + shadowmask.gba * 点光全局强度 * 点光TintColor = 黑夜

⑤TextureStreaming

有效控制贴图内存

  • MaxLevelReduction
  • MemoryBudget
  • 贴图导入配置:mipmap priority
  • Texture2D.requestedMipmapLevel
  • Texture2D.streamingTextureDiscard
  • UnusedMips

源码改进requestedMipmapLevel及时生效

打开全屏UI角色模糊问题

  • 打开UI时增加内存阈值
  • UICamera streamingMipmapBias = -2
  • 或者使用StreamingController进行预加载,比较麻烦

ASync Upload Size合理设置(使用最大纹理大小,避免自动调整缓冲区造成性能影响)

5.PVS遮挡剔除
①MMORPG首选PVS方案,降低运行时开销
②参考Unreal实现

自动标识遮挡物/被遮挡物
栅格化生成可见性单元(6米6米高8米cell2层)
按256
256生成block(Mesh归属block和block内ID生成)
cell与可见距离内mesh进行physics求交
导出分块数据文件,bitarray标记可见性
数据精简(按场景流式加载分层进行可见性判断)

③利用Job System加速烘培时计算
④分布式烘培支持(12台机器搭建集群)
⑤运行时流式加载卸载数据文件,多线程中解析数据文件
⑥分帧循环遍历设置Renderer可见性
⑦只对LOD0烘培,减少数据量
6.地形&植被
①使用传统方案,Terrain转Mesh
  • 自动减面转Mesh并生成多级LOD
  • 256*256为一块,保持边缘锁定
  • 地形HLOD
  • 工具采集overdraw决定指定位置地形应该先渲染还是后渲染
②地形全局预混合贴图(适用于2K地形场景)
  • 原始地形采样14张贴图ctrl+全局ao+(4 diffuse + 4 normal + 4 Metallic)
  • 原始地形使用TextureArray2D
  • 全局预混合贴图只需采样3张(全局diffuse + 全局normal + 全局Metallic)
  • 近处使用原始地形(保持精度),远驰降级地形为全局预混合贴图
③虚拟贴图VT/RVT优劣
④水下地形优化
  • 主城覆盖大面积水,玩家可潜入水底
  • 按水深度分割开地形(水深2米基本看不到地形)
  • 角色在水上时:水下地形加载为低模并切换为全局预混合模式
  • 角色进入水下:水下地形切换为原始地形
  • 同理角色在水下或水上时,也可针对性对其它模型进行同样优化或裁剪掉
  • 进入水下时相机视距适当缩减
⑦植被相关(草,灌木,树,远景公告板)
  • 植被制作

    • 3级LOD(插片数量,草单面材质,树双面材质)
    • 草顶点计算光照
    • 接受阴影(抓屏shadowmask软阴影),接地色(抓屏地形diffuse)
  • 数据文件组织

    • 128米*128米为大块:适配使用DrawIndirect机型
    • 大块内再切割为32米*32米小块:适配使用DrawInstacing机型
    • 裁剪
    • 128大块与32小块均参与PVS烘培,先进行PVS剔除(大块+小块)
    • 进行块级别的加载距离剔除(大块+小块)
    • 进行块级别的视锥体剔除(大块+小块)
    • 适配DrawIndirect模式的在GPU再进行单颗粒度的视锥体剔除+Hi-Z遮挡剔除
  • 流式加载卸载

  • 分画质密度限制,按距离密度衰减

  • 按距离切换LOD(mesh,shader lod)

  • 支持限定DrawCall数量,限定绘制植被数量,避免分布相差过大

    • 需要避免绘制抖动
  • CommandBuffer.DrawMeshInstancedIndirect

  • CommandBuffer.DrawMeshInstanced

  • Graphics.DrawMeshInstanced

  • AlphaTest OverDraw,Pre Z Pass

  • IndirectDraw在手机平台/模拟器均存在兼容+性能问题,建议针对性测试有提升机型才开启

7.移动逻辑体素格子
①减少物理使用场合
  • 实时物理计算:死亡效果
  • 实时物理计算:场景可破坏物件
②场景可移动表面体素化
  • 数据文件和内存展开 都要尽量精简压缩
  • 联通性等不要使用指针等消耗过多内存
  • 相似区块合并
③远距离寻路配合
  • 近距离格子联通性
  • 远距离A*打点
8.远景优化思路
①公告板
  • 冒名顶替物(Imposter)(实时生成or静态bake)
  • 单张Billboard
②远景模型
  • 背面剔除掉多余三角形
  • 交错摆放营造层次感
③天空盒地平面虚影
  • 营造纵深
④HLOD
⑤雾效/大气散射/体积渲染

2.资源相关

一、静态资源扫描处理

UWA/UPR Tools定期扫描问题处理
导入资源工具自动设置好格式(贴图,FBX,动画)
材质多余keyword清理/材质多余参数清理(切换shader后还保留之前的记录,打断合批或增加依赖)
动画控制器冗余依赖清理(从A控制器复制到B控制器产生错误的交叉依赖)
单独剪出动画文件存放(anim),避免运行时加载依赖了原始FBX的模型
动画文件的浮点数精度精简,移除多余的轨道(如确定不需要scale变化)
定期清理掉重复贴图,资源等

PS:可以做一个资源工具,正则表达式识别并自动化设置格式与上传

二、打包原则

1.打包策略好坏直接影响运行效率与内存
2.减少AB数量总数,限制单个AB大小(1-5M)
3.分资源类型,分大目录,分场景打包
4.单个场景按流式加载规则分层分块打包
5.减少单个AB的依赖链长度,不允许AB出现循环依赖现象
6.公共资源单独打包(被依赖次数大于多少次),按被依赖次数分级打包
7.去除运行时不使用的冗余资源

三、AssetsBundle管理

1.自己管理Manifest,记录资源依赖与AB反向依赖
2.按单个资源粒度记录依赖关系(相同依赖关系共用数据)
3.加载单个资源,只需加载该资源实际依赖的AB
4.资源缓存/AB缓存/引用计数/加载依赖链记录
5.异步分帧加载卸载/控制帧耗时/控制帧加载数量/读条时加快
6.卸载资源,资源的依赖链AB无任何反向依赖引用存在时,使用AssetsBundle.Unload(true)可减低UnloadUnused调用频率(避免卡顿)
7.特殊情况同步加载与异步加载关系处理
8.新版本AssetsBundle.memoryBudgeKB

四、增量更新&后台下载

首包内容补全-压缩包一次性下载(老账号)
首包内容补全-边下边玩模式(新账号)
增量内容更新
华为HMS NetWork kit加速下载能力
多线程处理(任务分发/读写文件/下载处理)
CDN主备下载速度优先切换

五、代码更新策略

紧急问题处理-后台GM命令下发(Lua)
常规更新-Lua
代码Bug修复-Inject Fix
大版本更新-安卓DLL Patch(libunity.so,libil2cpp.so)

3.内存相关

一、Mono

配置表序列化二进制格式,使用精简的数据格式,如bool合并为bitset
管理好各类型的缓存,避免频繁分配,同时要做好智能释放策略
容器相关:字典等可实现自己的unit16位索引的版本,注意2倍扩容
字符串:string.Intern,减少拼接,类似技能CD的时间进度字符串等均可预先生成缓存
网络协议序列化/反序列化对象复用
避免每帧或频繁分配的GC
流式加载卸载/用到才加载/延迟加载
减少GC.Collect调用频率卡顿,使用增量式GC
控制在100M左右

二、资源内存

警惕资源冗余多引入的无用部分
同时只加载一级LOD Mesh
不使用的资源及时释放掉(低配画质不开启法线贴图只加载最后一级别mipmap)
关注大小异常的资源和重复资源(一份资源存在两份文件拷贝)
按设备的内存去动态控制加载的资源的级别
理解资源格式各种压缩方案的优劣/Project settingO(Mesh optimize、Compress)
相关设置影响
资源泄露监控-返回登录界面后的资源内存要与首次进入登录界面保持一致

三、第三方插件

音频播放-注意释放
视频播放-注意释放
Lua-注意泄露-扫描深度过大,key数量过多的table
警惕Native插件BUG,定期关注升级信息
XCODE-内存Profiler/Unity Memory Profiler

四、RT

理解Rt格式,UNorm,SRGB,SFloat等,是否需要深度/模板
临时RT复用/swap
不同机型设定不同格式
不使用的特性及时关闭
合并PASS处理

五、特效粒子内存

注意渲染模式为MeshRender的粒子
URP不支持粒子IndirectDraw,会产生大量合批的VBO数据且不回收

六、Shader lab

自动化工具跑场景-采集SVC(不同画质/不同设置/不同天气/不同地图等)
修改shader_feature/使用ShaderStripper工具在打包时裁剪掉多余变体
注意URP本身的shader变体/lit/后处理等,裁剪掉多余的变体(引用存到了URP配置中)
同画质下不同的keyword能合并的合并,使用直接define方式,减少keyword数
减少shader种类,同时对SRP合批也不友好

七、内存泄露

自动化案例监控内存变化曲线
形成历史数据统计数据
同案例历史数据波动变化大的需要关注当天提交的内容

Unity内存释放接口汇总

Object.Destory/Object.DestoryImmediate
GC.Collect/增量式GC
Resources.UnloadAsset
Resources.UnloadUnusedAssets
AssetsBundle.Unload
Texture2D.streamingTextureDiscardUnusedMips
Application.lowMemory

4.其它相关

1.角色
2.特效
3.内存
4.shader/后处理
5.功耗
6.机型适配&兼容

一、角色

CPU蒙皮,降低GPU开销
角色LOD:高模 + 低模 + ShaderLOD 2级 + 贴花开关 + 半透转不透 + 动骨开关 + 骨骼优化 + 不同画质下处理方案区别
同步人数:服务端限定同步人数 + 客户端显示模型人数
距离判断:限定动骨模型总数 + 动画频率控制 + 人物关系优先判定
剔除模型多余Mesh通道,合并UV通道
角色自阴影PCF PASS:拉近才开启
大规模战场同模机制
战斗场合自适应降级,如关闭挂件动画
捏脸运行时合并图集/顶点格式压缩

二、特效

特效开销静态资源扫描
特效数量限制:按特效类型/敌对关系 等限制每种类可创建上限
特效LOD:制作分高低两层级别,按距离施法距离和画质切换,近距离限定高级特效数量,远距离按随机比例出现高或低特效
按距离自适应控制特效粒子发射数量
按画质配置不同参数
场景特效:场景流式加载机制控制裁剪距离

三、UI

优先保证主要界面Drawcall数量
UI图集智能缓存 + 限定缓存总数量
半屏或全屏UI,关闭场景相机与不需要的PASS
半屏毛玻璃效果界面使用一次抓屏模糊复用,关闭场景相机
冒泡文字Instacing
小地图等大图 + Mask方式改为shader上uv位移,减少overdraw
UI异步加载模式,减少卡顿点
脚本层优化:事件回调合并,延迟触发等

四、shader/后处理

合并使用低精度浮点数
避免精度转换
避免冗余重复计算
优先顶点计算
降低顶点带宽(低配机主要问题)
减少使用pow,cos,除法等操作
做好shader计算降级,采样降级,半透转不透(距离远近切换)
材质中空白贴图使用宏关闭采样
不使用PASS特性要及时关闭
减少SetRenderTarget切换,合理使用LoadStoreAction,适当降RT分辨率
匹配机型性能实施不同开销版本的效果

五、功耗

优化的目的为了提升玩家体验,提高帧率,降低内存,减少功耗发热,提升畅玩时间
功耗采集作为日常自动化工具性能分析的一个重点(瞬时电流/电压/游戏时长)
智能降温模式:自适应分辨率 + 各类配置参数动态降级(如LODBias,加载物件数,角色数,后处理等)

六、机型适配&兼容

机型默认画质级别适配:GPU型号,CPU型号,设备标识
异形机型UI适配,折叠屏等
不同画质机型兼容、稳定性测试
机型性能配置:50+项可配置参数控制性能相关参数
常见问题:RT格式/shader复杂度过高/Shader采样数过高/CS兼容/NAN/深度异常
GPU驱动问题:识别驱动版本绕过
模拟器兼容问题:识别模拟器并特殊处理
机型内存适配:动态更改内存相关参数(如Streaming配置等)
引擎BUG:保持与unity官方一个月一次沟通

分享到