ニューラルネットワーク・モデルのプリロード( Neural Network Model Preloading)の使い方
メッシングやデプスなどの認識機能は、ARセッションで各ピクセルをどのように描画するかを知るためにニューラルネットワークモデルを使用します。 実行時のパフォーマンスを向上させるために、モデルのプリロードにより、前もってモデルファイルをダウンロードすることができます。これにより、インストールプロセスをフロントローディングし、ユーザーエクスペリエンスに悪影響を与えるジャストインタイムのロード遅延を回避することができます。
前提条件
- Lightship ARを有効にしたUnityのプロジェクトが必要です。 詳細については、 ARDK 3.0 のインストールを参照してください。
Neural Network Performance Modes
Lightship のアウェアネス機能は、さまざまなパフォーマンスモードをサポートしており、開発者はパフォーマンス、品質、またはバランスの取れたミックスを選択することができます。 これらのモードは、 XROcclusionSubsystemで使用される Unity EnvironmentDepthMode 値 に類似しています。
例えば、 Fast は、品質を犠牲にして処理時間を短縮するように最適化し、 Smooth は、最高のグラフィックを提供するために時間をかける。 Custom モードもあり、定義済みのLightshipモデルをオーバーライドすることなく、独自のニューラル・ネットワーク・モデル・ファイルを提供することができます。 各モードは、AR機能を使用する前にプリロードできる独自のニューラルネットワークモデルファイルを持っています。
機能モードを要求するには、アウェアネス機能を開始する前に機能とモードを指定します:
DownloadModel(DepthMode.Medium);
DownloadModel(SemanticsMode.Smooth);
Runtime/Utilities/Preloading/Feature.cs を参照してください。
Lightship Neural Network Modelのプリロード
アウェアネス機能を開始する前に、ニューラルネットワークモデルファイルをプリロードします:
- プリロードが行われるべきシーンに、空の GameObjectを作成する:- Hierarchyでシーンを右クリックし、 Create Emptyを選択します。
 
- 新しいスクリプトコンポーネントを空に追加する:
- 空の GameObjectを Hierarchyで選択し、 Inspectorで、 Add Componentをクリックし、新しいスクリプトを追加します。
 
- 空の 
- アプリケーションのパフォーマンスと品質のニーズに基づいて、プリロードするモデルを決定します。
- プリロードのタイミングを決めます。 プリロードは、AR Foundation が Lightship ローダーを初期化した後( XRGeneralSettings.Instance.Manager.isInitializationCompleteが true の場合)、アウェアネス機能が開始する前であればいつでも実行できます。 例えば、ARスクリプトがMonoBehaviourの場合、プリロードはStart()の間に行うことができます。
モデルのプリロードAPIは、Lightship ARセッションが開始した後のランタイム中にのみ利用可能です。 モデルがプリロードされていない場合、アウェアネス機能が起動時に自動的に選択します。
- ModelPreloaderオブジェクトを- ModelPreloaderFactoryでインスタンス化します:
private void Start()
{
    IModelPreloader preloader = ModelPreloaderFactory.Create();
    if (null == preloader)
    {
        // プリロードする前にXRが初期化されるのを待つ必要がある。
        // プリローダーのインスタンス化を後の関数に延期する。
        return;
    }.
}
- カスタムニューラルネットワークファイルを使用している場合は、 ExistsInCache()を呼び出して、モデルがすでにキャッシュに存在するかどうかをチェックします。
if (preloader.ExistsInCache(DepthMode.Medium))
{
    // モデルはすでにキャッシュにある。 ダウンロードは不要。
}
- DownloadModel(Feature, FeatureMode)を呼び出し、ダウンロードを開始する:
preloader.DownloadModel(DepthMode.Medium);
- ユーザーにダウンロードの進捗状況を表示したい場合は、定期的に CurrentProgress()を呼び出す。progressが1.0の場合、モデルのプリロードは完了します。 使用例:
private void Update()
{
    var statusCode = preloader.CurrentProgress(DepthMode.Medium, out float progress)
    if (Mathf.Approximately(progress, 1.0f))
    {
        // The download has completed successfully, or the model was already in the cache
    }
}
ローカルモデルファイルの登録
ARセッションを開始するときに、高価なダウンロードプロセスを実行するのを避けるために、モデルファイルを事前にダウンロードしておくとよいでしょう。 これをサポートするために、ModelPreloaderは、任意のフィーチャーモードに対してローカルモデルファイルを登録することができます。
ローカルモデルファイルをARDKに登録する:
- モデルファイルが、アプリケーションがアクセスできる場所にあることを確認してください。
- プリロードが行われるべ  きシーンに、空の GameObjectを作成する:- Hierarchyでシーンを右クリックし、 Create Emptyを選択します。
 
- 新しいスクリプトコンポーネントを空に追加する:
- 空の GameObjectを Hierarchyで選択し、 Inspectorで、 Add Componentをクリックし、新しいスクリプトを追加します。
 
