共用方式為


教學課程:介面和自定義模型

在本教學課程中,您會了解如何:

  • 將混合實境工具組新增至專案
  • 管理模型狀態
  • 設定模型擷取 Azure Blob 儲存體
  • 上傳和處理用於轉譯的模型

必要條件

開始使用混合實境工具組 (MRTK)

混合實境工具組 (MRTK) 是一個跨平臺工具組,用於建置混合實境體驗。 我們使用 MRTK 2.8.3 進行互動和視覺效果功能。

匯入 MRTK 的官方 指南 包含一些我們不需要執行的步驟。 只需要這三個步驟:

  • 透過混合實境功能工具(匯入 MRTK)將「混合實境工具組/混合實境工具組基礎」2.8.3 版匯入至您的專案。
  • 執行 MRTK 的設定精靈(設定 MRTK)。
  • 將 MRTK 新增至目前的場景(新增至場景)。 在這裡使用 ARRMixedRealityToolkitConfigurationProfile,而不是教學課程中建議的配置檔。

匯入本教學課程所使用的資產

從本章開始,我們將針對涵蓋的大部分材料實作基本 模型檢視控制器模式 。 模式的模型部分是 Azure 遠端轉譯 特定程式代碼,以及與 Azure 遠端轉譯 相關的狀態管理。 模式的檢視和控制器部分是使用 MRTK 資產和一些自定義腳本來實作。 本教學課程中可以使用 模型 ,而不需要 在這裡實作檢視控制器 。 此區隔可讓您輕鬆地將本教學課程中找到的程式代碼整合到您自己的應用程式中,而此應用程式會接管 設計模式的檢視控制器 部分。

透過 MRTK 的引進,現在已將多個腳本、預製專案和資產新增至專案,以支援互動和視覺回饋。 這些資產稱為教學課程資產,會組合成 Unity 資產套件,其包含在 '\Unity\TutorialAssets\TutorialAssets.unitypackage' 的 Azure 遠端轉譯 GitHub 中。

  1. 如果將 zip 解壓縮至已知位置,請複製或下載 Git 存放庫 Azure 遠端轉譯
  2. 在您的 Unity 專案中,選擇 [ 資產 -> 匯入套件 -> 自定義套件]。
  3. 在檔案總管中,流覽至您複製或解壓縮 Azure 遠端轉譯 存放庫的目錄,然後選取.unitypackage在 Unity - TutorialAssets ->> TutorialAssets.unitypackage 中找到
  4. 選取 [ 入] 按鈕,將封裝的內容匯入您的專案。
  5. 在 Unity 編輯器中,從頂端功能表欄選取 [混合實境工具組 -> 公用程式 -> 升級 MRTK 標準著色器],然後 遵循提示來升級著色器。

設定 MRTK 和教學課程資產之後,會再次檢查是否已選取正確的配置檔。

  1. 選取 場景階層中的MixedRealityToolkit GameObject。
  2. 在 Inspector 的 MixedRealityToolkit 元件下,將組態配置檔切換至 ARRMixedRealityToolkitConfigurationProfile
  3. Ctrl+S 儲存變更。

此步驟主要使用預設 HoloLens 2 配置檔來設定 MRTK。 提供的設定檔案會以下欄取的設定:

  • 關閉分析工具(按 9 將它切換為開啟/關閉,或在裝置上說「顯示/隱藏分析工具」)。
  • 關閉眼睛注視游標。
  • 啟用 Unity 滑鼠點選,因此您可以使用滑鼠來按下 MRTK UI 元素,而不是仿真的手部。

新增應用程式功能表

本教學課程中的大部分檢視控制器都是針對抽象基類運作,而不是針對具體類別運作。 此模式提供更多彈性,並讓我們為您提供檢視控制器,同時仍可協助您瞭解 Azure 遠端轉譯 程式代碼。 為了簡單起見, RemoteRenderingCoordinator 類別沒有提供的抽象類,而且其檢視控制器會直接針對具體類別運作。

您現在可以將 prefab AppMenu 新增至場景,以取得目前會話狀態的視覺回饋。 AppMenu 也會顯示使用者用來授權應用程式連線至ARR的強制回應面板。

  1. 找出 Assets/RemoteRenderingTutorial/Prefabs/AppMenu 中的 AppMenu 預製專案

  2. AppMenu 預製專案拖曳到場景中。

  3. 如果您看到 TMP 匯入工具對話框,請遵循提示匯入 TMP Essentials 然後關閉匯入工具對話框,因為不需要範例和額外專案。

  4. AppMenu 已設定為自動連結並提供同意連線到會話的強制回應,因此我們可以移除稍早放置的略過。 在 RemoteRenderingCoordinator GameObject 上,按下 On Requesting Authorization 事件上的 '-' 按鈕,移除我們先前實作的授權略過。

    拿掉略過.

  5. 在 Unity 編輯器中按 [播放 ] 來測試檢視控制器。

  6. 在編輯器中,現在已設定 MRTK,您可以使用 WASD 鍵來變更檢視的位置,並按住滑鼠右鍵 + 移動滑鼠來變更檢視方向。 嘗試在場景周圍「開車」一點,以取得控件的感覺。

  7. 在裝置上,您可以舉起手掌來召喚 AppMenu,在 Unity 編輯器中使用快捷鍵 'M'。

  8. 如果您看不到功能表,請按 『M』 鍵來傳喚功能表。 菜單位於相機附近,方便互動。

  9. AppMenu 會提供UI元素,以授權AppMenu許可權。 從現在開始,您應該使用此 UI 元素來授權應用程式管理遠端轉譯工作階段。

    UI 授權

  10. 停止 Unity 播放以繼續進行教學課程。

