The world at your fingertips — 天涯明月刀幕后14(性能)

作者:顾煜 专栏: 2018-05-09

https://zhuanlan.zhihu.com/gu-yu

前文回顾:The world at your fingertips — 天涯明月刀幕后13(无垠)

裁剪

大地形技术带来了很好的视觉冲击,也缓解了美术制作效率的问题。

但我们眼睁睁看着游戏的帧数从60变成30,然后继续下探,一路冲低到10来帧。在Editor中,因为需要加载整个地图,所以只有几帧的效果,基本已经没有办法工作了。

在游戏中还好,因为有streaming系统的存在,我们并不会加载太远地方的场景。

但随着大地形效果进一步提升,老于就开始动视距的脑筋了。当时我们的游戏视距并不算近,也要上百米,但不足以表现开阔的场景。老于各种旁敲侧击,威逼利诱,让程序员改大了Streaming范围,同屏可以看见更远的场景。效果的确很好,大家都很满意。

但当时美术同学还在艰难的施工中,场景还是空空荡荡的,所以引擎还可以跑。随着施工进行,各类外包的美术资源返回,被整合进了版本,不出意外,内存首先扛不住了。

由俭入奢易,由奢入俭难,既然已经看惯了大视距,就再也没办法接受近视距了。我们也不用和老于商量了,他自然还是会要求尽可能保持大视距。我们开始着手优化。

这些年和很多国内开发者接触过,很多团队中程序员有着极高的地位,程序员表示不能做,策划就会屈服。程序员表示要优化美术资源,美术就得苦命的熬夜修改。究其根源,还是在于产品方面同学缺乏一些技术理解力,无法和程序同学有理有据的争辩,程序同学也缺乏一些担待,没有负起该负的责任。

但在天刀项目,老于有充足的技术理解力,且口才好,我也不想去和他争辩,以免自取其辱。程序团队也好强且有能力,被美好的愿景驱使着,尽力探索技术边界,给美术和策划创造更好的开发环境。

内存不够就分批分优先级加载,场景中的物件并不都是需要的,根据物件大小、远近和优先级,我们尽量少加载物件,但保留最重要的地标性建筑。细心调整后程序员放出一大堆参数,基本可以在保持远视距的同时,合理的偷工减料,既减少渲染,也减少内存,且维持不错的整体观感。

当然这类工作说起来容易,做起来就不那么简单,依赖美术程序的共同努力,程序先是熬夜找方法,思考如何合理的取舍。即使有了基础功能支持,美术同学不用删减素材,但在地图上做合理的标记,依然是逃不掉的工作量。好在这样做,美术有一个更崇高的目的,所有的工作不是为了让游戏变得更差,而是让游戏变得更好,所有的努力,都是为了尽可能保留更远的视距,虽然还是一样的加班,但心情想必会有所不同吧。

进一步可以做的事情,是如何更好的管理场景裁剪。大型引擎的裁剪分层,在开始的时候都会使用类似四叉树之类的技术,做一次高层面的场景管理,第一时刻剔除不需要的物件,不给渲染管线造成更多的麻烦。天刀引擎开发一直遵循精益的原则,场景管理自然不是第一时刻需要做的事情,更何况网游的streaming机制,天生保证了关卡中的物件都是按需加载,不会有太多的物件。。但美术大规模开工以后,这事情不做不行了,编辑器并不是streaming的,一次加载了所有的内容,即使在高端电脑,帧数也很快就低到不能忍了,影响了美术的工作。

场景管理并不难做。但想想游戏运行的时候并不需要复杂的场景管理,只为了editor做一下也不太甘心。

这时候,我们发现引擎技术中心的深圳团队中,Milo老师做了高端的组件,专门处理visibility的culling。这个组件思路和Dice的Battlefield用的裁剪有点像,通过软件光栅化,模拟一下物件渲染,当然是用很简化的Bounding Box模拟,然后根据遮挡关系,算出物体的遮挡关系。

比起Dice技术,Milo的技术比较好的地方在于,Dice的技术是在PS3的SPU上算的,效率极高,但PC上并没有SPU。所有高端的技术都有逆天的优化,Milo用了SSE做了深度的优化,在Core Duo的机器上也只需要1-2ms就可以算完。同时Milo针对我们游戏的大地形,加入了新的地形裁剪,可以遮挡更多的物件。

肯定有人会说,GPU也有类似的裁剪功能,何必重造轮子。相比GPU的Occlusion Culling,Milo的CPU裁剪库也有巨大的优势。因为GPU的culling,结果会慢几帧,会带来架构上不必要的复杂性,有一些问题不好处理,对新进入显示区域的物体,需要各种复杂的处理。而CPU端的Culling,就没有这些限制,即裁即用,这样我们的整个渲染和逻辑pipeline,都可以依赖Culling结果,做最精准和暴力的裁剪。

我们赶紧整合这个裁剪库,瞬间提速2倍,大量物体第一时刻就被裁剪系统丢弃,不影响后续的渲染管线。

然后我们就发现,有了这样的系统,我们似乎并不需要传统意义上的scene management系统了,runtime的时候有streaming,控制加载的场景和物件数量有限,渲染的时候用culling,大幅度减少不必要的物件,编辑器中通过culling后,可以做各种激进的优化,而且编辑器中很多物件买的逻辑不用跑,计算量本就可控,这样看来,四叉树之类也不需要加上,简化了整体架构,也降低了运行时对四叉树的管理成本。

