以下为演讲实录:
大家好!
我是王祢,来自Epic Games,现在在中国区卖力引擎技能支持以及一些针对中国区的技能功能的开拓。我将分享利用UE4制作大地型游戏的寻衅和优化的手段。
这是本日会讲到的总体内容,比较多一些。首先我们来看一下在移动设备上做大地型多人游戏的寻衅。大地型肯定是开放的舆图,视野比较宽,视距比较远,舆图比较大,还会有比较多的风格变换,导致绘制内容的种类比较多,资源的利用、变革比大略一些的游戏繁芜非常多。

游戏线程的优化
对付同样的移动硬件来看,优化的压力会大非常多。我们来看看优化分为哪几部分,紧张的优化包括有,大量的角色须要跟场景发生交互,角色的动画之类的打算以及与场景交互的打算发生在游戏线程,因此游戏线程承担了非常重的优化任务。以是首先我们讲游戏线程的优化。
引擎里面有一个东西,我知道这个是比较倾向于游戏逻辑业务的观点,可能一样平常大家不太认为会在引擎里面实现,我们叫做主要度管理系统,大家知道游戏的常规优化手段叫做LOD,不管是面数、更新频率,我们都会根据在屏幕上所占比进行调度,这是很通用的,沿用良久的优化手段。
我们怎么样让各个游戏模块从游戏逻辑层去改动LOD的打算?这时我们引入Significance Manager,我们会分配针对每个平台的Bucket,大家可以看到右下示意图中蓝色的小点代表玩家掌握的角色,边上的小点是别的玩家和交互的动态工具。我们根据离主角玩家的间隔,在屏幕上的尺寸或者可见性,决定利用什么Bucket。例如基于可见性的打算,虽然离我很近,但是由于在我的背后,可能很多时候我都感想熏染不到,Bucket就可以分得不一样,通过Bucket我们会用来掌握、改动LOD的各种打算。这里是一个例子,我们这个别系本身用于我们自己比较火热的游戏《堡垒之夜》,手机、掌机、电脑都可以跑,我们兼容所有的平台可以联机玩,游戏在不同平台上的场景、繁芜程度实在是一样的。
这种情形下,硬件的打算能力有非常大的差别,以是我们针对移动平台和主机Bucket也不一样,除了自身掌握的角色给的Bucket比较高,剩下的角色的比较低,主机有四个,手机有一个,这个设置不仅按平台来,也可以按设备来,移动设备好的和差的硬件打算能力差很多,我们可以在Device profile指定当前这台设备Bucket的方案。
刚才是比较全局的系统,接下来我们看游戏线程里开销最大的部分,便是我们的动画。动画系统大部分角色是可以定制的,角色会分为几个部分,绘制调用的数量、动画骨骼更新、不同部件的不同动画打算量非常大,针对《堡垒之夜》这样的游戏有一些分外的游戏模式,例如50V50,这种情形下,终极在缩圈往后,同屏会涌现超过50乃至80个角色,每个角色还分了好几个部件,背包、武器都有不同的动画,这个时候打算量非常大,我们须要对动画做非常大量的优化。
刚刚我们已经说到角色可能分为几个部分,有一些不同的策略,引擎供应各种办法,一种是将不同的部位的Mesh合成为一个,这个模型有一个问题,材质是要合并起来的,你的表情的动画就没有了,在这个方案上我们做了一些取舍,终极决定不在《堡垒之夜》用这种办法。另一种,身上不须要动画的刚体挂件可以方便的挂在角色骨骼的Socket上面,这是比较大略的办法。还有Master Slave的办法,主体动画是一套完全的骨骼,身上挂载的动画是这个骨骼的子支,这个时候我们可以把这些挂载的部件的动画完备跳过自己的动画更新打算,完备用Master驱动,这样的骨骼动画直策应用Master的骨骼矩阵,没有办法扩展,比如Master Skeleton没有尾巴或是披风的骨骼,尾巴或是披风的独立动画或者物理仿照就没办法做。针对这种情形,我们还有一个办理方案是Copy Pose,可以把主体的打算完的骨骼矩阵拷贝给附属的骨骼矩阵,只要保持目标骨骼和原骨骼的层级构造同等,就可以在目标骨骼上增加扩展性的骨骼,可以根据自己的状态播自己的动画,也可以仿照物理。这是四种多部件角色setup的方案,无论利用哪一种,都须要对骨骼模型和骨架设置LOD,这是下面提到多种优化的条件。
第一步比较直不雅观的是在动画更新的时候会有大量的逻辑事宜的打算,我们称之为Event Graph,这是UE4供应的图形化的脚本功能,Event Graph是须要经由图形化的脚本虚拟机,这个调用在动画逻辑比较繁芜的时候开销有点高,我们把在虚拟机上打算的Event Grape转到C++,省却了大量开销。
再有一个是Anim Graph,我们根据当前的状态选择不同的骨骼层级,播放哪个动画,或是经由哪些骨骼掌握节点,比如说IK、物理仿照终极的POSE的打算。在这个打算中间有一些步骤会用到数学打算,由于是在Graph,会有一些额外的开销。我们做了一些优化,我们把所有这些独立打算的模块通通纳入到一些根本的骨骼动画稠浊节点,包括偏移和缩放,这样可以减少虚拟机的调用开销,我们把这些包含大略打算项的动画稠浊节点叫做Fast Path节点,骨骼稠浊的打算逻辑通通是用Fast Path就可以完备肃清在虚拟机上的开销。
同屏有那么多的角色要做骨骼动画打算,大家知道移动设备是多核设备,为了更好地利用多核的定性,我们须要把刚刚这种虚拟机上的调用更好地平摊到不同的线程。基于上面两个优化方向,我们不要利用Event Graph,把游戏逻辑更新的部分放在AnimInstanceProxy上,这样引擎会自动判断这个Event Graph是不是可以放在别的线程上更新。如果你用了Fast Path,我们就可以把骨骼的update和evaluation都放到working thread上面去,例如有50个角色,在任一角色更新开始,就把打算分到别的线程上面,主线程连续往下走。
纵然我们能利用多线程,打算量还是非常大的,我们要减少动画更新的数据量,已经有些设置可以帮助动画在不渲染的时候跳过 Tick pose,也可以通过Singnificance Manager跳过附属武器、背包的更新,除了自己的主角,别的角色离你远一些,信息不更新实在你是把稳不到的。
我们的掉落物会仿照物理,是骨骼物体。骨骼打算有一个问题,是走的Dynamic Path。我们引擎的中的静态工具,会在加到场景中的时候就直接排序分组到自己的Drawing Policy,绘制的时候可以很大程度减少渲染状态的切换。而动态的单位,是每一帧在渲染开始的InitViews阶段动态获取到数据,它和静态获取数据的办法不一样,不会进入到静态排序的表里,绘制的效率比较低。针对这种实际每一帧渲染数据不发生变革的骨骼物体,我们把这些物体额外加到了一个StaticRenderPath,加速了这些物体的渲染。
URO(Update Rate Optimization),我们实在没有必要对所有的角色在每一帧都做骨骼打算。比如画面中一个角色的POSE上半身动作是怎么样,下半身动作是怎么样,是否须要领悟,什么频率领悟,中间是不是要插值,这些设置可以非常大程度决定骨骼更新的打算量。大家可以看到下面的图,左一是每一帧都更新;左边二是每四帧更新一次,中间用插值;第三张图是每十帧更新一次,中间用插值;末了一张图是每四帧更新一次,不用插值。大家可以看到当角色占屏面积比较小,离得比较远的时候实在是没有大差别的。
刚才讲的这些是针对骨骼动画更新的优化,实在伴随着骨骼LOD的设置,我们在AnimGraph中可以设置骨骼掌握节点从某一级LOD下不打算,比如说IK、物理仿照。
说完动画的优化,接下来游戏线程还有大量的Scene Component,Scene Component是指天下中有坐标位置的工具,它的Transform更新都是在游戏线程中计算。当你大舆图、大场景动态更新工具非常多,同时每个工具身上会挂很多Scene Component的时候,打算量是非常大的。只管我们会把Scene Component的打算踢到异步线程,但是打算量依然很大。我们做了一些改进,针对一些挂载在人物身上,不是处于激活状态的Scene Component做了自动的管理。
打开Auto Manage Attachment,对付音频和粒子殊效,可以自动根据它是否激活的状态,决定是否挂在父级Scene Component。如果Detach掉,它的Transform就不会再更新。
当Scene Component发生位置变革的时候会触发Overlap的检讨,每一帧有大量运动工具时会产生大量Overlap事宜,耗费比较大的开销。优化的原则是尽可能把不须要产生Overlap的事宜关掉,把稳引擎默认是打开的。我们对层级构造比较繁芜的做了子Component是否打开overlap事宜的引用计数,会看自己是不是打开了Overlap事宜,以及自己的子工具有没有打开。这个时候我们在做Overlap检讨的时候可以很快地跳过,这个节点往下都没有,就不须要再检讨自己的子节点,这在场景的工具构造比较繁芜的情形下是可不雅观的优化。
Character Movement,由于角色比较多,角色的移动更新是非常大的游戏线程的打算,针对这个打算,一部分是角色在移动的时候要检讨新的位置是不是能站立,要做一些扫描,要做一些碰撞,还要找落脚点是不是斜坡,这个斜坡的斜率是不是角色可以站上的,往前走的高度变革是不是可以超过跨过阶梯最大的高度,角色一多打算量就非常大。以是除了玩家自己掌握的角色,须要比较精确的打算外,别的角色分到的Significance Manager的Bucket我们终极是用了插值,通过网络同步过来的位置做大略的插值来仿照预测打算,在大部分时候都不随意马虎把稳到明显的差异,只有在帧数较低或者网络带宽受限比较严重的时候,对付落地点会有显著的偏差,大家可以比拟看到这两个视频中左边是预测打算,右边是插值。
Physics,我们会尽可能地用一些替代的Physics优化物理注册的工具,有一组工具,比如说边界,不须要很细致的碰撞模型,我们可以用大略的volume来表达物理碰撞工具,减少注册到物理场景中的工具数量。物理的一个场景会有两个树,一个用以做Query,一个用于做Simulation,我们要尽可能担保注册进去的工具最优化。因此须要尽可能的简化每个物理工具的繁芜度,以及减少全体场景注册的物理工具数。可以同时以比较小的内存开销打开异步的物理场景,Physics注册的工具是一样的,只不过他会用Shared Shape的办法加到Async Scene里,这样在场景做物理仿照的同时,他可以在异步的scene里做其他的query。
其余我还考试测验过把同样mesh的不同实例工具用Shared shapes减少注册的物理工具的内存开销,在内存敏感的场景下也可以考试测验。还有一个思路是我们可以把物理工具和视觉工具解耦,默认的情形下,引擎的Mesh工具打开碰撞就会注册物理工具到PhysX Scene,增加了物理场景的繁芜度和物理的内存占用。因此当你的Mesh加载到内存里,纵然不被渲染出来,这些开销就在了,但是实在很多情形下视觉会看得更远一些,实际须要物理打算交互的间隔在有些游戏中没那么远,我们可以用一些手段把视觉上工具的物理关掉,把这个物理属性转到一些新的Component和Actor上面放到新的Streaming Level里,用更近的加载卸载间隔来管理,这样实际的物理场景繁芜度和内存占用都会小很多。其余移动真个布料,打算量和网格数量干系,在移动端会不太推举利用那么繁芜的仿照,引擎也就没有供应移动真个NvCloth的lib,以是我们一样平常会用刚体来仿照。
Ticking,也即所有动态逻辑更新的工具,引擎的图形化脚本可以让美术策划和GamePlay程序很方便的在Event Graph做Tick更新,但是须要付出一定虚拟机的调用开销,当Tick事宜触发的实行行列步队非常长,每一帧付出虚拟机的本钱就会比较高一些。一个方法是转到C++,其余一个方法是减低Tick的频率,更有一些分外的,例如每一帧只是视觉上在迁徙改变的风车或是旗帜在飘、树在摆动,实在可以不须要用骨骼动画、或者在Tick做旋转,可以用顶点动画来做。
引擎还有个功能较TextureStreaming,这个别系会在游戏线程打算用到贴图的精度,用以决定更新给渲染线程的资源的精度再提交给GPU,对付这个每帧剖析画面贴图Wanted Mip的打算量每帧还是占比较多的,游戏线程急急的情形下可以降落Texture Streaming的剖析打算的频率。
UI,如果游戏HUD有大量的UI工具,它的位置打算会比较繁芜,在游戏线程的打算量就会比较大,可以多利用我们新出的SlatLayoutCaching和Invalidation Box来Cache Prepass减少widget transform更新的打算,这些Cache可以把打算的位置和大小记录下来,有一些可以把顶点Buffer Cache下来。其余,我们也须要只管即便让UI的Widget可以Batching起来。引擎的一些布局空间会自动帮你布局子控件,例如Horizental和Vertical Box,Grid等,这时候子控件是在同一层上,引擎会优先Batch起来。当利用比较灵巧的Canvas Panel时,会导致引擎默认的行为会把每个加入的子空间的Implicit Zorder自动增一,这时候如果你确定这些子Widget不重叠,实在可以手动掌握这个ZOrder。当然Batch的条件还是你用了同样的材质和贴图。那么如果做一个背包界面,里有很多不同东西的图标,我们又希望这些图标有一些殊效,我们可以用同一个材质,这只同一个Texture Altas,针对每个子控件设置不同的Vertex Color,在Vertex Shader里通过VC的值做为uv来使得这些子控件可以被Batching起来。
音频和殊效,音频是比较大的开销,我们之前的《堡垒之夜》又是从主机到移动端兼容的项目,为了优化音频在移动真个开销,我们增加了做了很多设置,使得在移动端不同的设备可以设置不同的SoundCue并发的数量,以及SoundSource的数量。个中SoundSource默认在移动端上总数是16个,主机上可能是32个。大略解释一下什么是SoundCue,这便是原始的SoundWave资源拿过来做一些实时处理封装后的音频资源,
例如可以在多个SoundWave中做一些随机、拼接,以及一些声音效果的实时处理,这些处理效果对打算量哀求比较大,我们可以针对不同的硬件设备做一些LOD的设置,比如说在比较差的CPU移动设备上,可以把Reverb,EQ等关掉,或者减少随机的Wave的数量等。
Particle比较显著的开销是Overdraw,我们在PC上有自动把贴图的Alpha切割出八面体,减少Overdraw的功能,但是这个功能之前在移动端无法利用,最近我创造实在只要支持SRV的设备,是完备可以用这个功能的,移动端上也可以打开。
Level Streaming,为什么用Level Streaming?实在道理很大略,由于场景非常大时,我们不可能把所有的场景加载到内存里面,这时候我们可以把舆图拆得非常碎,每次只加载视距内的一小部分,使得内存的占用变得比较低。这样一来场景在内存里的东西比较小,场景遍历的开销也会比较小。同时也可以在设计上增加场景可利用的物件的种类,丰富了场景的繁芜度。全体Level Streaming统共分为三个步骤:
IO,这一步我们是放在Worker thread做的。第二个步骤是反序列化,在启用Event Driven Loader后,IO和Deserialization可以并行,个中反序列化也可以由打开s.AsyncLoadingThreadEnabled放到异步的ALT去做。末了一步是Postload,这个有很多时候须要对游戏线程注册工具,须要在主线程做,在引擎里可以用Time Slice的办法分帧异步来做,同时,对付PostLoad中某些不影响游戏线程的行为,我们也挪到了ALT里,很大提升了Level Streaming的效益。
做事器,实在刚才针对客户真个优化,都会惠及做事器的优化。在新版本中,我们加入了Replication Graph,在集中的类里做了ServerReplicateActors的打算,总体思路便是减少PerConnection,PerActor的relevancy以及priority的打算量,通过把Net Actor注册到以空间位置划分的grid中,每次针对当前Connection只检讨所在Grid内工具的信息来大大降落全体Replication的打算量。其余,对付不同Connection见的部分工具,我们也会Cache下来须要replicate的数据结果针对别的connection复用。这个改动优化使得在我们的项目中我们做事器的全体CPU用以做replication的开销降到原来的1/4。
其余一些做事器优化手段有,这是降落所有工具Net relevancy distance的间隔;把以移动的RPC包做优化,如果连续的几个移动方向和速率是同等的,可以把几个移动RPC包合并起来只发一个,减少网络带宽的占用和包的序列化等打算量。
做事器我们也可以关掉大量动画的打算,只在播一些分外动画的蒙太奇的时候才会打开动画的更新。在Server上也可以把一些只关注渲染视觉和实际游戏逻辑打算没有关系的Component在Server上去掉。
渲染线程的优化
好了,看完大量游戏线程的优化手段,接下来我们来看看渲染线程,渲染线程的第一个开销取决于场景的繁芜度,纵然实际绘制出来的内容很少,但是场景遍历的开销却是正比于场景在内存里的Primitive数量的。如果我这个遍历韶光很长,那么实际绘制调用发出的韶光就会比较晚。这个时候,我们就要利用好Streaming Level来最小化Scene Tranversal的开销。其余,动态的工具每一幀重新获取要绘制的渲染数据,也会有不小的开销,同时也会降落静态工具的渲染状态排序的上风。这也是上面提到过的加入了分外的Static Render Path的优化手段的缘故原由。
场景遍历后的大头是是Culling,包括估量算的Precomputed visibility Volume,场景针对每个场景的可见性,不是特殊大的舆图比较适用,在runtime险些没有开销,tradeoff是离线打算的韶光和一部分内存。然后是并行的视锥文体剪和基于间隔的裁剪,都是很常规的Culling手段。移动真个occlusion是比较头痛的问题,我们在支持ES3.1的设备上,利用了Hardware occlusion query,在3.1以下的设备我们供应了一个Software occlusion的办理方案。当然要把稳这并不是万能的,有些情形下还多了绘制的三角形面数及大量bounds transform的CPU开销,却没有实际occlude掉什么工具。
剔除完就到了终极头的开销来源:Draw Calls,减少DC的手段多种多样,譬如引擎供应了刷foliage的工具,对付石头、树之类大量复用的工具,用这种办法刷出的HISCM,会做gpu instancing大大减少DC数。然后一个有用的方案是HLOD,可以把一组Mesh乃至是一个关卡合并成一个Proxy Mesh,在最低级LOD后,可以切换到这个合并的Mesh,大大的减少远处物件的Draw Call并依然保持很远的视距。HLOD依然可以做多级的LOD帮助进一步减少DrawCall和减少面数,这些工具都是引擎内建,可以很方便支配自动化。
Dynamic Instancing,我们有一些分外的方案,针对腾讯的Studio也做了一些整合,接下来的引擎版本会有非常大的渲染pipeline的重构,会对这个有更天然支持,乃至支持带光照烘焙的Dynamic Instancing,在光照图打算的时候就把可以instancing到一起的工具优先并到一张光照图上。
其余一个和DrawCall开销息息相关的是渲染状态切换的数量,引擎里有个靠近的观点叫Drawing Policies,刚才说静态的工具我们会按Drawing Policies分组排序,现在的版本中,我们针对这个分组排序的规则做了一些改进,可以更好的减少渲染线程的渲染绘制调用的状态切换,同时也一定程度兼顾gpu的overdraw。刚才说到的新的mesh draw command pipeline要到今年年底,明年年初才上线,在目前的测试场景中,对付渲染线程的优化,可能有近十倍的改进,当然终极在移动端上表现如何还不能下定论。全体新管线的思路是尽可能使得渲染线程在cpu端没有什么开销的,场景资源管理等的开销都在GPU上。
RHI Thread,在OpenGL ES上,GraphicAPI的调用必须和glcontext在一个线程,于是,我们把所有的gl command都enqueue到了一个叫RHI Thread的线程,这样一来,实际渲染驱动的开销和引擎渲染线程的事情就可以有一部分并行化,减少全体渲染的frame time,以及变向降落渲染线程所在核的主频,这样可能在部分设备上还能减少一些功耗开销。
卡顿优化
讲完渲染线程,我们来看看Hitches,卡顿紧张分为四块。
Loading,加载,当量启用streaming level异步加载往后,如果游戏逻辑发生了壅塞加载,由于引擎并不知道加载数据的依赖性,以是会导致引擎Flush异步线程,造成卡顿。个中普通游戏逻辑触发的加载我们可以比较随意马虎的察觉并改正,但是另一个情形是在网络同步的时候,当做事器第一次同步回来一个新的Actor时,客户端会创建Actor Channel,并须要实际Spawn Actor,可能会依赖壅塞加载的数据,进而导致flush造成卡顿,我们可以通过打开net.AllowAsyncLoadingEnabled,使得触发的加载变成一个异步加载,并且这个Actor Channel的创建过程,也会加入一个pending的行列步队,等到加载资源都到了往后的那帧才可以实际的创建。
Compile Shader,由于ogl es没有固定的shadercache标准,引擎供应了ShaderCache,在新版本中改进成了ShaderPipelineCache的功能,该系统可以在离线环境下先跑一遍游戏,在这个过程中用到的Shader,绘制的状态记录都会在Log文件中。Runtime的时候,我们会先读log,分一些批次预先Compile完以减少runtime发生compile的情形。其余,一旦compile,可以合营另一个ProgramBinaryCache的功能,引擎会把link完的program保存下来,往后再须要加载Shader的时候,如果创造这个link program存在,会直接加载program。这样不但能省去compile和link的过程,还跳过了shader code的加载过程和节省了内存。除了compile,这个cache系统还会做warmup,也便是预先绘制,以减少第一次利用的额外开销。
Spawning,降落spawn的开销一个是减少每个components的数量,再者,尽可能用C++的Component。如果你是BP components,引擎项目设置中有一个选项,可以在cook的时候把components的序列化,初始化的结果存下来,spawn的时候直接拿这个数据做实例化就行了。然后Component注册到游戏线程可以做分时。当然最常规的减少spawn卡顿的方法还是做pooling,如果有大量同类型Actor的Spawn,建议这样做。
GC,紧张分为两步,先是引用剖析,然后剖析完标记可以destruct的工具会在这时开始发出BeginDestroy,而实际的Destroy会分幀去做,由于有些工具渲染线程的资源还在访问,不能当场删掉,以是只是发出一个render fence,渲染线程回收掉,我们才不才一帧主线程purge的阶段把工具删掉。在全体GC过程中最费的,是引用剖析,由于这个必须在当前这幀做完,新版本中我们把标记和引用剖析都做了多线程并行,利用所有的核打算,可以比较好的提高引用剖析的效率。还有一种手段是可以跳过大量的常驻内存的工具,我这里列了一个参数,MaxObjectNotConsideredByGC,设置这个参数范围内的工具是不会在引用剖析的时候做检测的。再有一点是Clustering,一组工具永久是共生的,可以方案在Clustering里面,这样的场景下GC效率可能提升十几倍。末了新版本中,我们把BeginDestroy也放到的发生GC的后一帧去做。
GPU优化
解析下来是GPU。
渲染分辨率,我们可以逐设备地通过MobileContentScaleFactor设置BackBuffer的分辨率。我们也可以通过r.ScreenPercentage把单独的3D的分辨率改小。改分辨率是显而易见提升GPU的手段,由于大部分时候我们都是pixel shader bound。当然,带宽也是很大的成分,引擎还可以灵巧的设置SceneColor的格式,默认HDR下我们利用FP16的RGBA,在有些项目里我们可以用r.Mobile.SceneColorFormat来调度成R11G11B10或者RGBE的办法减少带宽的占用。当然要把稳,移动端有些特性一来DepthBuffer,而支持DepthStencil fetch扩展的设备并不算太多,以是引擎默认会把Depth存到SceneColor的A通道,以是采取R11G11B10这样的格式,可能就会使得某些依赖读回深度的feature发生问题。
材质,也便是shader繁芜度,我们可以设置Quality Switch利用不同繁芜度的材质针对设备做优化。也可以直策应用fully rough,non metal之类的材质优化选项。当然滥用的话会使得最终生成的shader permutation的分裂数量很多,须要把稳一下。
Shadow,紧张分为两种。Modulate shadow我们已经不太适用,不过由于是单工具一个shadow volume,以是可以设置的shadow map利用率和精度比较高一些,在某些角色展示场景中可能比较有用;CSM是全场景的动态shadow,非全动态光照时,移动端默认只对动态工具投射。可以通过Device Profile掌握,例如可以在低端设备上没有shadow,中等的设备上可以不做PCF filtering,好的设备上才开filtering做多次采样。
Landscape,我们在近期版本中也做了一些改进,不同层LOD的打算以前是根据间隔,现在改成根据屏幕占比,顶点shader的打算量会小很多。其余现在新的版本中移动真个材质不再受三层的限定,当然三层的时候,两个weightmap和normal共享一张贴图,依然是比较优化的情形。地形本来占屏范围就广,采样多的话pixel shader开销很高,以是还是只管即便推举利用三层以内的稠浊。
Base Pass pixel shader,效果上我们做了一些改进,sky light和refleciton的打算都做了改动,Specular换成了GGX,以前GGX在半精度的情形下,NoH靠近1时会有比较大偏差,我们做了一些改进。其余,在MobileBasePassPixelShader中的各个模块,项目组也可以根据须要去除不须要的,例如IBL或者lightmap或者shadowmap的部分。
后处理,可以根据不同的设备做不同功能的开关。
Mask,在移动硬件上比较费的缘故原由是由于如果写depth时,某个像素发生clip/discard,硬件的earlyz就会失落效,导致overdraw。一个方案是开启prepass画mask,basepass做z equel;还有一个是引擎的LOD transition,在发生LOD时,不是直接换模型,会把两个LOD模型都画一下,通过一个dither的mask逐步的渐变过去,这个时候可以采取类似于mask的行为,我们可以把LOD的结果dither的结果画到Stencil,在BasePass时做stenciltest减少不必要的discard。
内存优化
接下来我们讲讲内存。
内存我们针对不同的设备,独立于其他的优化选项,单独有一组Bucket设置,可以针对不同设备的可用内存决定自己利用的Memory Bucket设置。
除了Streaming Level,引擎还有一个内建的很强大的功能是Texture Streaming,刚才已经先容过一些,IOS上的实现利用了Apple的GL扩展,安卓有些设备没有扩展,我们可以做完全的贴图资源抛弃和重新的创建。在cpu上根据物件bounds的屏幕尺寸×材质中用到的对应贴图的uv scale系数×一个可以由美术tweak的scalar值来决定实际贴图提交的mip数,可以用r.Streaming.PoolSize在不同设备上很方便设置全局的贴图资源的内存Budget。
Shader code,我们会利用Shared Shader code的功能,将大量静态的分裂导致产生的Shader有重复的去除,将实际的Shader code存入ShaderLibrary,在每个MaterialInstance工具上只存ShaderCode的GUID,大大减小了实际的ShaderCode大小。在有些项目里可以减掉80%。其余,不该用的rendering功能一定要在项目设置中关掉,可以大大减少shader分裂的组合数量。
RHI,UI的贴图比较大,由于默认情形下贴图资源被CDO(Class Default Object)引用住无法GC掉,可以用弱引用技能的办法来缓解这个问题。其余,Slate altas Size可以小一点,可以减少冗余的空掉的贴图内存。GPU Particle不用的时候可以把fx.AllowGPUParticles关掉,我们会用到两张128位1024的RT存gpu particle的position和velocity,有将近60兆的大小。其余,FSlateRHIResoureceManage,FrenderTargetPool里polling起来的资源,可以应时主动调开释的接口,以减少之前用过,之后短期内不会用到的资源。
其余,近期我们还创造在利用UniformBuffer的时候,在一些gles的驱动里会有非常可不雅观的内存开销,因此我们现在改成了在ES3也会用pack过的UniformArray的形式。
还有很多比较散内存优化点,碍于韶光关系,这里就不展开细说了,例如在clang下TCHAR是4字节的,我们改成了二字节,也把干系的字符串函数做了一些自己的实现。
引擎关于适配和迭代的设置手段
这是引擎大量依赖的scalability系统,引擎所有可以掌握的属性,都可以放到Scalability Group,引擎内建了一些分组,我列在这里了,项目组也可以定义任意的分组,每个分组里面可以有我们不同的参数掌握,合营有继续关系的Device profile系统,可以很方便的针对不同的设备利用不同的scalability设置,单独可利用的设置项非常多,可能有上千个。
下面的这个Device Profile的例子是iPhoneX,大家可以看到iPhoneX的设置是继续自IOS高配的并做了一些override,而ios高配又继续自IOS,而IOS继续自移动设备的Profile,一个项目可以适配任意多的硬件和平台。不同的Device Profile的选择依赖不同平台的Selector,安卓上可以根据正则表达式或者严格匹配等方案去匹配SoC,GPU Family,Device Module或者GL Version等。
再来我们看下项目Iterating的步骤,数据转换过程我们叫做Cook,cook分为两种办法,一种是你设备跑起来的时候,设备上是没有资源的,设备的资源访问不是访问本地,而是访问网络磁盘,编辑器的一个commandlet会作为server端持续供应你要访问的数据,这个数据如果没有经由转换会先壅塞的cook完再发过去,迭代的时候非常有用,叫cook on the fly。还有一个是把资源全部转化完发得手机上,在不-iterate时,纵然资源不改,也会先都load出来再save回去做检讨。项目大了会用良久,如果资源变革了,在DDC(Derived Data Cache)中找不到,须要发生资源转换的过程,则会更慢。当用了-iterate后就会跳过这个步骤,但是有时候依然会load+save,是由于ini文件发生了变动,引擎不知道这个变动会不会影响cook结果,只能重新load/save,这时候引擎有一些优化选项,可以让你配置一些分外的字段见告引擎,当这些字段发生变革时cook也会不做检讨,例如项目版本号之类的字段。当迭代测试的时候只要改变启动命令行参数的时候,可以push一个UE4Commandline.txt文件到设备上,就可以免除重新打包的韶光。
Debug没什么好说的,新版本中,为了加速迭代,我们开始利用Android Studio做debug,可以同时debug native和java代码。当native代码改动后,可以在vs里编译,UBT会自动更新build.gradle,使得Android Studio会自动识别并更新,改完后直接去android studio中启动就能debug了,不须要再打包了。
Profiling方面,gpu上细节的profiling紧张靠移动gpu厂商工具;其余引擎有大量的内建的工具,例如常用的stat系列的命令以及showflag系列命令可以快速帮忙定位问题,cpu的profiling,引擎有自带的工具,近期还加入了第三方工具framepro的支持,可以以很小的overhead做基于namedevent的profiling。我们也正在和腾讯互助,在做一些新的Profiling工具供大家利用。关于内存的profiling,引擎也有一些Memreport和llm的命令和对应的Memory Profiler工具赞助检讨内存的利用状况,以及查找内存透露和优化的方案。
本日要讲的便是这些,感激大家。
from: www.u3dchina.com/forum.php?mod=viewthread&tid=8676