你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:接口和自定义模型

在本教程中,你将了解如何执行以下操作:

  • 将混合现实工具包添加到项目
  • 管理模型状态
  • 为 Azure Blob 存储配置模型转换
  • 上传和处理模型以实现渲染

先决条件

混合现实工具包 (MRTK) 入门

混合现实工具包 (MRTK) 是用于构建混合现实体验的跨平台工具包。 我们将使用 MRTK 2.8.3 实现其交互和可视化功能。

导入 MRTK 的官方指南 包含一些我们不需要执行的步骤。 只需要执行以下三个步骤:

  • 通过混合现实功能工具(导入 MRTK)将“混合现实工具包/混合现实工具包基础”版本 2.8.3 导入到项目。
  • 运行 MRTK 的配置向导(配置 MRTK)。
  • 将 MRTK 添加到当前场景(添加到场景)。 在此处使用 ARRMixedRealityToolkitConfigurationProfile,而不是本教程中建议的配置文件。

导入本教程要使用的资产

从本章开始,我们将为涉及到的大部分材料实现一种基本的“模型-视图-控制器”模式。 该模式的“模型”部分是特定于 Azure 远程渲染的代码以及与 Azure 远程渲染相关的状态管理。 该模式的“视图”和“控制器”部分是使用 MRTK 资产和某些自定义脚本实现的 。 在本教程中,可以只使用模型,而无需实现“视图-控制器”。 通过这种分离,你可以将本教程中的代码轻松集成到自己的应用程序中,在那里,代码将接管设计模式中的“视图-控制器”部分。

通过引入 MRTK,现在可以将多个脚本、预制项和资产添加到项目中,用于支持交互和视觉反馈。 这些“教程资产”会捆绑到 Azure 远程渲染 GitHub 中“\Unity\TutorialAssets\TutorialAssets.unitypackage”路径下的一个 Unity 资产包中。

  1. 如果下载操作会将 zip 提取到已知位置,请克隆或下载 git 存储库 Azure 远程渲染
  2. 在 Unity 项目中,选择“资产”->“导入包”->“自定义包”。
  3. 在文件资源管理器中,导航到克隆或解压缩的 Azure 远程渲染存储库的目录,然后选择“Unity”->“TutorialAssets”->“TutorialAssets.unitypackage”中的 .unitypackage>>
  4. 选择“导入”按钮,将包内容导入到项目中。
  5. 在 Unity 编辑器中,从顶部菜单栏选择“混合现实工具包”->“实用工具”->“升级轻量级渲染管道的 MRTK 标准着色器”,并按照提示升级着色器。

将 MRTK 和教程资产设置为双检查后,会选择正确的配置文件。

  1. 选择场景层次结构中的 MixedRealityToolkit GameObject。
  2. 在检查器的 MixedRealityToolkit 组件下,将配置文件切换为 ARRMixedRealityToolkitConfigurationProfile。
  3. 按 Ctrl+S 保存所做的更改。

这个步骤将主要使用默认的 HoloLens 2 配置文件配置 MRTK。 提供的配置文件按以下方式预配置:

  • 关闭探查器(在设备上可按 9 打开/关闭,或说“显示/隐藏探查器”)。
  • 关闭“眼睛凝视”光标。
  • 启用 Unity 鼠标单击,这样可使用鼠标而不是模拟手势来单击 MRTK UI 元素。

添加应用菜单

本教程中的大部分视图控制器操作是针对抽象基类而不是具体基类。 此模式提供了更大的灵活性,使我们能够为你提供视图控制器,同时还有助于你了解 Azure 远程渲染代码。 为简单起见,RemoteRenderingCoordinator 类未提供抽象类,并且其视图控制器直接针对具体类进行操作。

