【精华】VR性能调优,工具介绍与原理分析
2016年VR很火热的一年,但目前VR设备正处于各种探索阶段,厂商们尝试着各种类型的头戴显示设备和输入设备,VR内容也在游戏、教育、医疗等范围逐渐试水。高清无延迟的要求对硬件提出了新的挑战。在同等硬件条件下,优化使用体验只能从软件调优出发,而调优的第一步是时刻了解各种维度的性能数据,所以本文先介绍相关性能数据采集工具的使用方法。
其次,在一款好的UnityVR产品中,吸引人的很可能是各种造型独特的角色外形,还有夸张神奇的光影效果,让人们体验到现实生活中无法感受的逼真反馈。但是大量高精细的模型和多层次的光影效果又是影响性能的主要元凶。具体这些模型和效果如何影响性能,本文第二部分从原理上进行分析和诠释,最后给出优化建议。
另外,VR程序的优化不仅仅是程序猿的工作,更多是美工同学的特效实现,以及产品对游戏设计的把控。
一、工具介绍篇
Unity作为一款收费软件,除了开发过程中IDE提供的便捷,在性能分析能力上也有拿得出手的地方。Unity提供了三个不同维度的性能分析工具,从使用的便捷程度看,依次是Statistics(渲染统计窗口)、Profiler(性能分析器)和Frame Debug(帧调试器)。下面将逐个介绍使用方法和对应的数据解释。
Statistics在操作上最简便,在Game主窗口中(Ctrl+2),点击右上角的Stats按钮即可打开一个固定浮窗,在项目运行过程中,始终显示一组固定维度的数据,这是几个最具总结性的数据,能给出性能大致的档次。
其中图像性能部分的数据含义如下:
Time per frame andFPS | 引擎处理和渲染一帧游戏画面的总时间。 根据这个时间可以计算出FPS,也即每秒显示帧数。(该数值主要受到场景中渲染物体数量和 GPU性能的影响,FPS数值越高,游戏场景的动画显示会更加流畅。一般来说,超过30FPS的画面人眼不会感觉到卡) |
CPU | 获取到当前占用CPU进行计算的时间绝对值 |
Render thread | GPU渲染线程处理图像所花费的时间 |
Batches | 一帧中待“批处理”次数。 “批处理”是指引擎尝试将多个对象的渲染组合到一块内存中,以减少由于资源切换而导致的CPU开销。注意这里提到尝试二字,每次批处理能具体合并的对象取决于多种因素,比如是否使用相同的模型。 |
Saved by batching | 实际合并了的批处理数。 为了确保合并更多的渲染调用,应该尽可能频繁地在不同对象之间共享材质。更改渲染状态将会导致形成不同的组,从而不能合并调用。 |
Tris | 摄像机视野(field of view)全范围内渲染的顶点总数 点是一切绘制的起点,点组成线,线组成面。反馈了GPU的工作量。 |
Verts | 摄像机视野(field of view) 全范围内渲染的的三角面总数 反馈了GPU的工作量。 |
Screen | 屏幕的大小,以及它占用的内存大小 |
SetPass | 渲染使用的Pass的数目,每个Pass都需要Unity的runtime来绑定一个新的shader,可能造成CPU的瓶颈 |
Visible skinned Meshes | 渲染的蒙皮网络的数目 |
Animations | 播放的动画数目 |
个人认为下面4个数据最能说明问题:
FPS说明了实际用户感受的流畅度。
Saved by batching说明了实际节约了CPU调用的次数。
Tris 和Verts 则如实的告知了GPU的工作量有多大。
Profiler基于时间流记录信息。提供切换开关,能够任意查看cpu、gpu、渲染、内存、音频、physics等分析项在30秒内采集的数据。对于有些项还可以观察子项目。下面是总体效果图,具体细节逐个图分解道来。
点击顶部的导航按钮中的record准备随时采集数据,如果此时把程序run起来,就可以立刻看到数据进来。性能数据从右向左在时间轴上推进。选择任何一个片段可以查看细分数据。通过两个尖角按钮查看前后帧的性能数据。
在分析性能时可以聚焦于某类数据(比如CPU和内存),直接点击分析器的关闭按钮即可。想要找回关闭的分析器从顶部菜单选择add Profiler即可。
对于CPU分析器和GPU分析器都有很多子统计分组,这些分组的数据可以屏蔽掉不显示,也能拖动后改变右侧的时间堆叠排序。
CPU分析器下面有几种视图可以切换,Timeline时间线模式可以看到具体消耗的先后关系以及时间占比。
GPU分析器类似于CPU分析器,在底部面板作为层级显示渲染时间的细节。从层级选择的项目会显示在右边的面板细分。
渲染分析器下面显示了主要的统计数据,这些和渲染统计窗口的数据一致。
内存分析器有两种视图,一种是内存统计值,一种是详细分类。对于统计模式有下面一些项目:
Used total | 使用的全部内存。在Game视图中测试时所使用的内存比实际运行要大。 |
Unity | Unity自身在本地使用的内存 |
Mono | Mono堆内存使用情况 |
GfxDriver | 用于显示驱动程序在纹理、渲染对象、着色器等渲染所需资源的内存使用情况 |
FMOD | 用于显示音频资源在内存的使用情况 |
Profiler | 分析器自身的内存使用 |
Textures | 纹理的数量以及内存使用 |
Meshes | 网格的数量以及内存使用 |
Materials | 材质的数量以及内存使用 |
Animations | 动画的数量以及内存使用 |
Audio | 音频的数量以及内存使用 |
Object Count | 是创建的对象的总数。如果这个数字随着时间的推移而上升,则意味着你的游戏创建了一些永远不会被销毁的对象 |
在Detailed模式下,选择take sample:Editer能够显示游戏中任何对象和资源在内存中的详细使用情况。
物理分析器主要统计了场景中的刚体和碰撞体相关信息。刚体是需要进行重力计算的物体,碰撞体则时刻要侦测物体是否产生交集,都是计算量大户。
Active Rigidbodies | 当前活动的刚体数(包括正移动或仅刚刚静止的) |
Number of Contacts | 当前场景中接触点的总数 |
Sleeping Rigidbodies | 当前场景中那些静止的刚体数量。不需要物理引擎主动更新。 |
Static Colliders | 附加到非刚体物体的碰撞体数量(物体不受物理作用影响移动) |
Dynamic Colliders | 附加到刚体物体的碰撞体数量(物体可以受物理作用影响移动) |
Frame Debug也即帧调试器,可以让你看到一帧中渲染的步骤分解。按时间顺序从空白画面到画面完成中每一步的渲染事件。左侧渲染事件列表,点击一个渲染事件可以在右侧看到渲染完成效果,结合渲染统计面板,可以查看这个渲染完成时的统计数据。
这个工具可能更适合项目中3D场景美工调优,通过这个工具可以得出时间主要花在哪些元素上。
在前面的Profiler中,使用deep profile可以在你的工程中自动进行全面的分析,但是耗时可能很久,甚至Unity因此而崩溃。改用下面的方法可以对具体代码段的耗时分析。通常是工程中新加入的代码模块,比较适合定点调优。
第一步,确定需要分析的代码
第二步,在代码区块前后加上开关(参考如下)
第三部,运行项目,查看代码块的时间消耗
//前面的代码…… Profiler.BeginSample ("vrPlaneMove Update" ); // code under Profiler 用上下代码段包裹你的调测代码,并给一个字符串,就能针对这部分进行剖析。! Profiler.EndSample (); //后面的代码…… |
注意上面的代码是匹配的一对。一个Begin,一个End。包裹着你想查看性能数据的代码段。下图中标示出来的就是定点分析的代码块的时间消耗。
前面看到Unity的profiler工具只能统计30帧内的性能数据,多少有些遗憾。为了改进这个问题,专项同学经过深入分析后,开发了定制的profiler工具BigProfiler,可以统计超过30帧的性能数据,还可以导出数据进行外部的二次处理。
二、Unity渲染原理篇
计算机完成动画的每一帧都涉及到CPU、GPU、内存,显存、驱动程序的协同调用。从总体上可以使用下图说明它们之间的关系:
其中OpenGL和DirectX是GPU的编程接口。应用程序通过接口操作GPU。而这些接口进一步调用具体显卡厂商的驱动程序来真正操作GPU。
概括来说,我们的应用程序运行在CPU上,通过调用OpenGL的图形接口将渲染所需的数据(顶点数据,纹理数据,材质参数)存储在显存中的特定区域,之后,开发者通过图像编程接口发出渲染命令(如OpenGL中的glDrawElements或者DirectX中的DrawIndexedPrimitive),也就是Draw Call。它们将会被显卡驱动翻译成GPU可以理解的代码,进行真正的绘制。
GPU拿到数据进行绘制在内部是流水线操作的。应用程序中的原始数据正是经过GPU的渲染绘图流水线之后,输出一帧确定的2D图片的。
具体每个流水线的内部主要分为下面几个步骤:
顶点处理,主要是通过一系列的坐标系转换,将模型的顶点在摄像机前进行位移,并最终投影到摄像机的投影屏幕上的过程。这里对应的主要工作量可以用前面的渲染统计面板的Tris(顶点数)来衡量。
面处理,经过前面的顶点数据,得到初步的三角面,进一步把被遮挡住的面进行删除或者裁剪。这个主要对应Verts(三角面)。
光栅化,将以向量为基本结构的面转换成一个个点阵式的像素。其中有个环节叫做三角形遍历。这时候得到的结果是一个片元序列,一个片元并不是真正意义上的像素,而是包含了很多状态的集合。这些状态用于计算每个像素的最终颜色。这些状态包括(不限于)屏幕坐标、深度信息、以及其他几何阶段输出的顶点信息,如法线,纹理坐标等。
像素处理,根据片元信息集合,进行一些运算(比如模板测试、深度测试、混合)得到具体像素的颜色值和透明度。对于图像中使用了透明效果的地方会使用混合操作。所以大量使用半透明效果将增加这一步的运算时间。
性能优化策略
最后,在经过多本相关书籍整体后,对于Unity的性能优化主要有下面的建议:
大类 | 问题 | 对策 |
CPU | 过多的Draw call | 使用批处理技术减少draw call次数,比如,类似的人物模型,降低整体使用的材质的数量 |
复杂的脚本或者物理模拟(如物体的重力计算,碰撞计算) | 不必要参与重力和碰撞效果的物体可以减少,简化碰撞物体的Collider集合形体 | |
GPU | 顶点处理:过多的顶点处理;过多的逐点计算 | 优化几何体,不一定要逼真的效果,抽象的卡通风格会极大降低顶点数,但是又不失美观; 使用遮挡剔除技术; 使用球体、box碰撞体代替精细的网格碰撞体; |
片元处理:过多的片元;过多的逐片计算 | 控制绘图的顺序; 警惕使用透明的物体; 减少实时光照和阴影; 多用烘焙光照(把光照效果烘焙到光照纹理中) | |
显存带宽 | 使用大尺寸且未压缩的纹理 | 减小使用纹理大小; 纹理长宽最好是2的整数幂(如1024、2048); 使用多级渐远纹理技术; |
分辨率过高的帧缓存 | 利用分辨率缩放 | |
编码级别 | FixedUpdate等的代码建议 | 在FixedUpdate中的代码尽可能精简,该函数不受帧率影响,以固定时间间隔疯狂的调用,针对具体物体的代码可以在绑定脚本的Update中实现; 少用find、findObjectOfType、findGameObjectWithtag来查找物体; 优化数学计算,用int代替float; |
参考:
3D图形学坐标系变换
浅谈 GPU图形固定渲染管线
渲染管线
《unityshader入门精要》冯乐乐
《Unity5.x 入门到精通》
......
近期热文