作为腾讯游戏首款自主研发的3D武侠题材MMORPG游戏,《天涯明月刀》凭借其自研的Quicksliver引擎,高度还原了很多真实景观,塑造出杭州、开封等经典场景,并通过真实的光影效果和流畅的战斗画面,向玩家展现了逼真写实的武侠世界。本期《论道》,腾讯北极光工作室群的专家工程师刘冰啸将深入复盘《天涯明月刀》5年开发历程中,针对游戏主城、多人场景等进行的诸多优化,分享制作大型MMORPG游戏效率提升的方法以及常见疑难问题。
以下为分享实录:
https://v.qq.com/x/page/v0842tfztro.html
《天涯明月刀》引擎的主要优化经历
纵观整个《天涯明月刀》的发展历史,从优化的角度来看,大概经历了以下过程:
在2011年,项目的内部测试已经实现了在定制的游戏引擎基础上的多线程渲染框架,极大地提升了渲染效率,也为后续开发渲染功能打下了深厚的基础。
在2012年的第一次技术内测前,我们完成了任务系统、逻辑线程和主线程同步方式的改进,这部分对后面多人及主城优化会有非常大的帮助。
在2013年的一测和2014年的二测,我们分别针对主城和多人展开了专项优化,最终在玩家群体中确立了《天涯明月刀》性能优化比较好的口碑。
在一个大型游戏项目里是如何完成这些优化的呢?
从《天涯明月刀》的经验来看,首先要求整个团队能在一起完成协作,如上图所示,程序优化只是一小部分。对一个项目来说,优化是整个团队的事情,包括策划、美术、客户端程序和服务器程序等同事都需要参与进来。
其次,要保证有一个固定的人力来专职处理优化的问题,可以是TA(技术美术),也可以是比较有经验的程序员。
另外,游戏引擎本身也需要有非常丰富的数据收集能力,最好能从各个角度来获取一些非常重要的性能指标。
最后,我们要保持技术预研投入一些高级的技术,往往能最后获得非常好的收益。
一测主城优化
在一测时,我们针对杭州主城和开封主城做了很多优化,给玩家看到的是一个非常精美细致的场面。但最开始的时候,通过Debug(调试)视图是发现了非常多的问题,如:物件缺少合适的LOD(细节级别);物件的贴图分配不合理,屏占比比较小的物件却给了很大的贴图。
当时《天涯明月刀》项目是怎么攻克这些问题的呢?
在展开优化之前,我们会首先和地图美术组打好招呼,预留好时间安排。在优化的过程中,数据是必须要优先考虑的。在程序展开优化工作时,数据收集和Profile代码的实现和改进 花费了大概一到两周的时间。其余时间,由于之前的文档准备和其他的Profile工具比较齐全,美术就可以完全自主展开工作。这时候,程序员就可以解放出来来完成自己的工作,可以达成比较好的并行开发。
到了真正开始着手调整地图的时候,效率很高,经过两周,在地图上面的优化已经非常地明显。
前期准备阶段我们都做了哪些准备?
第一,我们开发了一整套可以自动记录和恢复相近位置的工具,并且和地图美术的同学一起针对主城的典型区域配置了大概有30套的相机位。
第二,我们制作并且完善了非常多收集性能数据的工具,其中比较重要的有PerfZone,可以在地图中划定部分区域,收集详细的性能数据。
第三,同时开始对一些先进技术展开预研,这里介绍一下DumpStats(统计输出)工具,它首先是按照时间,对于当前PerfZone里面的数据进行Dump(输出),方便美术、QA同事回溯历史并且事无巨细地把所有用到的资源按照大小、类型、出现的问题输出。这样,美术同学很快就可以针对性地修复一些数据,很容易就能把一些前期的重点问题给解决。
其次,我们也开发了一种场景热度图的技术,地图中每4米一个单位,我们保存这里的性能关键数据,例如FPS(帧率)、ShadowMap(阴影绘制时间)、Geometry Buffer时间和Lighting(光照渲染)时间,并且可以做到随着版本实时走。这样,我们可以很容易看到地图中的热点区域,并且也能随着历史回溯查到它的信息。
除此之外,从美术角度出发,我们也制作了能显示各种Debug Views(调试视图)的工具,美术通过这些工具就可以找到和解决具体问题。例如,这个视图就把场景中的高光贴图尺寸不符合和Diffuse(漫反射)贴图比例关系的物件以粉红色的方式体现出来,通过美术的调整,我们就可以把场景里面出现问题的物件全部地给替换掉。
这是另外一个Debug Views视图,它显示了物件在屏幕空间的投影尺寸和它的三角面的对应关系,越红的物体,表示它需要制作面数更低的LOD。
这张视图反映了物体的贴图尺寸是否过大,如果我们制定一个10米左右的物件,在5米摄像距离下应该采用的是512像素的贴图,那么超过这个比例的物体就会以不同的颜色来显示。
以上的这些Debug Views,能帮助美术第一时间找到出现问题的物件,然后采用相应的方法去修正。
在主城优化里,另外一个非常重要的工具就是遮挡剔除,好的遮挡剔除能极大地减少不必要的Drawcall(CPU对图形绘制接口的调用)数目,针对这个功能,我们采用的是软件光栅化的方法来在CPU上获得深度信息。
我们通过在建筑内制作保守的内包围盒来制作遮挡体,同时提供Debug视图来帮助美术判断这里的遮挡是否有效,除了显示遮挡体的深度图方式,我们也给了一个更直观的视图,这张视图显示了当前摄像机位置下的Drawcall数目以及物件类型,物件类型越多会导致占用内存的显存越多。有多少物件被遮挡剔除掉是用来衡量遮挡是否有效的,越多的遮挡体会导致越多的光栅化时间,应该控制在一定的范围内。
我们看到图像上的这个Bug模式,X轴是物件类型,Y轴是对应的物件数目,可以很直观地观察当前视角下物件种类和利用率的情况。
通过以上的这些工具,美术就可以有针对性地在不同的位置上调整模型位置或者重新制作遮挡体,以优化Drawcall数目、显存占用和绘出的三角形数目。优化之后,大部分观察点上的性能都能得到50%以上的提升。
在程序提供各式各样的工具让美术同学可以着手进行调整之后,程序这边也并行进行了一些新技术的研究,其中一个产出就是:将远景物件离线或者在线烘培成一个2D面片的Impostor(图片)。烘焙出的Impostor仍然保留Normal(法线)和高光,所以在渲染的时候仍然可以计算光照。
像上图所示,在技术内测的时候因为太远,Streaming(流式加载)物件无法加载,使得这个区域看起来十分地空。
当我们采用Impostor技术之后,这些物件就可以使用Impostor方式显示出来,由于这种技术是可以以一个Drawcall的方式完成大量的绘制,所以在效率上是非常地节省。我们可以看到这些绿色或者红色区域分别是离线和在线烘焙出来的Impostor,其中很多细节部分都可以显示出来。
回到主城的视角来看,在《天涯明月刀》里绝大多数的物件的第四级LOD都会以Impostor的方式替代,这个时候物件其实已经被卸载掉了,节省了大量的内存显存,也极大地提高了效率。这个是我们在主城优化中,使用的非常重要的一门优化技术。
总结一下,在主城优化的过程中,是离不开程序和美术的分工协同,各个团队在各自领域里面做出重要的工作;并且在整个时间线上,程序与美术可以做到协同并进。这样的话,使得《天涯明月刀》的引擎在大规模上场景设计上以最小的资源保证了最好的表现。
二测多人场景优化
接下来,将分享二测时我们是如何完成针对多人场景的优化的。二测开始前,我们了解到策划有大规模团战的设计需求,要能做到500人级别的团战。
在开始之前,我们就通过论坛、微博、QQ、直播等方式收集了玩家的反馈信息,了解到玩家在主城里经常聚集的区域,另外了解到相对一测,玩家的基础配置已经提升了不少。这也对我们采用更高级的技术提供了数据支持。
多人战斗是非常动态的环境,所以我们设计了一套测试机制,协同服务器、客户端和QA Team一起选择稳定的服务器和客户端版本,部署在几乎接近于真实的外网测试环境,测试服务器长期地在同一位置进行大量角色的耗费性能模拟。有了这样的测试环境,我们就可以采用之前提到的各种数据上的Profile工具进行调优。
首先,我们从全局角度上来考虑这个优化问题,策划同学根据收集到的性能数据和玩法设计,估算出我们游戏里面的重点密集区域,主要是主城里面的摆摊区和外贸行。它的开销主要是主城本身的复杂物件开销和多人开销。
我们把拍卖行和摆摊区的区域分别进行设计,拍卖行设计为室内,摆摊区设计为室外,这样的话通过遮挡剔除,我们会剔除掉部分进入室内的角色。
而对于野外的盟会战开销,盟会战我们优先是选择在野外地图中,而且是性能比较好的区域 进行投放,包括复活点、传送点,我们都采用类似的方法来设计。甚至在主城门口的接收区也设计了城门、城墙这样大型遮挡,来剔除进入城内的角色的渲染开销。
从程序的角度来看,关键策略是根据重要度,在全局的角度来分配计算的配额。例如针对动画更新,我们设计了一个算法,优先更新离你最近的以及最靠近视野中心的一个角色,其它的角色可以不更新或者是隔一定的帧数来更新。
另外,我们对特效的开销也做了分级,每个特效类型都被分配了一定的权重,当整个特效类型的预算耗费达到了一定的指标之后,新的特效将不再产生。我们也特别开发了一套系统,来判断主角和主角关系链上面的角色,例如同小队、游戏目标、攻击主角的人,优先把配额分配给这些角色。
其次,我们也测试了角色LOD,在多人且相当近的范围内,角色可以采用更加低的LOD级别,像这样的两个角色的多边形数目,左边的是 100%,右边的只有左边的15%,但是从远处看起来基本上没有任何区别。
所有的策略都建立在相机的位置和玩家的当前性能之上,而且各个模块可以根据当前的性能指标进行动态的Scale(扩展)。在多人团战中,使用了动态预算系统,经过调校的多人战斗有了非常流畅的表现。
由于多人场景是一个非常动态的情况,所以会存在非常频繁地加载和释放的调用,为了防止卡顿,我们针对加载创建做了非常多的平滑处理。
例如,我们给定运行时候的角色加载数目上限,我们给Streaming(流式加载)和Loading(底层加载)层控制任何Geometry(物件)和材质的创建数目,甚至包括根据移动来加载的植被,也有平滑加载的机制来防止系统在过度震荡的情况下引起卡顿和多余的计算开销。
其实,平滑开销的方法也会用在其它的系统里面,例如,我们在处理最远一级的阴影系统,计算物件之间的遮挡关系时,也是平滑到多个帧里面来完成,避免单帧计算出现的卡顿。
那么,回到底层的技术实现上,我们针对多人系统做了哪些?
首先是多个角色的GPU Instancing机制,可以在角色模型一致的情况下,极大限度地减少角色的渲染开销。
其次,因为角色身上套装量的开销,美术手动制作LOD太过于麻烦,我们也集成了自动制作LOD的工具,在角色LOD的自动生成上有非常好的性能收益。通过角色LOD的方式,可以在Geometry Buffer时间上获得一倍的数据提升。
另外,我们会把每帧更新之后的动画数据写入动态贴图中,在Skining(GPU蒙皮)时,贴图里面读取骨骼的Transform(矩阵变换),为了节省写入动画数据中的开销,我们优化了这部分机制。从CPU更新到GPU的骨骼texture(贴图)的过程中,可以看到传输过程中要做两次拷贝,
第一次拷贝是从游戏拷贝给显卡驱动提供的PCIE接口上的内存,第二次拷贝是驱动从PCIE拷贝至显卡上的显存。在最早的实现下,这些写入都是在主线程完成的,这妨碍了主线程的时间。当我们采用任务系统来更新动画的时候,我们可以选择直接在动画更新完之后写入,这样的话,把写入的开销分散到不同的线程里面完成,减少了主线程的整体开销时间,数据传输上可以减少6到7毫秒的时间,相当于FPS从30帧提到43帧。
优化过程中,我们也发现了不少的问题。从程序的质量来看,分为如下的几级。
第一级,我们认为是错误的级别。例如,错误地使用数据结构,主要是缺乏对算法和硬件架构的认识,想当然地调用函数。如在vector里面做遍历来查询,其实只要换成MAP 或者HashMap就可以获得更好的效率。另外一个是在运行时创建字符串的哈希,会导致字符串哈希计算,并且在更新字符串池的时候会有加锁更新操作
再往上一级,我们认为是Normal级别。例如,某模块里面需要每帧获得玩家的高度位置进行计算,这个操作需要获取当前玩家碰撞数据所在的三角形,根据这个三角面的Barycentric(坐标)来获取其精确的高度数据。这个级别上如果做得比较好,可以到达Good级别。
Good级别上,我们可以看看其它模块是否也需要每帧去获得玩家的位置高度,然后我们每帧可以只计算一次,当每个模块获取的时候,直接从cache里面去拿到这个结果。
再往上,我们认为是一个更高级别的Great的做法,可以真正思考下,这个玩家高度是否真的需要一个准确的高度值,我们是不是可以实现一个粗略的版本。
最后一个级别,我们认为是从产品的角度出发,能和策划、美术一起讨论问题。例如,前面提到的规划多人区域的方式,我们避免从不合理的角度来处理问题;又如,必须要求在场景最复杂的情况下达到多人场景的性能。
总结一下,多人场景优化的过程其实也是一个全面协同的结果,需要策划、美术和程序一起从产品的角度上去考虑优化的问题,自上而下地对每个环节进行优化,使得《天涯明月刀》即便是在大规模团战中,也能表现出优越的性能。