MRが楽しい

MRやVRについて学習したことを書き残す

LCCUnitySDKとMRTKを使ってQuest3でLCCモデルを表示する その4(読み込み完了までLCCモデルを表示しない)

本日はQuest3の小ネタ枠です。
LCC Unity SDKとMRTKを使ってQuest3でLCCモデルを表示する方法です。
今回は読み込み完了までLCCモデルを表示しない方法についてです。

前回記事

以下の前回記事の続きです。
bluebirdofoz.hatenablog.com

読み込み完了までLCCモデルを表示しない

LCCCore.Rendererが提供するSetEnable()メソッドを利用して任意のタイミングでLCCモデルの表示/非表示を切り替えることができます。
developer.xgrids.com

m_renderer = m_manager.GetRender(this.transform);

// Load 直後から描画が走るので、事前に止める
m_renderer.SetEnable(false);

m_renderer.Load(path, PlatformType.Quest, OnLoadCallbackInternal);

またGetLoadRatio()メソッドは現在の読み込みの進捗率が取得できます。
本関数を利用して一定以上の進捗率になったときにLCCモデルを表示することで読み込み完了と共にLCCモデルを表示できます。
developer.xgrids.com

サンプルスクリプト

読み込み進捗率が85%に達したときにLCCモデルの表示を行う以下のサンプルスクリプトを作成しました。

using UnityEngine;
using LCCCore;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Collections;

public class LCCEnableRenderer : MonoBehaviour
{
    [Header("LCC Manager")]
    public LCCManager m_manager;

    [Header("Loading")]
    [Tooltip("GetLoadRatio を何秒ごとに監視するか")]
    public float progressPollIntervalSec = 0.5f;

    [Tooltip("GetLoadRatio がこの値を超えたら 'ロード完了相当' とみなして描画を開始する")]
    [Range(0.0f, 1.0f)]
    public float readyRatioThreshold = 0.85f; // API Reference の Experience value

    // 進捗 (0..1) を通知。ratio は GetLoadRatio() の値。
    public event Action<float> OnProgress;

    // Load() の callback が呼ばれたタイミング(ロード完了通知)
    public event Action OnLoaded;

    // エラー通知(必要ならUI側で拾える)
    public event Action<string> OnError;

    private Coroutine m_progressRoutine;

    private bool m_loadCallbackFired;
    private bool m_loadStarted;

    private LCCCore.Renderer m_renderer;

    public async void StopLoad()
    {
        // 既に監視中なら止める
        if (m_progressRoutine != null)
        {
            StopCoroutine(m_progressRoutine);
            m_progressRoutine = null;
        }
    }

    public async void BeginLoad()
    {
        string documentFolderPath = string.Empty;
#if UNITY_ANDROID && !UNITY_EDITOR // Quest実機上で動作している場合
        // AndroidSDK29以降では予めストレージへのアクセス許可をユーザに問い合わせて許可を取得する必要がある
        await QuestDocumentAccessor.GetStoragePermissionAsync();

        // Questのドキュメントフォルダのパスを取得する
        documentFolderPath = QuestDocumentAccessor.GetDocumentFolderPathPerAndroid29();
#else // それ以外のプラットフォーム(Editor含む)
        // 標準のドキュメントフォルダのパスを取得する
        documentFolderPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
#endif

        // ドキュメントフォルダ内のLCCフォルダ内に存在する .lcc ファイルを検索する
        string searchPath = System.IO.Path.Combine(documentFolderPath, "LCC");
        string[] files = Directory.GetFiles(searchPath, "*.lcc", SearchOption.TopDirectoryOnly);

        // .lcc ファイルがない場合は処理しない
        if (files.Length == 0)
        {
            Debug.LogError($"[LCCRenderer] No .lcc files found in: {searchPath}");
            return;
        }

        // 複数ある場合は最初のファイルを利用する
        string path = files[0];
        Debug.Log($"[LCCRenderer] Loading file: {Path.GetFileName(path)}");

        // Renderer 取得
        m_renderer = m_manager.GetRender(this.transform);
        if (m_renderer == null)
        {
            EmitError("[LCC] GetRender returned null.");
            return;
        }

        m_loadCallbackFired = false;
        m_loadStarted = true;

        // --- 重要:即座に表示しない ---
        // Load 直後から描画が走る仕様なので、事前に止める
        m_renderer.SetEnable(false);

        // --- 重要:GetLoadRatio はブロックレンダリング時のみ有効 ---
        // そのため Load 前に非フル(=ストリーミング寄り)に固定するのが安全
        // ※SetRenderAll は Load 前に呼ばないと効かない、と API に明記
        m_renderer.SetRenderAll(false);

        // 進捗監視開始(Loadの直後から ratio を取りたい)
        m_progressRoutine = StartCoroutine(WatchProgress());
        OnProgress?.Invoke(0f);

        m_renderer.Load(path, PlatformType.Quest, OnLoadCallbackInternal);
    }

    private void OnLoadCallbackInternal()
    {
        m_loadCallbackFired = true;
        Debug.Log("[LCC] Load callback fired (data loaded).");

        // ここではまだ描画開始しない(要件に合わせて “完了時点” で開始するが、
        // どのタイミングを完了と定義するかは readyRatioThreshold と併用する)
        OnLoaded?.Invoke();
        OnProgress?.Invoke(1f);
    }

    private IEnumerator WatchProgress()
    {
        // ratio が 0..1 で取れるのは「ブロックレンダリング戦略」前提。
        // フルレンダリングだとこの値が期待通りにならない可能性があるので注意。
        while (true)
        {
            float ratio = 0f;

            try
            {
                // m_renderer が null の場合は終了
                if (m_renderer == null)
                {
                    EmitError("[LCC] Renderer lost while loading.");
                    yield break;
                }

                ratio = m_renderer.GetLoadRatio(); // 0..1
            }
            catch (Exception e)
            {
                EmitError($"[LCC] GetLoadRatio error: {e.Message}");
                yield break;
            }

            OnProgress?.Invoke(ratio);

            // “ロード完了” 判定:
            // 1) ratio が閾値を超えた(最初の見栄えが整った)
            // 2) Load callback も呼ばれている(SDK上のロード完了通知)
            //
            // どちらか片方だけだと環境によりブレるので、両方満たしたら描画開始にしています。
            if (ratio >= readyRatioThreshold && m_loadCallbackFired)
            {
                Debug.Log($"[LCC] Ready to render. ratio={ratio:0.000}");
                m_renderer.SetEnable(true);   // ← ここで初めて描画開始
                yield break;
            }

            yield return new WaitForSeconds(progressPollIntervalSec);
        }
    }

    private bool EnsureRenderer(string caller)
    {
        if (!m_loadStarted)
        {
            EmitError($"[LCC] {caller}: Load has not been started. Call BeginLoad() first.");
            return false;
        }

        if (m_renderer == null)
        {
            EmitError($"[LCC] {caller}: renderer is null.");
            return false;
        }

        return true;
    }

    private void EmitError(string msg)
    {
        Debug.LogError(msg);
        OnError?.Invoke(msg);
    }

    private void OnDisable()
    {
        if (m_progressRoutine != null)
        {
            StopCoroutine(m_progressRoutine);
            m_progressRoutine = null;
        }
    }
}

シーンを再生してLCCモデルの読み込みを試します。
LCCモデルは徐々に読み込み表示されず、読み込み完了と共に完全なモデルが表示されます。