VR の Scene 遷移処理中に Compositor Layer を用いた Loading 画面を表示する

VR空間での画面切替は厄介で,突然画面が切り替わると,人は不快に感じてVR酔いに繋がります.
VR上で画面が切り替わるときは,映像に連続性をもたせて段階的に画面を切り替えるような処理が必要になります.

対策としては,

  • フェードアウト/フェードインを利用する
  • 周りを黒くする&視界を狭くする

などが挙げられます.今回は,OVR Overlayを用いてVR空間でSceneを切り替える時に
Loading...画面を表示して,ユーザーエクスペリエンス向上とVR酔いを防ごうと思います.

OVROverlay

Oculus Intergration の SampleFramework に OVR Overlay のサンプルシーンがあります.
OVROverlay は Compositor Layer を利用できるスクリプトです.

Compositor Layer

www.heistak.com

VRアプリにおいて、ローディング画面などでレンダリングがリフレッシュレートに間に合わず、
Asynchronous Timewarp (ATW)処理で擬似的に生成されたフレームが表示されることがあります。
このような時、ATWが前の絵の位置をずらしているため、ずれた分だけ表示領域の端に黒い枠が見えてきます。

VRをやっている人ならわかる現象だと思います.アレって没入感が失われるんですよね.

「Compositor Layer 」はOculus SDKに内蔵されている、一部のオブジェクトを通常のレンダリングと別枠で、
出力直前に合成して表示することができる機能です。
レンダリングのフレームレートではなくコンポジターのフレームレートで動作するため、
通常のレンダリングが間に合っていない時であっても常時滑らかに表示されます。

developer.oculus.com

アプリのフレームレートでレンダリングされるのではなく,
Compositor のフレームレート(HMDのリフレッシュレート)でレンダリングされるようです.
これにより,Compositor Layer は表示の揺れが発生しにくく、テクスチャーやテキストがよりはっきりと表示されます.

実装

Hierarchy 構造

適当なシーンを2つ作ります.
OVROverlayのサンプルシーンを真似して,下図のようにHierarchyでオブジェクトを作ります.
全て空オブジェクトで作ってます.

f:id:xrdnk:20200527224734p:plain

Overlay_Background

Overlay_BackgroundにOVROverlayスクリプトをアタッチします.
Overlay ShapeはCubemapにし,Left Textureにskybox02_openGLを入れます.
このテスクチャはOculus IntegrationのSample FrameworkをImportしていると入っているはずです.
Left Textureにテクスチャも入れるとデフォルトではRight TextureにはLeftに入れたものと同じになります.

最初にOverlayを表示したくないため,コンポーネントのチェックを外していることに注意してください.

f:id:xrdnk:20200527225128p:plain

Overlay_LoadingText

同様にして,Overlay_LoadingTextにOVROverlayスクリプトをアタッチします.
Overlay ShapeはQuadにし,Left TextureにLoadingを入れます.
同じくこのテクスチャはSample FrameworkをImportしていると入っているはずです.

同様に,コンポーネントのチェックを外していることに注意してください.

f:id:xrdnk:20200527225534p:plain

他の設定が具体的になんなのか気になった場合は,以下を詳細してください.

developer.oculus.com

テクスチャは自分の好みで変えても構いません.

SceneLoader

シーン切替用のSingletonクラス,SceneLoaderを作成します.
作成後,SceneLoaderオブジェクトにアタッチし,下図のようにオブジェクトを当てはめます.

f:id:xrdnk:20200527225840p:plain

シーン切替を行いたくなったところに,SceneLoader.Instance.LoadScene()を呼び出してください.

using UnityEngine;
using UnityEngine.SceneManagement; // SceneManager 利用に必要
using UniRx.Async; // UniTask 利用に必要

public class SceneLoader : MonoBehaviour
{
    /// <summary>
    /// 背景用のOverlay
    /// </summary>
    [SerializeField]
    private OVROverlay _overlay_Background;
    /// <summary>
    /// LOADING表示用のOverlay
    /// </summary>
    [SerializeField]
    private OVROverlay _overlay_LoadingText;

    private static readonly string CENTER_EYE_ANCHOR = "CenterEyeAnchor";
    private static readonly int MARGIN_MILLISECOND = 3000;

    private static SceneLoader _instance;
    public static SceneLoader Instance;

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(this.gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public async void LoadScene(string sceneName)
    {
        await ShowOverlayAndLoad(sceneName);
    }

    private async UniTask ShowOverlayAndLoad(string sceneName)
    {
        ActivateOverlay(true);

        GameObject centerEyeAnchor = GameObject.Find(CENTER_EYE_ANCHOR);
        _overlay_LoadingText.gameObject.transform.position = centerEyeAnchor.transform.position + new Vector3(0f, 0f, 3f);

        await UniTask.Delay(MARGIN_MILLISECOND);

        await SceneManager.LoadSceneAsync(sceneName);

        ActivateOverlay(false);
    }

    private void ActivateOverlay(bool isActive)
    {
        _overlay_Background.enabled = isActive;
        _overlay_LoadingText.enabled = isActive;
    }
}

実機確認

f:id:xrdnk:20200528004141g:plain

こんな感じです.
Tipsですが,Compositor LayerはそもそもLayerが違うため,
Unity Recorderで録画しても,Compositor Layerが表示されません.アプリケーションの画面のままになります.
(つまりUnity Recorderで録画した動画やUnity Editor の Game Scene で見ると,キンクリ発動しているように見える.)

f:id:xrdnk:20200528002637g:plain