你当前正在访问 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 资产包中。
- 如果下载操作会将 zip 提取到已知位置,请克隆或下载 git 存储库 Azure 远程渲染。
- 在 Unity 项目中,选择“资产”->“导入包”->“自定义包”。
- 在文件资源管理器中,导航到克隆或解压缩的 Azure 远程渲染存储库的目录,然后选择“Unity”->“TutorialAssets”->“TutorialAssets.unitypackage”中的
.unitypackage
>> - 选择“导入”按钮,将包内容导入到项目中。
- 在 Unity 编辑器中,从顶部菜单栏选择“混合现实工具包”->“实用工具”->“升级轻量级渲染管道的 MRTK 标准着色器”,并按照提示升级着色器。
将 MRTK 和教程资产设置为双检查后,会选择正确的配置文件。
- 选择场景层次结构中的 MixedRealityToolkit GameObject。
- 在检查器的 MixedRealityToolkit 组件下,将配置文件切换为 ARRMixedRealityToolkitConfigurationProfile。
- 按 Ctrl+S 保存所做的更改。
这个步骤将主要使用默认的 HoloLens 2 配置文件配置 MRTK。 提供的配置文件按以下方式预配置:
- 关闭探查器(在设备上可按 9 打开/关闭,或说“显示/隐藏探查器”)。
- 关闭“眼睛凝视”光标。
- 启用 Unity 鼠标单击,这样可使用鼠标而不是模拟手势来单击 MRTK UI 元素。
添加应用菜单
本教程中的大部分视图控制器操作是针对抽象基类而不是具体基类。 此模式提供了更大的灵活性,使我们能够为你提供视图控制器,同时还有助于你了解 Azure 远程渲染代码。 为简单起见,RemoteRenderingCoordinator 类未提供抽象类,并且其视图控制器直接针对具体类进行操作。
现在可以将预制项 AppMenu 添加到场景中来获取当前会话状态的视觉反馈了。 现在,AppMenu 将以可视方式指示 ARR 状态,并显示用户用于授权应用程序连接 ARR 的模式面板。
在 Assets/RemoteRenderingTutorial/Prefabs/AppMenu 中找到 AppMenu 预制项
将 AppMenu 预制项拖到场景中。
如果看到“TMP 导入程序”对话框,请按照提示导入 TMP 概要。 然后关闭导入工具对话框,这里不需要示例和其他功能。
AppMenu 配置为自动挂钩,并提供“同意连接到会话”的模式,以便能够删除之前放置的旁路。 在 RemoteRenderingCoordinator GameObject 上,按“请求授权时”事件上的“-”按钮,删除先前实现的授权旁路 。
。
在 Unity 编辑器中按“播放”来测试视图控制器。
在编辑器中,已配置了 MRTK,现在可以使用 WASD 键更改视图的位置,然后按住鼠标右键同时移动鼠标来更改视图方向。 尝试在各方向上稍微移动场景,感受控制的感觉。
在设备上,可以通过举起手掌来召唤 AppMenu,在 Unity 编辑器中,可以使用热键“M”来实现此目的。
如果看不到菜单,可按“M”键召唤菜单。 菜单位于相机附近,便于交互。
AppMenu 在 AppMenu 的右侧显示一个用于授权的 UI 元素。 从现在起,你应该使用此 UI 元素来授权应用管理远程呈现会话。
让 Unity 停止播放,以继续学习本教程。
管理模型状态
现在,我们将实现一个新的脚本 RemoteRenderedModel,用于跟踪状态、响应事件、引发事件和进行配置。 实质上,RemoteRenderedModel 在 modelPath
中存储模型数据的远程路径。 它侦听 RemoteRenderingCoordinator 中的状态更改,以确认自己是否应自动加载或卸载定义的模型。 附加了 RemoteRenderedModel 的 GameObject 是远程内容的本地父级。
请注意,RemoteRenderedModel 脚本实现所包含的、教程资产中的 BaseRemoteRenderedModel 。 这种连接使远程模型视图控制器能够与脚本绑定。
在与 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,相对于现实世界,该阶段将保持不变。 我们将使用固定的阶段,这样模型本身在以后仍可以移动。
在场景中创建新的空游戏对象并将其命名为 ModelStage。
向 ModelStage 添加 WorldAnchor 组件
创建一个新的空游戏对象作为 ModelStage 的子级,并将其命名为 TestModel。
将 RemoteRenderedModel 脚本添加到 TestModel。
分别用“TestModel”和“builtin://Engine”填写
Model Display Name
和Model Path
。将 TestModel 对象放置在相机前面,位置为 x = 0, y = 0, z = 3 。
确保启用 AutomaticallyLoad。
在 Unity 编辑器中按“播放”来测试应用程序。
单击“连接”按钮授予权限,允许应用创建会话,连接到会话并自动加载模型。
当应用程序通过其状态进行监视时,请监视控制台。 请记住,某些状态可能需要一些时间才能完成,并且可能暂时没有进度更新。 最终,你会看到模型加载中的日志,然后场景中会很快呈现测试模型。
尝试在检查器中通过转换,或在场景视图中和在游戏视图中观察转换,移动和旋转 TestModel GameObject。
预配 Azure 中的 Blob 存储和自定义模型引入
现在,我们可以尝试加载自己的模型。 要执行此操作,你需要在 Azure 上配置 Blob 存储,并上传和转换模型,然后使用 RemoteRenderedModel 脚本加载模型。 如果此时你没有要加载的自己的模型,则可以安全地跳过自定义模型加载步骤。
按照 快速入门:转换要渲染的模型中指定的步骤进行操作。 对于本教程,请跳过“将新模型插入快速入门示例应用”部分。 引入模型的共享访问签名 (SAS) URI 后,请继续。
加载和渲染自定义模型
在场景中创建新的空 GameObject,并将其命名为类似于自定义模型。
将 RemoteRenderedModel 脚本添加到新创建的 GameObject。
使用适当的模型名称填充
Model Display Name
。使用在 Azure 中预配 Blob 存储和自定义模型引入步骤中创建的共享访问签名 (SAS) URI 填充
Model Path
。将 GameObject 放置在相机前面,位置为 x = 0, y = 0, z = 3。
确保启用 AutomaticallyLoad。
在 Unity 编辑器中按“播放”来测试应用程序。
连接会话后,控制台会显示当前会话状态和模型加载进度消息。
从场景中删除自定义模型对象。 本教程中的最佳实践将使用测试模型。 虽然 ARR 中支持多个模型,但在按照本教程操作时,最好一次支持一个远程模型。
后续步骤
现在你可以将自己的模型加载到 Azure 远程渲染并在应用程序中进行查看了! 接下来介绍如何操作模型。