VR游戏相对传统游戏,个人认为主要有三个方面的不同:玩法设计,输入方式,性能压力。今天就来谈一下VR游戏中的性能优化。
为什么VR游戏的性能压力很大?
- 主要有三个因素的影响:高帧率,高分辨率,画两遍,影响权重由高到低。
- 高帧率:DK2为75,最新的CV1是90;HTC Vive为90;PS4 VR为120。对比PC游戏的60以及主机游戏的30,压力可想而知!
- 需要说明的是鉴于帧率这么高,每一帧即便2ms的提升意义也巨大。即便以75为例,每帧时间为13.33ms,2ms占比15%!
- 高分辨率:DK2为1920 * 1080,最新的CV1为2160 * 1200;HTC Vive为2160 * 1200;PS4 VR为1920 * 1080
- 除了账面分辨率之外,实际渲染时为抵消透镜畸变带来的分辨率损失需要超采样,具体:DK2为135%,CV1和HTC Vive都为140%
- 即使以DK2的数据:1920 * 1080 @75Hz来说,每秒的像素处理量为283 millions, 这个数据4倍于一般的主机游戏!更别忘了,最新硬件的这个数据提升至457 millions
- 画两遍
- 方法一:依次画两遍场景
, SetTexture,SetTransforms,SetViewport,切换RenderState,DrawCall等均翻倍 - 方法二:依次画两遍物体
,相比较方法一有节省,但DrawCall依旧翻倍
- 方法一:依次画两遍场景
关于像素处理部分
通过上面的数据可以看到其实VR游戏性能压力主要集中在像素处理方面,那么如下和像素处理相关的部分就要特别注意:
- 光影计算方案的选择:空间换时间尤为重要,light map、静态AO,环境反射贴图等能上就上,dynamic shadow在任何时候能省则省。
- 后期处理:不用的效果统统干掉。如DOF、Motion Blur、Lens Flare等本就不适合VR游戏;SSR、SSAO等尽量用前面说的静态方案来替代;
AA也可以不用,因为已经有Super Sampling了 - 特别注意OverDraw的问题:典型的如范围巨大的透明面片特效省着点用,不要动不动叠加个7、8层。
- Shader复杂度问题:UE4的viewmode里面有一个是专门查看shader复杂度的。一般来说,出现粉色和白色的情况说明shader太复杂了,需要修正。
- early z culling:
- 原理:
- 延迟渲染已经成为各大引擎的标配,很多人觉得对于延迟渲染来讲,early z culling没有存在的必要,毕竟生成GBuffer之后相当于已经做了像素级别的culling,而且多了一个pass提前写深度往往得不偿失;但early z culling针对延迟渲染的受益部分主要在GBuffer的生成阶段,传统游戏这部分相对于lighting计算阶段开销不大,所以往往被忽略掉,但VR游戏中受制于超大的像素处理量,这部分的优化提升在我们游戏中经过测试是相当明显的。当然,世事无绝对,这里仅作下提醒,实际要根据自己的游戏场景做下详尽的测试。
关于画两遍
批次翻倍加上面数翻倍因此VR游戏中优化批次和面数较传统游戏的意义更大。
- 静态场景的批次优化:针对UE4,我们专门做了扩展工具来合并场景中相同物体的批次,而不需要美术对已经做好的场景进行返工。
绝大多数情况下,这事总是程序开发效率对美术制作效率的妥协,程序逃不掉的:) - 动态批次优化:多用instance的思想合并数量巨多但因个头小而往往被忽视的物体,典型的如FPS游戏中的子弹。
- 其实优化中很多这样的情况,比如不起眼的string对性能和内存造成的巨大的压力(当然如string相关的如此底层的优化,现代成熟引擎已经都做好了)
- 面数:对UE4而言,其消耗体现在生成GBuffer的Base pass阶段,要善用统计工具去定时定性得分析游戏场景;
- 另外关于面数除了美术提供的静态场景和角色之外一定要关注下自动生成的东西,如tessellation;工具可能也会统计不到。
- 举例:UE4中Ribbon特效的tessellation默认步长为15uu,而我们游戏中的Ribbon特效可达30000uu,如果不改变默认值,一条拖尾
可生成4000面,同屏50条拖尾就令绝大部分GPU歇菜了 - 特定游戏中特例化的问题防不胜防,应善用不同工具从多种角度分析。
- 举例:UE4中Ribbon特效的tessellation默认步长为15uu,而我们游戏中的Ribbon特效可达30000uu,如果不改变默认值,一条拖尾
其他
当然,前面讲的都是针对VR游戏的特点来重点强调的,其他的优化方法同样使用,根据之前的经验做下总结,包括但不限于:
- 对表现效果妥协,如很多手机平台的游戏角色连normal都没有。。还有贴图精度,模型精度等
- 对制作流程 、制作效率的妥协。如开发无尽之剑XboxOne版时发现,UI直接调用d3d API画的。。
- 开发效率的妥协。 注意shader中的数据类型,顶点的数据格式等,能用16位浮点就不要用32位的浮点
- 游戏类型具体分析,比如如果确定场景中物件都必须渲染则把Ocullusion Culling关掉,因此这种情况下不需要预计算遮挡剔除关系!
- 特别注意下CPU、GPU的同步点,线程之间的同步点(多发生在竞争统一资源上,如主线程和第三方库的线程用同一个内存分配器)
- 善用第三方库站在巨人肩膀,比如小内存多,分配频繁自己又懒得写内存库的话,干脆用tcmalloc、nedmalloc等
- 多用LOD,不只是贴图mipmap、模型LOD等这种,还有逻辑层面的LOD,如特效分层LOD
- 不同Actor、不同Component、不同系统设置不同的更新频率
- 多线程加速、SIMD加速
- 很track的做法:避免使用基于win32 API的高级函数,例如memeset,因为这个是单字节填充;可用汇编进行优化,效率提升明显(当然成熟引擎不需要操心这点)
其他方案
除了这些,业界还有些全新的优化方案,这里也做下介绍。
- 多/双显卡渲染:
- DX12支持显卡混搭,可把render task绑定到任意GPU上
- Nvidia的SLI和ATI的CrossFire可应付非DX12的情况,叫法不同但原理相同:一块显卡渲染左眼,另一块显卡渲染右眼:
要求两块显卡必须型号一致,实测效果很不错
- StencilMesh的思想,同样是culling,不过在另外的层面上。UE4中的实现叫做HMD Distortion Mask,实际也是节省掉周围四角区域的像素计算。
- Instanced stereo Rendering:
- 核心思想:一次提交绘制双份几何体,draw call不需要翻倍了
- UE4的4.11 preview版本已经放出了第一个版本的实现
- Multi-Resolution
- 人眼对中心区域像素更敏感,所以保持中心区域分辨率并降低边缘区域分辨率。整体分辨率降低的同时尽可能抵消对效果的影响。
这种方法可以节省25%~50%的像素处理量
- 人眼对中心区域像素更敏感,所以保持中心区域分辨率并降低边缘区域分辨率。整体分辨率降低的同时尽可能抵消对效果的影响。
补充和总结
其实真正做优化之前,有两点怎么强调都不为过:
- 稳定测试环境。包括关闭PC上其他3D程序,关闭垂直同步,保证每次采样点以及采样上下文完全一致,不要以编辑器模式启动等等。
- 量化观测数据。同一游戏,在完全稳定的测试环境下,前后两次测试的性能观测数据有有些许浮动都是很正常的,因此直觉不可靠!直觉不可靠!
直觉不可靠!重要的事情说三遍!不要想当然的认为:”这个没影响“,”那个没关系“,”这次有提升“,”感觉没作用“等等。捕获如下精确的数据加以分析才是靠谱的做法。
另外,优化是一个长期迭代进行的过程,中间过程做好记录;遇到和美术PK的情况,也要做到尽量用数据说话。