现在可以将预制项 AppMenu 添加到场景中来获取当前会话状态的视觉反馈了。 现在,AppMenu 将以可视方式指示 ARR 状态,并显示用户用于授权应用程序连接 ARR 的模式面板。

  1. 在 Assets/RemoteRenderingTutorial/Prefabs/AppMenu 中找到 AppMenu 预制项

  2. 将 AppMenu 预制项拖到场景中。

  3. 如果看到“TMP 导入程序”对话框,请按照提示导入 TMP 概要。 然后关闭导入工具对话框,这里不需要示例和其他功能。

  4. AppMenu 配置为自动挂钩,并提供“同意连接到会话”的模式,以便能够删除之前放置的旁路。 在 RemoteRenderingCoordinator GameObject 上,按“请求授权时”事件上的“-”按钮,删除先前实现的授权旁路 。

    删除旁路

  5. 在 Unity 编辑器中按“播放”来测试视图控制器。

  6. 在编辑器中,已配置了 MRTK,现在可以使用 WASD 键更改视图的位置,然后按住鼠标右键同时移动鼠标来更改视图方向。 尝试在各方向上稍微移动场景,感受控制的感觉。

  7. 在设备上,可以通过举起手掌来召唤 AppMenu,在 Unity 编辑器中,可以使用热键“M”来实现此目的。

  8. 如果看不到菜单,可按“M”键召唤菜单。 菜单位于相机附近,便于交互。

  9. AppMenu 在 AppMenu 的右侧显示一个用于授权的 UI 元素。 从现在起,你应该使用此 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)加载所需的数据并跟踪远程模型状态。 在加载模型时,在 RemoteRenderingCoordinator 上调用 LoadModel 方法,并返回包含模型的实体以供引用和卸载。

加载测试模型

现在通过再次加载测试模型来测试新脚本。 对于此测试,我们需要一个游戏对象来包含脚本并成为测试模型的父级,还需要一个包含该模型的虚拟阶段。 使用 WorldAnchor,相对于现实世界,该阶段将保持不变。 我们将使用固定的阶段,这样模型本身在以后仍可以移动。

  1. 在场景中创建新的空游戏对象并将其命名为 ModelStage。

  2. 向 ModelStage 添加 WorldAnchor 组件

    添加 WorldAnchor 组件

  3. 创建一个新的空游戏对象作为 ModelStage 的子级,并将其命名为 TestModel。

  4. 将 RemoteRenderedModel 脚本添加到 TestModel。

    添加 RemoteRenderedModel 组件

  5. 分别用“TestModel”和“builtin://Engine”填写 Model Display NameModel Path

    指定模型详细信息

  6. 将 TestModel 对象放置在相机前面,位置为 x = 0, y = 0, z = 3 。

    位置对象

  7. 确保启用 AutomaticallyLoad。

  8. 在 Unity 编辑器中按“播放”来测试应用程序。

  9. 单击“连接”按钮授予权限,允许应用创建会话,连接到会话并自动加载模型。

当应用程序通过其状态进行监视时,请监视控制台。 请记住,某些状态可能需要一些时间才能完成,并且可能暂时没有进度更新。 最终,你会看到模型加载中的日志,然后场景中会很快呈现测试模型。

尝试在检查器中通过转换,或在场景视图中和在游戏视图中观察转换,移动和旋转 TestModel GameObject。

Unity 日志

预配 Azure 中的 Blob 存储和自定义模型引入

现在,我们可以尝试加载自己的模型。 要执行此操作,你需要在 Azure 上配置 Blob 存储,并上传和转换模型,然后使用 RemoteRenderedModel 脚本加载模型。 如果此时你没有要加载的自己的模型,则可以安全地跳过自定义模型加载步骤。

按照 快速入门:转换要渲染的模型中指定的步骤进行操作。 对于本教程,请跳过“将新模型插入快速入门示例应用”部分。 引入模型的共享访问签名 (SAS) URI 后,请继续。

加载和渲染自定义模型

  1. 在场景中创建新的空 GameObject,并将其命名为类似于自定义模型。

  2. 将 RemoteRenderedModel 脚本添加到新创建的 GameObject。

    添加 RemoteRenderedModel 组件

  3. 使用适当的模型名称填充 Model Display Name

  4. 使用在 Azure 中预配 Blob 存储和自定义模型引入步骤中创建的共享访问签名 (SAS) URI 填充 Model Path

  5. 将 GameObject 放置在相机前面,位置为 x = 0, y = 0, z = 3。

  6. 确保启用 AutomaticallyLoad。

  7. 在 Unity 编辑器中按“播放”来测试应用程序。

    连接会话后,控制台会显示当前会话状态和模型加载进度消息。

  8. 从场景中删除自定义模型对象。 本教程中的最佳实践将使用测试模型。 虽然 ARR 中支持多个模型,但在按照本教程操作时,最好一次支持一个远程模型。

后续步骤

现在你可以将自己的模型加载到 Azure 远程渲染并在应用程序中进行查看了! 接下来介绍如何操作模型。