Unity VR性能优化散记
我的项目当前运行情况:Unity 2021,URP 12;
一些跟随主角移动的载具和UI,场景中一些元素动态添加;
Batches 50左右(UI,场景各占一半) ,Tris 20K;
在一个算力很低的一体机上50FPS左右;
性能目标:
一般来讲,屏幕刷新率(FPS)要达到 75 Hz 以上,人眼才不易感觉出屏幕的闪烁。
游戏进行优化,首先要确定性能瓶颈,然后针对cpu、gpu、内存进行优化,这里就需要利用性能分析工作来帮助我们做决策:
Unity Profiler:是我们比较常用的检查工具,其中GC和Time是关键指标,如果你的设备支持GPU Usage的话,可以通过Time ms 来看看GPU的耗时更大还是CPU的耗时更大 。
GC Alloc展示了每帧在Mono堆上进行内存分配的代码,过于频繁的在堆上分配内存会导致Mono定期触发GC.Collect操作,进而导致游戏卡顿。
Time ms展示了每一帧CPU耗时最高的函数,通过这项可以找到耗时不合理的代码,然后进一步对代码进行优化。这里我们有几个标记需要了解,当我们选择CPU Usage在下面选择Hierarchy的时候,使用Time ms排序如果发现耗时最多的函数是WaitForTargetFPS、WaitForRenderThread的话,就要利用Timeline再继续分析等待的线程 ,可以参照unity官方文档的profiler-markers。
当发现某个Update函数特别耗时时,没有有效的手段进一步定位问题出自该Update函数的哪一个模块或哪一段代码。可以使用Profiler提供的性能采样接口,来更精确地分析定位代码存在的性能问题:Profiler.BeginSample Profiler.EndSample()
Frame Debug:通过Enable按钮可以抓取当前渲染帧的全部数据。 了解不能合批的原因,DrawCall过多的原因。例如,同一张图集,为什么会多一个DrawCall,因为对象的材质不同。工具会提示为什么会有一个新的drawcall产生,还可以用来查看比如SRP Batcher的合批情况。
除了针对unity的分析工具 ,我的工程还有一些蓝牙传输等在java、c++部分的任务,定位的时候也可以用像adb top -H来分析线程cpu、内存使用情况,比如HeapTaskDaemon线程cpu占用比较搞要考虑GC方面。
批处理:
使用批处理技术减少DrawCall数目。批处理技术原理是减少每帧需要的DrawCall数目,即每次调用DrawCall时尽可能的处理多个物体。
动态批处理:
条件限制:进行批处理的网格顶点属性规模要小于900,如果Shader有三个属性,那么顶点数目不能超过300个。
多Pass的Shader会中断批处理。在前向渲染中,我们有时需要使用额外的Pass来为模型添加更多的光照效果,这样一来,模型就不会被动态批处理了。
静态批处理:
无论是动态批处理还是静态批处理,都要求模型之间需要共享同一个材质。如果两个材质之间只是使用的纹理不同,可以使用Unity的Sprite Atlas 或者 TexturePacker把这些纹理合并到一张图集(atlas)。
SRP Batcher:
URP或者HDRP中,相邻的Shader变体一样就能合批,可以使用不同的材质但依然能够达到合批的效果,可以写几个通用的Shader来应用在大部分场景上,这样就会有效提升性能。
减面:如果Tris数值很大需要减少三角面数,一般来说需要减面都是由美术人工操作 ,产出不同面数的模式还可以做LOD,LOD允许当对象逐渐远离摄像机时,减少模型上的面片数量,从而提高性能。如果觉得人工减面成本太高,自动化减面工具也可以得到还不错的结果,比如微软免费的Simplygon(也有收费版本),可以直接在Unity中对模型进行面数的精简,还包含其他一系列的模型优化功能。面数精简的同时会充分考虑到UV、法线,还可以对贴图进行合并。
射线:在unity xr比较常规的交互方式是通过射线(XRRayInteractor),在我的项目中因为视角再频繁的移动旋转,所以射线也会一直跟着变化,这造成了交互计算上的浪费,这里可以限定Raycast Mask,不必要的不勾选Raycast Target等方式来减少计算量 ,同时也可以考虑如果没有移动vr手柄的情况下关闭Raycast检测。
抗锯齿:开抗锯齿 MSAA x 4 效果不错但是性能影响严重,不开的话效果也确实看不过去,MSAA x2 效果,性能折中。也可以尝试像Fast FXAA高性能插件
多视图渲染模式(Multiview Rendering)即原先的单通道立体渲染模式(Single Pass Stereo Rendering:
多视图渲染模式下,可使用单个摄像机完成双目图像渲染,即:先将场景内容渲染至左眼纹理,然后将场景内容自动复制到右眼纹理上,同时自动修正顶点位置等相关参数。该模式可减少一半的绘制调用和遮罩剔除,明显提升复杂场景的帧率,十分适用于对性能要求高的应用。Single Pass 模式不支持屏幕后处理。该选项针对所有场景,全局生效。
TextMeshPro:UUGUI的Text组件把所有需要显示的字符放到在纹理中,放大字体会有毛边或锯齿,一个更好的解决方案是用TextMeshPro,不使用像素来代表字符的形状,而使用Signed Distance Field(SDF)即有向距离场作为主要的文本渲染管线来定义字符的形状,矢量文字不会因为放大缩小而变的不清晰;
可以通过不同的着色器来实现描边,阴影,发光等效果,表现更出色的同时性能也更好,同时也可以很方便的来合批;
不过TextmeshPro需要提前制作字体文件(Font Asset),只适用于可以提前确定的文字范围,如果也没有放大缩小的需求并且还是提前可以确定的,建议直接做成图片。
HDR(高动态范围渲染):纹理的每一个颜色通道的范围为0-1,所以它能够表示强度为1的光照级别。可是入射光的强度没有固有的上限。太阳是很是明亮的光源的一个例子,它的强度远大于咱们在眼睛受损以前所能感知的强度。可是许多常规光源也会产生强度超过观察者极限的光,HDR的目的是 当图形过量了 亮的部分会退化为均匀的白色 ,这看起来不太好 ,这是一个转换问题 ,HDR提供了存储更多明亮的数据(存储超过1的颜色值),并将其转换为可见的颜色。HDR纹理的颜色通道包含浮点值而不是8位值, 所以它们须要更多的内存,这意味着你仅应在须要时使用HDR,如果没有需要关掉它会节省不少内存。
VSync(垂直同步): 显示器上的所有图像都是一线一线的扫描上去的,无论是隔行扫描还是逐行扫描,显示器都有两种同步参数——水平同步和垂直同步。选择等待垂直同步信号, 及时迅速的绘制完一屏的图像,也要等待垂直同步信号的到达才能绘制下一屏,这样FPS要受到操作系统刷新率运行值的制约。同理 不等待的话 ,就不用受操作系统刷新率运行值的制约,可以发挥渲染最大能力,显示设备上的图片不会持续更新,而是定期更新,很大程度上就像 Unity 中的帧更新一样。但是,Unity 的更新不一定与显示器的更新同步,因此 Unity 有可能在显示器仍在渲染前一帧时发出新的帧,这会导致在屏幕上发生帧变化的位置处产生称为 “撕裂” 的视觉瑕疵。
所以开了游戏进程和显示器刷新率同步,使得画面更加平滑和稳定,但是会影响游戏帧率;不开可以换来更快的速度,但是可能会出现画面不连续的情况。因为它的影响会显示在Profiler窗口上,一般来说可以先不开来排除垂直同步的影响。
Mip Maps(多级渐远纹理):根据摄像机远近不同而生成对应的八个贴图,运行会加载到内存中。远离相机时,使用较模糊的纹理。使用Mip maps需要使用33%以上的内存,但不使用它会导致巨大的性能损失。优化显存带宽,用来减少渲染。因为可以根据距离摄像机远近,选择适合的贴图来渲染。对处理锯齿和闪烁的很有用,内存使用不紧张的情况下建议部分纹理使用,当然如果纹理据摄像机距离不会变化的话 换句话说在屏幕上大小保持不变的纹理,Mip Map 不是必需的,直接修改纹理成合适的尺寸也可以。
光源:光照的影响每顶点,每像素的进行计算,实时光源越少越好,可以不用实时光就不用,用离线烘焙把光照提前烘焙到一张光照纹理中light mapping在运行时根据纹理坐标得到光照结果来表现效果,Spotlight(聚光灯)开销很大尽量避免使用,可以使用Culling Mask 来取消不需要光照的层。
实时阴影也很昂贵,会增加很多三角面和计算成本,要用的话最好使用Hard Shadow并且减少Shadow Distance,不用Shadow Castcade:就是远处的阴影用分辨率比较小的贴图,近处的阴影用分辨率比较大的贴图,提升了近处阴影的质量,但增加了性能开销。
压缩纹理:这个一般是不需要我们主动去做,Build Settings里选择平台后,unity会根据每个纹理指定的对应平台的压缩格式去压缩纹理。我们可以减少纹理大小,减少纹理大小打来的收益不仅仅的内存、显存的收益 ,也可以优化渲染速度。
遮挡剔除:消除在其他物体后面看不到的物体,也就不会渲染这个看不到的顶点,从而提高性能。但是遮挡计算开销太大,不建议使用。
eyeTextureResolutionScale(URP管线的RenderScale):在渲染到摄像机目标以前,将全部内容渲染为较大或者较小的纹理。越大 显示效果更锐利。 越小 像素化很严重,经测试对性能的影响巨大,最后再考虑吧。
页:
[1]