管理模型狀態

我們需要名為 RemoteRenderedModel 的新腳本,用於追蹤狀態、回應事件、引發事件和設定。 基本上, RemoteRenderedModel 會將模型數據的遠端路徑儲存在 中 modelPath。 它會接聽 RemoteRenderingCoordinator 中的狀態變更,以查看它是否應該自動載入或卸除它所定義的模型。 已附加 RemoteRenderedModel 的 GameObject 是遠端內容的本機父代。

請注意,RemoteRenderedModel 腳本會實作 Tutorial Assets 隨附的 BaseRemoteRenderedModel。 此連線可讓遠端模型檢視控制器與您的腳本系結。

  1. 在與 RemoteRenderingCoordinator 相同的資料夾中,建立名為 RemoteRenderedModel 的新腳本。 以下欄程序代碼取代整個內容:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using System;
    using UnityEngine;
    using UnityEngine.Events;
    
    public class RemoteRenderedModel : BaseRemoteRenderedModel
    {
        public bool AutomaticallyLoad = true;
    
        private ModelState currentModelState = ModelState.NotReady;
    
        [SerializeField]
        [Tooltip("The friendly name for this model")]
        private string modelDisplayName;
        public override string ModelDisplayName { get => modelDisplayName; set => modelDisplayName = value; }
    
        [SerializeField]
        [Tooltip("The URI for this model")]
        private string modelPath;
        public override string ModelPath
        {
            get => modelPath.Trim();
            set => modelPath = value;
        }
    
        public override ModelState CurrentModelState
        {
            get => currentModelState;
            protected set
            {
                if (currentModelState != value)
                {
                    currentModelState = value;
                    ModelStateChange?.Invoke(value);
                }
            }
        }
    
        public override event Action<ModelState> ModelStateChange;
        public override event Action<float> LoadProgress;
        public override Entity ModelEntity { get; protected set; }
    
        public UnityEvent OnModelNotReady = new UnityEvent();
        public UnityEvent OnModelReady = new UnityEvent();
        public UnityEvent OnStartLoading = new UnityEvent();
        public UnityEvent OnModelLoaded = new UnityEvent();
        public UnityEvent OnModelUnloading = new UnityEvent();
    
        public UnityFloatEvent OnLoadProgress = new UnityFloatEvent();
    
        public void Awake()
        {
            // Hook up the event to the Unity event
            LoadProgress += (progress) => OnLoadProgress?.Invoke(progress);
    
            ModelStateChange += HandleUnityStateEvents;
        }
    
        private void HandleUnityStateEvents(ModelState modelState)
        {
            switch (modelState)
            {
                case ModelState.NotReady:  OnModelNotReady?.Invoke();  break;
                case ModelState.Ready:     OnModelReady?.Invoke();     break;
                case ModelState.Loading:   OnStartLoading?.Invoke();   break;
                case ModelState.Loaded:    OnModelLoaded?.Invoke();    break;
                case ModelState.Unloading: OnModelUnloading?.Invoke(); break;
            }
        }
    
        private void Start()
        {
            //Attach to and initialize current state (in case we're attaching late)
            RemoteRenderingCoordinator.CoordinatorStateChange += Instance_CoordinatorStateChange;
            Instance_CoordinatorStateChange(RemoteRenderingCoordinator.instance.CurrentCoordinatorState);
        }
    
        /// <summary>
        /// Listen for state changes on the coordinator, clean up this model's remote objects if we're no longer connected.
        /// Automatically load if required
        /// </summary>
        private void Instance_CoordinatorStateChange(RemoteRenderingCoordinator.RemoteRenderingState state)
        {
            switch (state)
            {
                case RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected:
                    CurrentModelState = ModelState.Ready;
                    if (AutomaticallyLoad)
                        LoadModel();
                    break;
                default:
                    UnloadModel();
                    break;
            }
        }
    
        private void OnDestroy()
        {
            RemoteRenderingCoordinator.CoordinatorStateChange -= Instance_CoordinatorStateChange;
            UnloadModel();
        }
    
        /// <summary>
        /// Asks the coordinator to create a model entity and listens for coordinator state changes
        /// </summary>
        [ContextMenu("Load Model")]
        public override async void LoadModel()
        {
            if (CurrentModelState != ModelState.Ready)
                return; //We're already loaded, currently loading, or not ready to load
    
            CurrentModelState = ModelState.Loading;
    
            ModelEntity = await RemoteRenderingCoordinator.instance?.LoadModel(ModelPath, this.transform, SetLoadingProgress);
    
            if (ModelEntity != null)
                CurrentModelState = ModelState.Loaded;
            else
                CurrentModelState = ModelState.Error;
        }
    
        /// <summary>
        /// Clean up the local model instances
        /// </summary>
        [ContextMenu("Unload Model")]
        public override void UnloadModel()
        {
            CurrentModelState = ModelState.Unloading;
    
            if (ModelEntity != null)
            {
                var modelGameObject = ModelEntity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
                Destroy(modelGameObject);
                ModelEntity.Destroy();
                ModelEntity = null;
            }
    
            if (RemoteRenderingCoordinator.instance.CurrentCoordinatorState == RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected)
                CurrentModelState = ModelState.Ready;
            else
                CurrentModelState = ModelState.NotReady;
        }
    
        /// <summary>
        /// Update the Unity progress event
        /// </summary>
        /// <param name="progressValue"></param>
        public override void SetLoadingProgress(float progressValue)
        {
            LoadProgress?.Invoke(progressValue);
        }
    }
    

