Optimization Tips for Mobile VR experiences

Good performance is always a key factor to ensure users have a comfortable VR experience. In this article, we share useful guidelines and tips to help you optimize your application for Mobile VR platforms.

The target framerate for Vive Focus Plus applications is 75 FPS. This means that each frame must be rendered in 1/75 = 13.3ms.

Mobile VR is quite demanding versus PC-based VR as standalone headsets are not as powerful as Desktop PCs and Laptops. Considering the computational limitations of Mobile VR platforms, it is essential to optimize your application while developing it to ensure an optimal performance as it’s quite challenging to try and improve the overall performance of your application after the development is done. Of course, don’t try to optimize too early before you notice any performance issues.

First thing to do: Before you try to improve the performance of your VR application, you must identify where the bottleneck is. Use the Profiler to get CPU/GPU performance metrics and identify the bottleneck of your VR app. Once you find out it is CPU-bound or GPU-bound you can plan for a better optimization strategy accordingly. Make sure that you profile your application while it’s running on your target hardware (e.g Focus Plus) using adb or Wi-fi.

CPU Optimization

  1. Reduce draw calls

    • Generally, try to limit each frame of your application to a maximum of:

      • 50,000-100,000 triangles or vertices
      • 50-100 Draw Calls
    • Decrease the number of objects that should be rendered (renderers and materials)

      • Reduce the camera draw distance by reducing the far clipping plane of the application or disable objects farther than a specific distance. Use a fog effect to hide the fact that distant objects are not visible.
    • Draw call batching

      • Dynamic batching: For small Meshes of the same Material (normally < 900 vertex attributes, or 300 vertices), Unity will group them and draw in one call.
      • Static batching: GameObjects that don’t move/rotate/scale can be set to ‘static’. Unity will then combine them to a big mesh and save draw calls. Be careful as this can lead to higher memory usage.
    • Texture Atlasing

      • Combine the textures used in the scene into a single texture. Using this technique objects that share the same textures become eligible for batching.
      • Note that if you are using Unity NGUI, you may need to reorder the UI layers to prevent more than one UIPanel being used.
  2. Physics calculation

    • Physics calculation takes considerable CPU resource. In most cases, not all layers need to consider Physics interaction with every other one. You can check the matrix table in Edit > Project Settings > Physics, and disable the interactions that are not needed by establishing what should collide with what.
    • Raycast is an expensive operation and its performance depends on the length of the ray and the type of colliders in the scene. We suggest using “Layer Masks” to filter out the unneeded target layers, and only keep specific ones.

GPU Optimization

  1. Use Mobile simplified shaders instead of standard ones

    • In Unity, find the simplified shaders under “Mobile” category. These shaders were specifically designed for Mobile platforms and can provide significant performance gain. However, please note that some shaders support only one directional light, and some do not support alphaTest or ColorMask.
  2. Lighting / Illumination

    • Real-time lighting and reflection probes are expensive. Avoid using them if possible. For many cases using baked lightmaps + GI provides a similar or even better result, with much less GPU computation since runtime calculations are not required anymore.
    • Be careful about shadows. In QualitySettings adjust the options to fine-tune performance and quality, especially Shadow Distance.
  3. More recommended practices

    • Use Fixed Foveated Rendering (FFR) to save GPU load. FFR is a performance improvement feature that renders with a lower resolution in the peripheral area while keeping the same pixel density in the central part. Because of the anatomy of the human eye, the impact to display quality is not obvious. The Wave SDK provides an API to set FFR on different levels. You may adjust the effect intensity according to the actual condition.
    • Try using the Adaptive Quality (AQ) feature. When AQ is enabled, it will dynamically adjust rendering quality according to CPU/GPU utilization. It can also be combined with the Dynamic Resolution feature to adjust the image quality of the application according to system changes and with Fixed Foveated Rendering to dynamically change the quality of the peripheral region when improving the performance is essential. This results in better battery consumption management and smooth frame rates.
    • Downsize particle systems, re-design artwork, meshes with fewer vertices and simpler textures.
    • Avoid “overdraw”. When a pixel is drawn multiple times, we have overdraw. This happens when objects are rendered on top of each other. An example is a particle effect: if you have a lot of alpha pixels from your particle effects textures, it takes heavy GPU loading to handle collision and overdraw. Try to avoid it by reducing the number of alpha pixels.

