VR沉浸体验的要求
以下为演讲实录:
大家好!今天我来这里主要介绍的是使用UE4做VR内容的一些优化技巧。VR需要给大家一个沉浸式的体验,这要求两点:一点是你的画面要尽可能的精美,另外一点是你的呈现要尽可能的流畅。
这两点其实是相互矛盾的,因为你画面做的越细致、越精美,其实你的内容就越重,你的渲染压力也就越大。你要做到75帧双眼同时渲染难度就非常高。
我们看用UE4引擎帮你做了多少事情,第一个已经早就完成了。引擎的输入更新并不是只有一次,传统引擎输入可能是游戏线程一开始有个输入,把所有这帧的输入都获取。获取完之后游戏线程再计算游戏逻辑然后送给渲染线程绘制。这种情况下,假设我们游戏跑在60帧,最后更新的HMD或者其它输入设备的最后数据,其实都是要有16毫秒左右的延迟。
最简单可以做的第一步是引擎在把数据送给渲染线程之前,我们会把整个渲染的View Matrix重新更新一遍,游戏逻辑是按照这一帧刚开始收集到的数据来计算。视点其实是通过游戏线程的逻辑线程几乎已经结束后送给渲染线程来计算的。
为什么我要这么说呢?因为引擎本身是多线程的,现在很多显卡都有双缓存或三缓存的。我们绘制的这一帧,你看到的帧都是前几帧的内容,这样给你带来的延迟是非常重的。少则也有40来毫秒的延迟,超过10毫秒延迟,你就很容易,稍微长一点时间就有不适感。这也是我刚说的,要降低延迟,首先要把渲染的多缓存关掉,也就变成了本来可以多线程渲染的,很多时候需要线程等待游戏逻辑线程的输入计算完成才能做这一帧的数据渲染,这其实又对你的渲染压力造成更大影响。
我们第一步做的是在逻辑线程计算都结束以后重新更新了ViewMatrix。这个是对头显设备和输入设备都做了同样的操作,包括动作捕捉设备、手柄类的设备,你最后都要捕捉到它的动作。为了让延迟更低,所以我们做了一个多次的Deferred的输入更新。
第二个是我们最近刚做完的一个Demo。这里有两个功能,一个是叫Instanced Stereo Rendering,一个是叫HMD Distortion Mask,我们最近测试基本上能在我们的测试Demo上效率提升将近1倍,而且纵观其他市场上所有支持VR的引擎,还没有一家在使用这样的技术。
HMD Distortion Mask,简单介绍一下。大家都知道通过HMD设备镜片畸变以后,其实图像输出之前是先要反向畸变的,反向畸变后四周其实是黑的,根本不输出内容的,现在大部分引擎的瓶颈也都是在Pixel Shader(像素着色器)或Fragment Shader(片段着色器)上面,如果我们能让这些黑色的区域都不去计算PS(像素着色器),黑色的那些区域其实都不经过我们Pixel Shader的运算。其实就能省掉很大的开销,所以我们做了这么一个优化。
Instanced Stereo Rendering,这个功能简单来说,我们VR的输入在引擎对接,一个是获取你的ViewMatrix矩阵,在每帧更新时从头显设备获得输入的ViewMatrix矩阵。另外一个是使输入的视点往两边偏移同距的宽度,来做立体渲染。立体渲染最简单的做法,引擎的渲染因为已经是定的,比较简单的做法就是渲染两遍,左边眼睛渲染一遍,右边眼睛渲染一遍,带来的开销就是DrawCall(绘制调用)数翻了1倍。大家做手游可能对DrawCall都很敏感,尤其是国内的情况,因为国内现在500块钱的手机已经算挺好的,主流的开发者面对的是一两年前的500块钱的手机,现在主流论点还是手游的DrawCall要控制在150以下。
我这里随便介绍一下,我们Epic做的一个Demo在iPhone5和5s上就能跑得很流畅,跑到30帧的一个Demo。在AppStore上可以免费下载,叫《Zen Garden(禅意花园)》。这个Demo我们主要秀的一个技术就是苹果的Metal的API,跟现在DX12,OpenGL Next就是Vulkan,包括之前AMD推的Mantle,这一系列大家的主流想法都差不多,包括Console(主机)的那些,比如说索尼一贯的API其实都是这样,我暴露给你更底层的控制,API的Level更低一点,你自己在引擎里面去做Draw和Batch的优化。CPU上的开销就会降到更小。一些做渲染的API,比如说DrawCall这样的API,开销不单单是在CPU上,GPU的开销也非常大。这也是为什么移动芯片会对DRAW CALL那么敏感,开发游戏会要求大家把DRAW CALL限制在很低的程度。
使用了这些新的图形API以后,对底层控制更细致以后,我们可以做很大范围的优化,比如说我刚才介绍的《Zen Garden》Demo,最极端的情况下,保持30帧的情况下,渲染的DRAW CALL数可以到5000、到7000,这是非常夸张的数值,在iPhone5IPhone和5s上能够跑起来的应用。这里Instanced Stereo Rendering解决的也是一个类似的问题。
在大部分图形API还没有更新到下一代之前,DrawCall那么敏感,怎么解决这个问题?大家考虑一下双屏立体渲染的时候,视点做那么小范围的偏移,我们绘制的内容几乎是相同的,我Camera看到的东西基本上差不多,尤其是场景里的Mesh之类的Bounds一般都会超出一些,周围两个视角看到不一样的东西,但都是需要绘制的。这个时候做Instance的思路是什么?我这两个绘制并不是完整的绘制两遍,而是把其中所绘制的Mesh做一个Instance。也就是说Mesh数会翻倍,而顶点数将近减半。
大家用过UE4可以知道,我们做大量草木渲染的时候,做大量同样物件摆放的时候,我们会用到叫Instanced Static Mesh Component这样一个东西,这个东西可以有一大批一模一样的东西,它只记录每个Instance的Transform的Matrix,也就是说我只记录它的Position、Uniform的Scale和Rotation的这些信息,每个Instance只记这些信息,因为它用到的顶点信息都是一样的。这样就可把一批东西并在一个DrawCall上。双屏渲染就是把两屏的东西都看成一个Mesh的两个Instance,这样就可以把本来翻倍的DRAW CALL减到跟以前一样。假设你的场景本身已经有了大量Instance的情况下,其实图形API的Instance和Index Buffer也是有上限的,所以在这种情况下,可能Instance的效果不是很明显,但大部分情况下,在座的各位普通情况下做到的应用和游戏里面,这样基本上都能提升50%以上的效率,这些技术现在是只有我们支持的。
我们现在正在做,可能在12月中,或者到明年初就会提供的功能,这个跟AMD还有Nvidia合作的,大家知道Nvidia做VR的GameWorks里有一些功能,其实非常实用,包括多GPU的渲染,还有一个比较有用的多分辨率的渲染Multi-Resolution,因为我们知道人眼都只对你关注的,看的中间位置比较敏感一些,所以其实屏幕中间的分辨率渲染的更高一点,周围的渲染更低一点,这也是降低我刚才说的Pixel Shader瓶颈问题,跟刚才的HMD Mask有异曲同工的妙处。
这里是引擎内建已经有的内容,对于不管是第三方的硬件商,HMD的接入商,还是对内容开发者来说,都是大家需要了解,但不需要关心的内容,这也是底层已经尽可能的帮开发者做的一些优化。
下面我主要要讲的内容还是开发者自己本身需要用到的一些内容。为了方便起见,我们自己做的VR Demo都是经过深度优化的,我来剖析的话,前期的那些项目工程,我这里其实也已经没有了,所以剖析的效果可能不是那么好。因为我拿到的数据都是优化后的数据,我这里先随便拿一个Demo来做一个简单的剖析。
大家在MarketPlace上都可以看到,是Particle(粒子)的Cave这样一个Demo。展示一些粒子效果,粒子本来就是很费的东西,这个Demo不是为VR准备的,所以这个Demo在VR设备上跑起来,帧数非常低,可能只有三四十帧。
我们来看一下大概的一个优化思路是什么样子的。当然第一步很简单粗暴,先看看瓶颈在哪里。这些命令对于使用过UE4或者稍微有一些Profiling经验的人肯定是第一步要做的事情。既然要优化,就要把力量花在你该花的地方,第一步肯定是查找你的瓶颈,看看是CPU Bounding还是GPU Bounding。第二步Stat fps就是看FPS,如果FPS已经满足了,当然进一步优化总是好事,但是也没有必要,因为毕竟优化也是要花费人力、物力的,已经满足你的目标,就没有进一步优化的必要。
如果没有满足,接下来Stat unit这个东西主要有三个数据信息,大家能看到这里有一个是Game,一个是Draw,一个是GPU。Game是游戏逻辑线程这一帧所耗费的时间。Draw也是GPU端的,是渲染线程所耗费的时间。GPU就是GPU耗费的时间。从这里就能推断出你是CPU端的Bounding还是GPU端的Bounding,是逻辑线程的Bounding还是渲染线程的Bounding。
对于VR内容,我个人认为以现在的硬件来看,CPU逻辑线程Bounding的可能性几乎为零。如果是CPU逻辑线程Bounding,一定是哪里的实现或设计出现了严重的问题。因为VR体验里不会有非常重的逻辑运算。
我看到那么多VR Demo里面,逻辑运算最重的应该是我们自己做过的一款叫《Bullet Train》的Demo,里面有非常大量的AI,有寻路,是一个类似第一人称射击的游戏,有各种设计的弹道都是ProjectTiled的实体,而不是一个射线检测,所以动态运算量,CPU的运算量会比较大,但即使是这样,CPU还是很富裕。跟传统游戏来比,我们的电脑CPU已经非常强了,VR对于逻辑的要求,其实跟普通的大型游戏来比是要轻得多的,所以CPU的Bounding,如果是逻辑线程的Bounding,一般来说意味着哪里代码写的有问题。
比如说大量是静态的对象,你可能现在是动态的在Tick,然后Tick里面你做了一些,比如说非常长的空循环之类的不合理的行为,这个东西其实通过我们的Profiler可以很容易一眼就看出来。
我今天要讲的大部分都是GPU的Bounding,一个就是我刚才说的,如果你Draw Call偏高的话,其实是同时渲染线程Bounding和GPU Bounding都会发生。因为有时候渲染线程是要等GPU的,你看到数字,这里可以看到GPU是28毫秒,总的Frame时间也是28毫秒,毫无疑问就是GPU Bounding。因为这里面用了大量的Particle和大量的半透,而且是比较暗的场景,肯定Translucency会占的比重比较大一点,这个色调肯定也是后期处理可能占的比重也比较大一点。
一般来说GPUBounding的更大的可能性都是在Pixel Shader上面,我刚才说的这两个都是影响Pixel Shader的,这是第一感觉看场景以后的。接下来我们来看看具体用什么比较快的方法能定位问题,然后解决我们的问题。
第一步能做的,这是一个命令行HMD SP 100,我们的HMD接入的设备大部分都有这样一个Debug的命令行,叫HMD SP,SP(Screen Percentage)是屏幕的比例,相当于是输出分辨率是固定的,但Render Buffer的分辨率可以调整,100%以后,你会觉得画面有点糊,不够精细,因为眼睛看的是当中的,所以一般我们默认值是120%,如果你的渲染帧数有富裕,甚至调到150%也是可以的,而且这个命令是实时生效的,你可以在游戏过程中,根据不同的关卡,甚至不同的进程,Camera的位置,来控制参数调整到不同的值,可以在运算比较富裕的时候,展示更精细一点的效果,在优化压力也很大,没有办法做进一步优化的情况下,让画面稍微变的糊一点,让帧数有比较好的提升,这个效果是非常明显的。
大家看到经过调整以后,我们的整个一帧的渲染时间,已经变到了18、19毫秒,刚刚是28毫秒,已经减掉了三分之一,非常非常的明显,说明就是Pixel Shader的Bounding,很明显。一般来说我们判断是Pixel Shader瓶颈,还是Vertex Shader瓶颈,最简单的做法就是设置分辨率。HMD的输出分辨率固定,没有办法设置,所以我们提供了这样一个Debug命令行,你通过这个调整,就能马上知道,首先能确定是不是GPU Pixel Shader的Bounding,是这个Bounding的话,然后来看影响有多少,而且这个值,虽然理论上是越高越好,但是其实有时候也跟内容有关系。比如说我内容色调不一样,可能对锐度、精细度的邀请也没有那么高,有时候100完全可以接受,甚至我们有些Demo就是放到90的,大家有没有感受?有什么不对的地方。这个就是根据项目来,对你来说优化压力比较大,你可以把这个参数放小一点,这是一个最立竿见影的调整效果。
接下来这个是Editor里面内建的,我们是调整整个渲染Quality的Scale Ability的设置,本来默认都是开在Epic上面,Epic是最牛逼的画面效果,我现在先把它调到中等,一下子13毫秒,为什么是13毫秒?其实是因为我们限制了帧数75帧。事实上,如果我们把帧数限制关掉以后,发现可能更短,可能已经能跑到100多帧,这是一个最快的效果。大家看到已经到75帧,是不是满足我们优化的效果了?可以说是,但肯定也不是,因为这是以牺牲大量的画面的渲染效果为前提的。做这样一个设置,只是为了让我们快速的定位到哪一档差不多的设置能满足我们的帧数要求。
接下来要做的就是比较细致的分析,到底哪些地方有问题了。有一个Profiling的命令行叫Profile GPU或GPU Profile都一样,这两个命令行的快捷键是“Ctrl+Shift+,”,这个东西在Editor里面,或者在游戏Runtime都可以实时的运行,你只要输入这个命令,我们就会做这一帧的GPU Profiling。我是推荐大家不要在编辑器里做,起码出一个Standalone的进程来做这件事。因为编辑器Slack等各方面也都是要经过渲染前的开销的,这些东西可能会误导你对Profiling一些开销的判断,所以最好是在Standalone的Game里面来做这样一个Profiling行为。
做完后大家能看到,比较大头的几个,一个是Base Pass,一个是Deferred Decals,一个是Lighting,然后是SSR(Reflection Environment),然后是刚刚说到的半透(Translucency),然后是一个后期处理效果(Postprocessing)。最厉害的是Base Pass和Postprocessing。渲染管线相当复杂,我这里不会更细的阐述,因为大家制作内容可能也不是那么关注引擎底层的细节。
我先简单说一下基本的概念。我们默认是把Pre Z Pass关掉的,如果你看到Base Pass高,引擎提供了很大的便利性,我们有一个命令行,可以打开Early Z Pass,这样会影响DRAW CALL,因为有些东西会先绘制一遍。带来的好处是Base Pass的开销会大量降低,这个需要你根据自己的场景去尝试。你看看是打开Early Z Pass总的GPU开销来的高,还是关掉来的高。在这里会发现打开Early Z Pass会带来很大的收益。
大家看到之前是总共一帧用掉了12毫秒,优化后只用掉了8毫秒,另外几个Decal,我们看你的镜头所在位置,首先这个跟你的VR设计内容有关系。如果你看到场景在移动,你自己没有在移动,尤其移动速度比较快,而且不是线性情况下,人很容易晕,现在很多做VR内容的开发者都从设计层面上解决这个问题,当然我们有可以移动的设备,但是这个设备对大部分人来说,还算是不太实际的设备。从设计上规避,我们尽可能的站在原地不动,然后用Teleport。甚至我可以提供一个思路,比如说我要移动到另外一个地方,比如说按键或者手柄各种操作以后,我看到一个虚拟的Avatar。我站在这里,我看到一个第三人称的角色往那边移,Camera并没有移动到那边。然后我把某个键松掉,然后人就Teleport过去。以这样的形式来呈现就可以避免晕眩。
在这种情况下,我们大部分的时候视点都是固定的,这时候会发现很多位置的贴花Decal等,在屏幕上占的象素非常低,这种情况下可以把它去掉。我们知道用这种技术是为了提升画面效果,但有些时候你需要做一些取舍,比如说这个东西带来的提升不那么明显,那你就可以去掉。Translucency也是一样。
很多粒子特效,尤其是糊到你脸上的东西是非常非常费的,半透的尽可能不要使用。然后就是后期处理,大部分后期处理肯定都是全屏的都要处理的,所以尽可能的减低后期处理,我们在大部分的Demo里面都是没有使用后期处理。
这几个优化完之后对帧数的影响非常明显,我们刚才已经看过了调整分辨率是会很大的提高帧数的,说明是Pixel Shader的Bounding,我们做的这一些包括Translucency和Decal,这些大部分都是影响了提升这一部分的效率。这是优化完以后,优化完以后帧数就已经到75帧了,这是因为刚说过这里限帧了。其实现在已经120帧,甚至150帧了。
我们把画面配置效果,刚刚调过去了,忘调回来了,我们现在调回来,好像依然有75帧,就是说刚刚优化起到效果了。一开始我们做这样一个设置是降低画面质量,提升帧数,在我们进一步分析以后,现在我们能把画面效果再重新调回来,依然达到比较流畅的帧数。
另外还有一些手段,比如说用我们内建的编辑器里面的一些ViewMode,就能看到比如说这里的Shader的复杂度,那些有大量半透的粒子,有一些贴花的地方颜色都会比较深。我们的这个ViewMode里的绿颜色就说明Shader是比较简单的,红色就是说明Shader比较复杂,粉色就是非常非常复杂,白色就已经复杂的一塌糊涂了。一般来说,我们场景都会控制在红色和绿色之间,如果发现你有大量的粉色和白色,Shader肯定已经太过复杂,一定要做比较针对性的优化。
下面的Stat scenerenering,这个也可以看总的DRAW CALL数量,我们最近在970上可以跑到75帧的Demo,我刚才说的叫《Bullet Train》的Demo,视野最开阔的时候DRAW CALL有1500多,其实已经非常高了。最近CryTek刚刚出来一个恐龙的那个在Steam上免费,那个DRAW CALL数比我们低,当然顶点数很高有大概8M,它的Index Buffer也很高,因为它是大量的草木环境,所以它用了巨量的Instance,但是在其他的环节上都是我们的数据要高的多,因为我刚刚说到的,我们做的那个Instance Rendering和HMD Mask的优化,所以使得我们能有比较好的效率,能在大量渲染的负担更重的情况下,能有一个更流畅的效果。
Stat Scenerenering就可以看到我们比较细致的一些渲染的Profiling数据,甚至你可以通过,比如说有很多Particle,甚至有些有比较复杂的UI,又会有一些Decal,一些Static(静态的)和动态的Mesh,,你来第一时间衡量,哪些东西对你的渲染造成最大的影响,你可以先把这一帧的渲染停下来,然后我们有一个Show Flag,可以把每一类我刚说的东西去开关,来看看这些东西对DRAW CALL和帧数的影响。
例如,如果你发现Static Mesh一关,好像DRAW CALL影响从1500降到了可能只有3、400,说明基本上所有的开销都是在Static Mesh上面,可能需要场景美术去做更大的优化。如果也就降到8、900,说明你有很大的开销在其他上面,你去优化场景是得不偿失的。
ViewMode、Wireframe能看到我们的网格信息,这里你能看到,比如说比较远的地方网格非常密,大家知道,如果一个三角面片渲染在2x2象素以内是非常浪费的。我刚才说的在设计上,我们很多内容都是视点固定的,既然在这个视点看到的内容离你那么远,密度就完全不需要那么高,这时候你可以尽可能的对模型做一些简化。
刚才我说的这是一个非常快速的例子,就是拿一个实际的例子来讲,我随便拿到一个Demo,用什么样比较快的方法能优化到一个可以在VR里面跑起来,导致你不晕眩的一个效果。实际上如果你真的做优化,其实有非常多的东西要来关注。
我接下来说的东西可能更细致一点,但是总体思路跟刚才一样。我们先是寻找限制你帧数的原因。测试之前,先要找到一个稳定的环境。我们在做GPU Profiling的时候,我希望不要在编辑器模式下跑,希望你在Standalone的Game里面跑,或者你即使是在编辑器里面,你也用编辑器去跑一个Standalone的进程,然后来做Preview。你在测试的时候,因为Camera移动,你的Oculus Update的信息都是不一样的,你可能会针对一些,比如说某个角度帧数特别低,你可以把游戏逻辑停下来,然后把渲染也停下来,这时候你可以用Debug Camera去看看你调的内容,以及不应该被绘制的内容是不是被绘制了。
刚刚我也说到还有一点,我们有一些帧数设置,你可以去把限制最高帧数的设置先打开,因为在我们降低整个渲染效果以后,很容易就到上限了。所以在做优化的时候,希望先把帧数上限去掉,然后把垂直同步关掉。
这些都是在判断问题和调试之前要创建的一个比较稳定的环境,当然包括你的目标硬件有没有在跑其他程序,这些都是有关系的。我们希望在一个比较稳定的环境下,你最终的目标环境下来做这样一个测试。
这个我刚刚大概解释过了,就是用Stat unit可以看到这几个模块每一帧耗费的毫秒数。这个就能看出来是GPU Bounding,而不是因为我设置了帧数的上限。因为如果我设置帧数上限,那一定是Game这里的毫秒数最高,因为Game会去每一帧判断是不是达到上限了,如果没有达到上限,我还可以Sleep,所以我主线程的时间会跟整个Frame是一样。如果Game跟Frame一样可能不是游戏主线程瓶颈,第一点先查看是否限帧。如果达到目标帧数,又是Game和Frame数值一样,基本上就是你限帧的关系。
SceneRendering这里能列出最敏感的信息就是DrawCall,可以看到这里的DrawCall数量。DrawCall数量多的原因上面说了,计算公式基本上是Mesh数量乘上ID再乘上投影的灯光数。
一个比较简单且高效的优化手段是这样,我们提供了一些统计的工具在编辑器里面,你打开Statistics统计面板,按照使用次数排序,能看到被你使用最多的Mesh对象是哪一个,你看一下它的ID是多少,如果它的ID是负数个数,你有很大的可能性,可以做一个非常快的优化。
比如用到了30个不一样的Mesh资源,其中有50%是用了同一个资源,比如说我就渲染这样一个场景,很有可能大量的都是椅子,我们先不说椅子做Instance的情况,假设每个椅子是一个独立的Static Mesh Component的一个Actor,我发现椅子用了三个ID,相对来说我让美术把ID合并一下,假设这个Mesh占了一半,本来1500个DRAW CALL,其中有800DRAW CALL是椅子的,我改一下,这800里面的三分之二就省掉了。并不是说让美术盲目的判断Static Mesh造成的影响太大,我就让美术把所有的Static Mesh,场景里面的东西能合并的就合并,并不是这样。一个是Instance这样的手段,一个是通过使用频度来减少它的ID,优先减少频度使用高的对象的ID。
另外你Staionary的灯光,或者动态灯光,尽可能的减少,并且能不要投影的就不要投影,并且减少Overlaid(覆盖)。
这里就是我刚才说的计算公式。这个命令行跟我刚才说的另外一种方式功能差不多。判断一下有没有什么东西出错的,这个命令行叫VisualizeOccludedPrimitives,绿色的框就是说这些被Occlude掉没有绘制的Primitive对象。如果你发现有对象是在它后面的,但是并没有被绿色的框画出来,那就意味着有一些东西不对了。一般来说是什么不对?一般来说是你的Bounds设的太大了。比如说尤其是对于Static Mesh这样的对象,如果你不是Root Motion的一些动画,很有可能我人站在这个位置,从这里移动到那里,其实我的Root没有变化,这个时候我的Bounds自动就会变的非常大,最终的结果,我人明明站在某建筑后面,被遮挡掉了,但还是在绘制它,引擎认为这个东西是可见的。这些情况下,需要美术注意,可以通过这样的命令行来调试发现这样的问题。
另一种方式是,你Freeze住这一帧的渲染,用Debug Camera去漫游这个场景,看看从你漫游之前的Camera位置,需要被遮挡掉的那些对象是不是还能绘制的出来,如果绘制出来,一定是我刚才说的比如说Bounds设置有问题。这个是跟我刚才说的配合起来,ToggleDebugCamera可以在Freeze Render那一帧情况下,跑到它的背后去看看被遮蔽的东西有没有能绘制出来,绘制出来了,就说明有问题。
还有就是ADB和bounding box的计算,如果你的东西不是LocalSpace有旋转的话,其实它的Bounds也会变大。尤其是长条的物件。
然后一个比较费的就是ParticleParticle,通过Stat Particle能看到Particle的详细信息。能把一些特别费的Particle尤其是靠Camera比较近的,半透比较多的,利用率比较高的那些做集中的优化。有一些比较离的比较远的,大家可以多做一些Particle的LOD。远的那些其实不太敏感。我们知道GPU在做立体渲染(Stereo Rendering)的时候,离屏幕近的那些ParticleParticle很容易穿帮。因为离视点越近,视差看到的位移就越远。一般来说,我们的游戏引擎里面的Particle都不是一个实体3D空间的对象,其实是类似叫Billboard的东西。它永远朝向你的Camera,当两次渲染的时候,它永远是朝向你左边一个Camera,右边是这样的,并不是实际在中间,视差到两边的例子。离你眼睛越近越大的Particle,在3D渲染下错误越明显。这时一般会用一些创造性的作假手段,比如说用一个实体的Mesh来做这样的Particle,我们也有很多Demo来演示比较有技巧性的Particle。比如说爆炸,比如说一些烟雾轨道,我这里就不着重介绍了。
我刚才说的这些就是为了说明,对屏幕影响比较大的Translucency这些Particle,大部分情况下都可以转化成Mesh的形式,并不一定要半透。这就是我刚才说的我们的一个统计工具,静态场景的统计工具,可以按照上面任何一列做排序。顶点数最多、使用数最多等等,按使用数最多排序,你能看到这个Mesh用了多少ID,是不是需要做进一步的优化。
还有一些比较Tips(技巧性)相关的功能,比如说可以选中一组对象做同样的优化。比如说这一组Mesh都要做同样的优化,比如说这一组Mesh对东西的影响都比较小,我都不希望它接受Cast Shadow,或者说我都希望统一对它做某一些优化参数设置如LOD设置等,一个比较简单的办法,场景里面选中它,右键可以选中所有使用这个Mesh的对象,选中后你可以打开一个叫Property Matrix的东西,这时候你可以勾选自己需要用到的属性,勾选了属性以后,那个页面上会把横向是所有你选中的对象,纵向是你选中的属性,以一个Matrix的形式列出来,你可以很清晰的看到,你同一类对象的参数属性。因为当美术不停的往里面放了很多灯光,有些属性调整,可能自己也记不清楚了,可以通过这样Matrix的工具来很方便的把所有你关注的对象的属性都列出来,很明显,一眼就能看出来,而且可以做批量调整,非常方便。
这就是我刚才说的一些Showflag,用来判断哪些类型的Primitive的绘制对象,对你的帧数影响比较大。这个我刚刚在Showcase示例里面也讲到了,比较方便的,一开始就可以用来调整的HMD的调试命令行。
其实比较主要的,一个是SHADER的复杂度,一个是灯光,一个是后期处理,还有一个是投影。有很多高级的优化技巧,比如说,我们尽可能把Reflection Capture减少,不让它们Overlaid,甚至把整个场景的SSR都关掉,在《Bullet Train》Demo中就关掉了SSR,但是你能看到很明显,在火车站的大理石石板上,人和广告灯箱牌是有倒影的,那些东西是我们自己做的材质,做了一个模拟SSR的材质,这样就相当于打开逐对象的SSR计算,就可以省去很多不必要的开销。包括我们在《Showdown》用到的一些Cast Shadow的技巧,我们把大部分的Dynamic Shader都已经去掉了,通过一些简单的Blueprint的控制,比如说人脚步离地面的远近,来Scale他脚下黑色的阴影面片大小,这样可以很好的满足视觉效果。
我刚才介绍引擎默认的有一些Viewmode,可以方便的看到Shader复杂度、贴图密度、灯光密度,引擎各方面的开销都可以看到。另外一些比较次要,但是对帧数影响也比较明显的showflag,这些开销非常大,但是有些时候完全可以舍弃掉。在我们的Demo里面大家看到画面效果非常好,但是我们很多时候都不用后期处理和动态投影。
这里也是我刚才说的一些,我们在《Showdown》里面用到的,右边是太糊了,可能看不太清楚。右边是我们《ShowDown》的设置,这是我们比较推荐的后期处理的设置,尽可能把所有东西都关掉,右边的设置就是在《ShowDown》Demo里面用到的设置。
GPU Profiler是更细致的每个环节的列表。
除了刚才说的这些是技术手段上的内容,还有更多的是大家需要在制作之前就制订一些规范,比如说你最终的目标是跑在什么样的硬件上面的,你美术制作的时候,在一开始可能做一些Protype的时候,设置一些美术的标准,根据这个标准来做,后期再做比较统一的优化,这样效率可能会比较高一点。
VR既需要更好的效果,又需要更高的流畅度,但同时VR本身对你的开销又更高,所以对优化的要求就更大。在刚刚那些内容优化,我们都找到了以后,接下来更细一步的就是美术重新去做美术资源了,来做LOD,做远处的剔除不必要的东西。也是一些我们用到的渲染参数调整优化。
我们后处理的一些设置。大家能看LPV(一种动态GI的solution)这种东西肯定不能开的。AA,我们基本上也可以不用,所有的AA基本思路就是超采样,但因为都是通过采样定律来做的,要么是时间上,要么是空间上的。如果Screen Percentage已经可以提高了,对你来说就不需要开AA了,比如说已经150%了,自然而然就已经AA了。大家知道为什么Retina屏幕的手机有些基本上不需要开AA,就是这个原因。这是引擎内建可以做渲染参数调整的一些设置。时间关系,后面的一些设置我就先带过了。Shader材质的一些统计数据。这里的Pixel和Vertex Shader的指令数。通过调整Shader来做一些优化。然后灯光尽可能不要Overlap。
最后我想说的是,你总是要先定一个目标,如果你的目标机器是非常差的机器,怎么样做优化,都不可能满足你的目标除非改变设计初衷。如果你的目标就是非常强的Titan X或980等,很多时候你也不一定需要做有优化,包括有时候你要认准你的机器,我们很多时候开发可能用的是工具性的比如至强的处理器,主屏可能比较低,CPU的核数比较高,我在编译开发环境上有优势,但是引擎做渲染的时候,优势比较差。有时候你会发现有影响,甚至CPU Bounding怎么回事,随便换一块正常的游戏用的i7 CPU马上就解决问题了,根本不需要做什么优化。其他的就是设计上的考虑,基本上就是这些。
谢谢大家!