在最基本的詞彙中, RemoteRenderedModel 會保存載入模型所需的數據(在此案例中為 SAS 或 builtin:// URI),並追蹤遠端模型狀態。 當載入模型時,LoadModel會在 RemoteRenderingCoordinator呼叫 方法,並傳回包含模型的實體以供參考和卸除。

載入測試模型

讓我們再次載入測試模型來測試新的腳本。 針對此測試,我們需要 Game 物件來包含腳本,並且是測試模型的父代,而且我們也需要包含模型的虛擬階段。 我們會使用固定階段,以便稍後仍可移動模型本身。

  1. 在場景中建立新的空白 Game 物件,並將其命名為 ModelStage

  2. 將 World Anchor 元件新增至 ModelStage

    新增 WorldAnchor 元件

  3. 建立新的空白 Game 物件做為 ModelStage子系,並將它命名為 TestModel

  4. RemoteRenderedModel 腳本新增至 TestModel

    新增 RemoteRenderedModel 元件

  5. 分別以 「TestModel」 和 「builtin://Engine」 填入 Model Display NameModel Path

    指定模型詳細數據

  6. TestModel 物件放置在相機前面,位置 x = 0,y = 0,z = 3

    Position 物件

  7. 確定 已開啟 [自動載入 ]。

  8. 在 Unity 編輯器中按 [播放 ] 以測試應用程式。

  9. 按兩下 [ 連線 ] 按鈕以授與授權,以允許應用程式建立會話、連線到該工作階段,以及自動載入模型。

當應用程式通過其狀態進行時,請觀看主控台。 請記住,某些狀態可能需要一些時間才能完成,而且可能會有一段時間的進度更新。 最後,您會看到模型載入的記錄,然後在場景中轉譯的測試模型之後不久。

請嘗試透過偵測器中的轉換,或在場景檢視中移動及旋轉 TestModel GameObject,並在 Game 檢視中觀察轉換。

Unity 記錄

在 Azure 中布建 Blob 記憶體和自定義模型擷取

現在我們可以嘗試載入您自己的模型。 若要這樣做,您必須在 Azure 上設定 Blob 記憶體、上傳和轉換模型,然後使用 RemoteRenderedModel 腳本載入模型。 如果您目前沒有要載入自己的模型,則可以安全地略過自定義模型載入步驟。

請遵循快速入門:轉換模型以進行轉譯的步驟。 針對本教學課程,請略過將 新模型插入快速入門範例應用程式 一節。 在您擷取模型的 共用存取簽章 (SAS) URI 之後,請繼續。

載入和轉譯自定義模型

  1. 在場景中建立新的空白 GameObject,並將它命名為您的自定義模型。

  2. RemoteRenderedModel 腳本新增至新建立的 GameObject。

    新增 RemoteRenderedModel 元件

  3. 以模型的適當名稱填入 Model Display Name

  4. Model Path使用您在 Azure 中佈建 Blob 記憶體和自訂模型擷取步驟中建立的模型共用存取簽章 (SAS) URI 填入 。

  5. 將 GameObject 放在相機前方,位置 x = 0,y = 0,z = 3。

  6. 確定 已開啟 [自動載入 ]。

  7. 在 Unity 編輯器中按 [播放 ] 以測試應用程式。

    主控台會在會話連線之後,顯示目前的會話狀態,以及載入進度訊息的模型。

  8. 從場景中移除您的自定義模型物件。 本教學課程的最佳體驗是測試模型。 雖然 ARR 支援多個模型,但本教學課程已撰寫為一次最佳支援單一遠端模型。

下一步

您現在可以將自己的模型載入 Azure 遠端轉譯,並在應用程式中檢視它們! 接下來,我們會引導您操作模型。