教學課程:介面和自訂模型

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

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

必要條件

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

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

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

  • 透過匯入MRT) K (功能工具,將 'Mixed Reality Toolkit/Mixed Reality Toolkit Foundation' 2.8.3 版匯入至 Mixed Reality您的專案。
  • 執行 MRTK (設定 MRTK) 的設定精靈。
  • 將 MRTK 新增至目前的場景, (新增至場景) 。 在此使用 ARRMixedRealityToolkitConfigurationProfile ,而不是在本教學課程中建議的設定檔。

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

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

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

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

當 MRTK 和教學課程資產設定重複檢查之後,就會選取正確的設定檔。

  1. 選取場景階層中的 MixedRealityToolkit GameObject。
  2. 在偵測器的 MixedRealityToolkit 元件下,將設定設定檔切換為 ARRMixedRealityToolkitConfigurationProfile。
  3. 按 Ctrl+S 儲存變更。

此步驟主要會使用預設HoloLens 2設定檔來設定 MRTK。 提供的設定檔會以下列方式預先設定:

  • 關閉分析工具 (按 9 加以開啟/關閉,或在裝置上說「顯示/隱藏分析工具」)。
  • 關閉眼睛資料指標。
  • 啟用 Unity 滑鼠點擊功能,讓您可以使用滑鼠 (而不是模擬的手) 來按一下 MRTK UI 元素。

新增應用程式功能表

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

您現在可以將 Prefab AppMenu 新增至場景,以取得目前工作階段狀態的視覺化意見反應。 AppMenu也會顯示使用者用來授權應用程式連線至 ARR 的強制回應面板。

  1. 找出 Assets/RemoteRenderingTutorial/Prefabs/AppMenu 中的 AppMenu Prefab

  2. AppMenu Prefab 拖曳到場景中。

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

  4. AppMenu 設定為自動連結並提供同意連線至工作階段的強制回應,因此我們可以移除稍早所放置的略過。 在 RemoteRenderingCoordinator GameObject 中,按下要求授權事件的 [-] 按鈕,移除我們先前實作的略過授權。

    移除略過

  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 指令碼會實作教學課程資產所包含的 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 物件來包含腳本,並且是測試模型的父代,我們也需要包含模型的虛擬階段。 階段會使用 WorldAnchor,相對於真實世界保持固定。 我們使用固定階段,讓模型本身在稍後仍可四處移動。

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

  2. 將 World Anchor 元件新增至 ModelStage

    新增 WorldAnchor 元件

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

  4. RemoteRenderedModel 指令碼新增至 TestModel

    新增 RemoteRenderedModel 元件

  5. 分別在 Model Display NameModel Path 中填入 "TestModel" 和 "builtin://Engine"。

    指定模型詳細資料

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

    Position 物件

  7. 確定已開啟 AutomaticallyLoad

  8. 按下 Unity 編輯器中的播放以測試應用程式。

  9. 按一下 [ 連線 ] 按鈕以授與授權,以允許應用程式建立會話、與其連線,以及自動載入模型。

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

請嘗試透過 Inspector 中的轉換,或在場景檢視中移動 及旋轉 TestModel GameObject,並觀察遊戲檢視中的轉換。

Unity 記錄

在 Azure 中佈建 Blob 儲存體和自訂模型內嵌

現在,我們可以嘗試載入您自己的模型。 若要這樣做,您必須在 Azure 上設定 Blob 儲存體、上傳和轉換模型,然後使用 RemoteRenderedModel 腳本載入模型。 如果您目前不需要載入自己的模型,可以放心地跳過自訂模型載入的步驟。

依照快速入門:轉換模型以進行轉譯中指定的步驟執行。 請略過本教學課程的將 新模型插入快速入門範例應用程式 一節。 一旦您的內嵌模型 共用存取簽章 (SAS) URI,請繼續。

載入和呈現自訂模型

  1. 在場景中建立新的空白 GameObject,並以與自訂模型類似的名稱命名。

  2. 將 RemoteRenderedModel 指令碼新增至新建立的 GameObject。

    新增 RemoteRenderedModel 元件

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

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

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

  6. 確定已開啟 AutomaticallyLoad

  7. 按下 Unity 編輯器中的播放以測試應用程式。

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

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

後續步驟

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