- 空の 
- アプリケーションのパフォーマンスと品質のニーズに基づいて、プリロードするモデルを決定します。
- プリロードのタイミングを決める。 プリロードは、AR Foundation が Lightship ローダーを初期化した後( XRGeneralSettings.Instance.Manager.isInitializationCompleteが true の場合)、アウェアネス機能が開始する前であればいつでも実行できます。 例えば、ARスクリプトがMonoBehaviourの場合、プリロードはStart()の間に行うことができます。
機能モードにモデルを登録すると、ARセッションの残りの時間  はそのモデルが上書きされます。 これを避けたい場合は、 Custom。
- ModelPreloaderオブジェクトを- ModelPreloaderFactoryでインスタンス化する:
private void Start()
{
    IModelPreloader preloader = ModelPreloaderFactory.Create();
    if (null == preloader)
    {
        // プリロードする前にXRが初期化されるのを待つ必要がある。
        // プリローダーのインスタンス化を後の関数に延期する。
        return;
    }.
}
- RegisterModel(Feature, FeatureMode, filepath)を呼び出し、ARDKにモデルを登録する。 このファイルはアプリケーションからアクセス可能でなければならず、ARセッションの間、そのままでなければならない。
var statusCode = preloader.RegisterModel(DepthMode.Medium, "/example/path/to/model_file");
if (statusCode is PreloaderStatusCode.Success or
                              PreloaderStatusCode.RequestInProgress or
                              PreloaderStatusCode.FileExistsInCache)
{
    // モデルがキャッシュに登録されたか、モデルが既にキャッシュに登録されていた。
    // これは、今後LightshipのEnvironmentDepthMode.Mediumに使用されるモデルになります。
}
モデル・プリロード・スクリプトの例
using System;
using System.Collections.Generic;
using System.Linq;
using Niantic.Lightship.AR.Utilities.Preloading;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
using UnityEngine.XR.Management;
public class PreloaderTestManager :MonoBehaviour
{
    [SerializeField]
    private Button _depthButton;
    [SerializeField]
    private Dropdown _depthDropdown;
    [SerializeField]
    private Button _semanticsButton;
    [SerializeField]
    private Dropdown _semanticsDropdown;
    [SerializeField]
    private Text _depthStatusText;
    [SerializeField]
    private Text _semanticsStatusText;
    [SerializeField]
    private Button _clearCacheButton;
    [SerializeField]
    private Text _preloadStatusText;
    [SerializeField]
    private Slider _percentageSlider;
    [SerializeField]
    private Text _percentageText;
    [SerializeField]
    private Text _cacheStatusText;
    [SerializeField]
    private string _localModelPath;
    private IModelPreloader _preloader;
    private DepthMode _depthMode;
    private SemanticsMode _semanticsMode;
    private void Start()
    {
        TryInitializePreloader();
        InitializeDropdownNames<DepthMode>(_depthDropdown);
        InitializeDropdownNames<SemanticsMode>(_semanticsDropdown);
    }
    private void OnDisable()
    {
        _preloader?.Dispose();
        _preloader = null;
    }
    private void InitializeDropdownNames<T>(Dropdown dropdown)
    {
        // ドロップダウンメニューの機能モード名を初期化します。
        SetFeatureModeNames<T>(dropdown);
        dropdown.value = dropdown.options.Select(option => option.text).ToList().IndexOf("Medium");
    }
    private void SetFeatureModeNames<T>(Dropdown dropdown)
    {
        List<string> modeNames = new();
        foreach (var i in Enum.GetValues(typeof(T)))
        {
            var modeName = Enum.GetName(typeof(T), i);
            if (modeName != "Unspecified")
                modeNames.Add(modeName);
        }
        dropdown.AddOptions(modeNames);
    }
    private void Update()
    {
        if (_preloader == null && !TryInitializePreloader())
            return;
        UpdateDownloadProgress();
        UpdateCacheStatusText();
    }
    private bool TryInitializePreloader()
    {
        if (!XRGeneralSettings.Instance.Manager.isInitializationComplete)
        {
            // Need to wait for XR to initialize before we can preload
            return false;
        }
        _depthMode = DepthMode.Medium;
        _semanticsMode = SemanticsMode.Medium;
        _preloader = ModelPreloaderFactory.Create();
        if (null == _preloader)
        {
            // Need to wait for XR to initialize before we can preload
            return false;
        }
        _depthButton.onClick.AddListener
        (
            () =>
            {
                _depthMode = ParseFeatureMode<DepthMode>(_depthDropdown);
                PreloaderStatusCode result;
                if (_depthMode == DepthMode.Custom)
                {
                    if (_localModelPath.Length == 0)
                    {
                        _preloadStatusText.text = "Depth file registration: Please specify a local path to a model file in PreloaderTestManager";
                        return;
                    }
                    result =  _preloader.RegisterModel(_depthMode, _localModelPath);
                    _preloadStatusText.text = "Depth file registration: " + (IsError(result) ? "failed" : "success");
                    return;
                }
                result = _preloader.DownloadModel(_depthMode);
                _preloadStatusText.text = "Depth download started: " + (IsError(result) ? "failed" : "success");
            }
        );
        _semanticsButton.onClick.AddListener
        (
            () =>
            {
                _semanticsMode = ParseFeatureMode<SemanticsMode>(_semanticsDropdown);
                PreloaderStatusCode result;
                if (_semanticsMode == SemanticsMode.Custom)
                {
                    if (_localModelPath.Length == 0)
                    {
                        _preloadStatusText.text = "Semantics file registration: Please specify a local path to a model file in PreloaderTestManager";
                        return;
                    }
                    result =  _preloader.RegisterModel(_semanticsMode, _localModelPath);
                    _preloadStatusText.text = "Semantics file registration: " + (IsError(result) ? "failed" : "success");
                    return;
                }
                result = _preloader.DownloadModel(_semanticsMode);
                _preloadStatusText.text = "Semantics download started: " + (IsError(result) ? "failed" : "success");
            }
        );
        _clearCacheButton.onClick.AddListener
        (
            () =>
            {
                int successes = 0;
                if (_preloader.ClearFromCache(_depthMode))
                    successes++;
                if (_preloader.ClearFromCache(_semanticsMode))
                    successes++;
                _preloadStatusText.text = "Clear cache: " + successes + " successes";
            }
        );
        _depthDropdown.onValueChanged.AddListener
        (
            (int val) =>
            {
                var mode = val + 1; // We skip the 'unspecified' mode
                _depthMode = (DepthMode) mode;
            }
        );
        _semanticsDropdown.onValueChanged.AddListener
        (
            (int val) =>
            {
                var mode = val + 1; // We skip the 'unspecified' mode
                _semanticsMode = (SemanticsMode) mode;
            }
        );
        return true;
    }
    private static T ParseFeatureMode<T>(Dropdown dropdown)
    {
        var modeName = dropdown.options[dropdown.value].text;
        T mode = (T) Enum.Parse(typeof(T), modeName);
        return mode;
    }
    private void UpdateDownloadProgress()
    {
        // Display the progress of the feature modes that are selected with the dropdown menus
        var depthStatus = _preloader.CurrentProgress(_depthMode, out var selectedDepthProgress);
        if (IsError(depthStatus))
        {
            _depthStatusText.text = depthStatus == PreloaderStatusCode.RequestNotFound ?
                "0%" : "失敗しました:" + depthStatus.ToString();
        }
        else
        {
            _depthStatusText.text = (selectedDepthProgress * 100).ToString("0") + "%";
        }
        var semanticsStatus = _preloader.CurrentProgress(_semanticsMode, out var selectedSemanticsProgress);
        if (IsError(semanticsStatus))
        {
            _semanticsStatusText.text = semanticsStatus == PreloaderStatusCode.RequestNotFound ?
                "0%" : "Failure: " + semanticsStatus.ToString();
        }
        else
        {
            _semanticsStatusText.text = (selectedSemanticsProgress * 100).ToString("0") + "%";
        }
        // Summarize their download progress
        float combinedProgress = 0, activeDownloads = 0;
        if (selectedDepthProgress > 0 && selectedDepthProgress < 1)
        {
            combinedProgress += selectedDepthProgress;
            activeDownloads++;
        }
        if (selectedSemanticsProgress > 0 && selectedSemanticsProgress < 1)
        {
            combinedProgress += selectedSemanticsProgress;
            activeDownloads++;
        }
        float totalProgress = activeDownloads > 0 ? combinedProgress / activeDownloads : 0;
        _percentageText.text = (totalProgress * 100).ToString("0") + "%";
        _percentageSlider.value = totalProgress;
    }
    private void UpdateCacheStatusText()
    {
        // Cache status
        List<string> modeNames = new();
        DepthMode depthMode = new DepthMode();
        SemanticsMode semanticsMode = new SemanticsMode();
        string cacheStatusText = "Model files preloaded in cache: " + System.Environment.NewLine;
        // Update the cache status for all depth modes
        foreach (DepthMode i in Enum.GetValues(typeof(DepthMode)))
        {
            if (i == DepthMode.Unspecified)
                continue;
            depthMode = i;
            var present = _preloader.ExistsInCache(depthMode);
            if (present)
                modeNames.Add(Enum.GetName(typeof(DepthMode), i) + " Depth");
        }
        // Update the cache status for all semantics modes
        foreach (SemanticsMode i in Enum.GetValues(typeof(SemanticsMode)))
        {
            if (i == SemanticsMode.Unspecified)
                continue;
            semanticsMode = i;
            var present = _preloader.ExistsInCache(semanticsMode);
            if (present)
                modeNames.Add(Enum.GetName(typeof(SemanticsMode), i) + " Semantics");
        }
        // Summarize cache status
        for (int i = 0; i < modeNames.Count; i++)
        {
            cacheStatusText += modeNames[i] + (i < modeNames.Count - 1 ? ", " : "");
        }
        _cacheStatusText.text = cacheStatusText;
    }
    private static bool IsError(PreloaderStatusCode statusCode)
    {
        if (statusCode is PreloaderStatusCode.Success or
                            PreloaderStatusCode.RequestInProgress or
                            PreloaderStatusCode.FileExistsInCache)
            return false;
        return true;
    }
}