进一步看,我们可以利用这个裁剪做更多的事情,阴影的渲染,生成CSM的阶段,水面的反射,也需要裁剪,也可以用上这套裁剪系统。

各种逻辑的Tick,可以考虑可见性,不可见的,过一段时间就不用再tick,这也可以用上裁剪系统。

这套库提供了一个通用的解决方案,简单优雅,快速稳定,简化了架构,提升了性能。

收集

精益开发的自制引擎,没有太多时间做图形化的shader编写系统,material系统都是程序员预先写好Shader,美术开发调整一些参数为主。开关各种不同材质特性主要靠uber shader。

uber shader会面临收集shader的问题,因为uber shader本质上就是一个巨大的shader,通过各种宏定义,来生成海量的不同shader。

我们有不同的shader参数组合需要考虑,每一组不同的参数,经过宏处理后,又产生了一个新的shader。这个新生成的Shader,需要被编译。如果这个shader在在引擎运行时刻编译,就会Block整个pipeline,造成了卡顿。通常会有500-1000ms的停顿,这自然完全不可接受。

当时并没有太多精力管这个事情,美术每天催功能,哪有时间细细调整这一块。

最简单粗暴的解决方法,就是预编译所有用到的shader。Tough哥根据shader参数组合,穷举,在引擎load时候全部编译shader并加载。这造成了很长的预加载时间,好在只有第一次sync大量代码才会有,后续就不会有了。

但随着代码规模量的扩大,美术的材质参数变多,穷举显然不是一个好的方案,编译时间越来越长,已经无法忍受,占用内存也越来越多。于是Tough哥打了些补丁,过滤掉一部分不可能出现的材质组合,很大程度减少了需要穷举的数据集,美术们又愉快的工作起来。

好景不长,临时的方案撑不了多久的,是时候好好解决一下这个问题了。

不太完美的方法是引擎第一次用到这个shader参数组合的时候,进行后台编译,用另一个线程去编译shader。虽然我们初始化D3D设备用了单线程,但编译shader并不受影响,可以用另一个线程同时处理,只涉及到CPU的计算。当shader编译完后,进行cache,这样后续再用到就可以直接读取cache了。这个方案的缺点主要在于,在第一次遇到这个shader并编译的时候,因为shader在后台编译,并没有准备好,所以相关的物体并不能被渲染,需要等几帧后才可以被应用到,画面上会有一些artifacts。

以往做某个Xbox360游戏的时候就是用这个思路。第一次遇到新shader就编译,画面上出现各种丑陋的色块,一会后台线程编译完成了,就替换成正确的shader,进行标准的渲染。显然这个只能用在debug和dev阶段,正式版本是没有办法放出去的。

为了解决这个问题,我们需要在版本发出之前,尽可能多的玩游戏,收集各种游戏中实际会用到的shader组合。每次遇到一个shader,我们就记录参数到Log文件。主机游戏有着较大的测试团队,每天下班前20多个测试人员打了一天的游戏,把所有人机器上的Log文件都发给我,我写了个Vim里面用的脚本,直接合并所有人的Log并去重复,就得到了当时版本的所有shader组合。这个方法并不完美,但考虑到一个单机小体量的游戏,20多个人,每天可以通过N遍,连着玩几天,shader参数也就基本收集完整了。

可是天刀项目太庞大了,而且没有那么多测试人员,我们没有办法依赖暴力测试,来人肉穷举版本中的所有shader。而且我也不高兴再去人肉合并所有的shader参数Log文件,太麻烦了。

路都是人走出来的,憋急了自然有出路。我需要有一个机制,能从开发团队的日常工作中来收集信息,且需要尽可能自动化,不要影响我自己的工作。另外我需要大量的测试人员去跑游戏,跑地图,这个在当时的团队里面基本没法做到。前者的解决方案自然是靠程序自动化来做,后者解决方案是尽可能发动所有的开发人员来做,且不能影响大家的日常工作。

我写了一个小Server,这个程序只做一件事情,所有开发版本的游戏,启动后都会连接到这个服务器,进行数据通讯,把我需要的数据发送过来 。在这个应用案例里面,我让每台开发机器把自己用到的shader组合宏全部发到我的服务器上,每次用到了新的shader参数,也会通知我的Server。在这个小服务器里面,我就可以做各种自动化的合并、去重复工作,这样版本放出去后,不管是测试人员跑版本,还是美术在编辑器里面工作,还是程序在开发渲染效果,他们的工作,都会上传用到的shader组合,对大家来说没有任何影响。团队工作两三天,我的小服务器上就收集了整个项目所有开发人员用到过的所有shader参数组合。虽然还是有可能不完整,但从外网版本的实际情况来看,已经收集了相当多的量,基本覆盖了99%的情况。

还需要解决的一个问题,就是无效shader参数的退出机制。每次用到一个shader组合我们就记录下来,长此以往,肯定shader参数越来越多。偏偏天刀的shader并不稳定,程序员还在不停修改版本,开发特性,总有不少参数组合是会老化废弃的。于是我又给参数组合一个生命周期,记录了最后一次使用的时间。如果一个组合很久没有用到了,就丢弃之。

有了这个系统,我们每次正式发布版本前,就把这个server上的参数组合导出,版本按照这个组合预编译一遍所有的组合,cache下结果就好了。

最新评论
暂无评论
参与评论

商务合作 查看更多

编辑推荐 查看更多