Using Wave SDK – Common mistakes and best practices

  • Avoid redundant main camera. Some VR projects developed for cross-platform may contain a Unity default main camera. But since the WaveVR prefab already comes with a built-in one, the redundant main camera will introduce extra GPU computation, even if the result is not seen on the display. You should remove or disable it and only use the WaveVR prefab.
  • Use Single-Pass mode while it is possible. On VR devices the display is rendered for both eyes to create a stereoscopic display. The Single-Pass mode can bring better performance. For some cases, it may cause display problem when Single-Pass is used (ex: used shaders that not support Single-Pass). If so, do NOT check Virtual Reality Supported with Multi-Pass mode selected. You should uncheck Virtual Reality Supported directly.

Reduce your application file size

  • Always use texture compression to get reduced loading times as well as a smaller total build size.
  • Set Texture Compression to ASTC for image decoding. By using hardware decoding, you can get better performance than using the Unity default settings of ETC2 on Android.

Tips using Unity UGUI

  • When changing any UI elements under the UGUI canvas, Unity re-generates the UI meshes. So try to group the UI into several canvases to eliminate the mesh re-generation.
  • It’s not recommended to use the Layout system, use UIAnchor instead.

Coding advice

  1. Cache frequently used properties

    • In Unity, if some Component properties are used very frequently, it’s usually worth caching them instead of trying to query them every time they are needed. |br|For example, gameObject.transform, gameObject.renderer, Input.touches, Camera.main, Screen.width, etc., are implemented with ‘GetComponent()’ and ‘FindWithTag()’. These function calls are not very fast.
  2. Avoid large GC and leak

    • Avoid creating and destroying objects frequently. It’s generally a good idea to utilize the concepts of Object Pooling to re-use your objects after creating them once.
    • Use RaycastNonAlloc to replace RayCastAll, because the former one does not allocate additional instance that takes memory.
  3. Fine tune performance and experience

    • Put your game logic to another thread. Unity runs with the main thread and its APIs are not thread safe. But you can still try to create another thread and move your game logic that is not Unity-related into it.
    • When loading a heavy scene, load an empty or simple one first. That can prevent two heavy scenes reside in memory at the same time.
    • SetActive() is a costly method because it takes time to handle all children objects Try to avoid using it by hiding the target objects when you don’t need them (moving them somewhere where they’re not seen by user).
    • Avoid using SetActive(false) to stop a particle effect. Call ParticleSystem.Stop() instead (or set emit to 0 for earlier versions of Unity). The Particle system has a lot of ‘children’. It is a typical case SetActive() takes lots of time to process.
    • It’s common to play several sound effects at the same time. Preload them in advance at proper timing.
    • Avoid using ‘foreach’. Use ‘for’ as much as possible since it is better for performance.
    • Prevent doing String operations in every frame.
    • When using Dictionary, use native types (ex: int, float) as the key. Do not use string or other complex types. (The types that use Equal() to compare take extra time)
    • Remove all empty event functions of Monobehavior if you don’t overwrite them. Like Start(), Update(),etc.
    • If possible, use Array instead of List or Dictionary.
    • Seal a class if you don’t expect any other classes to derive from it

Other Tips

  • Cast and Receive Shadows is enabled by default. Consider disabling it if possible.
  • Skinned Mesh Renderer comes with poor performance on Mobile platforms. Suggest not to use it if possible.
  • Use animation culling: Check this option so that when the model is not seen in your sight, the animation is paused or stopped to save computation.
  • For animation engine, we would recommend DOTween as in our experience it is resources saving and better performance than other tween engines or legacy animation.
  • Suggest using UnityEngine.JsonUtility. It provides better performance than other Json libraries. (You may still notice about the limitations to Unity)