Tutorial: Interfaces y modelos personalizados

En este tutorial, aprenderá a:

  • Agregar la utilidad Mixed Reality Toolkit al proyecto
  • Administrar el estado del modelo
  • Configurar Azure Blob Storage para la ingesta de modelos
  • Cargar y procesar modelos para representarlos

Requisitos previos

Introducción a Mixed Reality Toolkit (MRTK)

Mixed Reality Toolkit (MRTK) es un kit de herramientas multiplataforma para la creación de experiencias de realidad mixta. Usaremos MRTK 2.8.3 por sus características de interacción y visualización.

La guía oficial para importar MRTK contiene algunos pasos que no es necesario realizar. Solo estos tres pasos son necesarios:

  • Importar la versión 2.8.3 de "Mixed Reality Toolkit/Mixed Reality Toolkit Foundation" al proyecto a través de la herramienta de características de Mixed Reality (Importar MRTK).
  • Ejecute el Asistente de configuración de MRTK (Configurar MRTK).
  • Agregue MRTK a la escena actual (Agregar a escena). Use el perfil ARRMixedRealityToolkitConfigurationProfile en lugar del que se sugiere en el tutorial.

Importación de los recursos usados en este tutorial

A partir de este capítulo, se implementará un patrón modelo-vista-controlador básico en gran parte del material incluido. La parte modelo del patrón es el código específico de Azure Remote Rendering y la administración de estados relacionada con este servicio. Las partes vista y controlador del patrón se implementan mediante recursos de MRTK y algunos scripts personalizados. En este tutorial, se puede usar el modelo sin la parte vista-controlador que se implementa aquí. Esta separación le permite integrar fácilmente el código de este tutorial en su propia aplicación, donde se ocupará de la parte vista-controlador del modelo de diseño.

Con el uso de MRTK, hay varios scripts, elementos prefabricados y recursos que ahora se pueden agregar al proyecto para admitir interacciones e información visual. Estos recursos, conocidos como recursos del tutorial, se agrupan en un paquete de recursos de Unity, que se incluye en el repositorio de GitHub Azure Remote Rendering en "\Unity\TutorialAssets\TutorialAssets.unitypackage".

  1. Clone o descargue el repositorio de Git Azure Remote Rendering, si la descarga extrae el archivo ZIP en una ubicación conocida.
  2. En el proyecto de Unity, elija Recursos -> Importar paquete -> Paquete personalizado.
  3. En el explorador de archivos, vaya al directorio en el que ha clonado o descomprimido el repositorio Azure Remote Rendering y, después, seleccione .unitypackage que se encuentra en Unity -> TutorialAssets -> TutorialAssets.unitypackage
  4. Seleccione el botón Import (Importar) para importar el contenido del paquete en el proyecto.
  5. En el editor de Unity, seleccione Mixed Reality Toolkit -> Utilities -> Upgrade MRTK Standard Shader for Lightweight Render Pipeline (Mixed Reality Toolkit > Utilidades > Actualizar el sombreador estándar de MRTK para la canalización de presentación ligera) en la barra de menús superior y siga las indicaciones para actualizar el sombreador.

Una vez que MRTK y los recursos del tutorial estén configurados, compruebe que el perfil correcto esté seleccionado.

  1. Seleccione el elemento GameObject MixedRealityToolkit en la jerarquía de la escena.
  2. En el inspector, en el componente MixedRealityToolkit, cambie el perfil de configuración a ARRMixedRealityToolkitConfigurationProfile.
  3. Presione Ctrl+S para guardar los cambios.

En este paso, se configura MRTK, principalmente con los perfiles de HoloLens 2 predeterminados. Los perfiles proporcionados están preconfigurados de las siguientes maneras:

  • Desactive Profiler (presione 9 para activarlo o desactivarlo o diga "Mostrar u ocultar Profiler" en el dispositivo).
  • Desactive el cursor de mirada.
  • Habilite los clics del mouse de Unity para poder hacer clic en los elementos de la interfaz de usuario de MRTK con el mouse en lugar de con la mano simulada.

Adición del menú de la aplicación

La mayoría de los controladores de vista de este tutorial funcionan con clases base abstractas en lugar de con clases concretas. Este patrón ofrece mayor flexibilidad y nos permite proporcionar los controladores de vista automáticamente, al tiempo que le ayuda a conocer el código de Azure Remote Rendering. Por motivos de simplicidad, la clase RemoteRenderingCoordinator no tiene una clase abstracta y su controlador de vista funciona directamente con la clase concreta.

Ahora puede agregar el elemento prefabricado AppMenu a la escena para obtener información visual del estado de sesión actual. AppMenu también presentará el panel modal que el usuario usará para autorizar a la aplicación a conectarse a ARR.

  1. Busque el elemento prefabricado AppMenu en Assets/RemoteRenderingTutorial/Prefabs/AppMenu.

  2. Arrastre el elemento prefabricado AppMenu a la escena.

  3. Si ve un cuadro de diálogo para TMP Importer, siga las indicaciones para Importar TMP Essentials. A continuación, cierre el cuadro de diálogo del importador, ya que los ejemplos y los elementos adicionales no son necesarios.

  4. AppMenu está configurado para enlazarse automáticamente y proporcionar el modal de consentimiento para conectarse a una sesión, de modo que podemos quitar la omisión colocada anteriormente. En el objeto GameObject RemoteRenderingCoordinator, quite la omisión de autorización que hemos implementado anteriormente; para ello, presione el botón "-" en el evento On Requesting Authorization (Al solicitar autorización).

    Quitar omisión.

  5. Para probar el controlador de vista, presione Play (Reproducir) en el editor de Unity.

  6. En el editor, ahora que está configurado MRTK, puede usar las teclas WASD para cambiar la posición de la vista, o mantener presionado el botón derecho del mouse y mover el mouse para cambiar su dirección. Muévase un poco por la escena para hacerse una idea de los controles.

  7. En el dispositivo, puede subir la palma para llamar a AppMenu; en el editor de Unity, use la tecla de acceso directo "M".

  8. Si ha perdido de vista el menú, presione la tecla "M" para llamar al menú. El menú se coloca cerca de la cámara para facilitar la interacción.

  9. AppMenu presenta un elemento de interfaz de usuario para la autorización a la derecha de AppMenu. A partir de ahora, debe usar este elemento de interfaz de usuario para autorizar a la aplicación a administrar sesiones de representación remota.

    Autorización de la interfaz de usuario

  10. Detenga la reproducción de Unity para continuar con el tutorial.

Administrar el estado del modelo

Necesitamos un nuevo script llamado RemoteRenderedModel que se usa para el seguimiento del estado, la respuesta a eventos y su activación, además de la configuración. Básicamente, RemoteRenderedModel almacena la ruta de acceso remota a los datos del modelo en modelPath. Para comprobar si debe cargar o descargar automáticamente el modelo que define, escucha los cambios de estado en RemoteRenderingCoordinator. El objeto GameObject que tiene asociado RemoteRenderedModel será el objeto primario local para el contenido remoto.

Tenga en cuenta que el script RemoteRenderedModel implementa BaseRemoteRenderedModel, que procede de los recursos del tutorial. Esta conexión permite al controlador de vista del modelo remoto enlazarse con el script.

  1. Cree un script llamado RemoteRenderedModel en la misma carpeta que RemoteRenderingCoordinator. Reemplace todo el contenido por el código siguiente:

    // 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);
        }
    }
    

Básicamente, RemoteRenderedModel contiene los datos necesarios para cargar un modelo (en este caso, el SAS o el URI builtin:// ) y realiza un seguimiento del estado del modelo remoto. Cuando sea el momento de cargar el modelo, se llama al método LoadModel en RemoteRenderingCoordinator y la entidad que contiene el modelo se devuelve como referencia y para descarga.

Carga del modelo de prueba

Vamos a volver a cargar el modelo de prueba para probar el nuevo script. Para esta prueba, necesitamos un objeto GameObject para contener el script y que sea un objeto primario para el modelo de prueba; también necesitamos una fase virtual que contenga el modelo. La fase permanecerá fija en relación con el mundo real mediante WorldAnchor. Se usa una fase fija para que el propio modelo se pueda mover más adelante.

  1. Cree un elemento Game Object vacío en la escena y llámelo ModelStage.

  2. Incorporación de un componente de World Anchor a ModelStage

    Incorporación de un componente de WorldAnchor

  3. Cree un elemento Game Object vacío como elemento secundario de ModelStage y asígnele el nombre TestModel.

  4. Agregue el script RemoteRenderedModel a TestModel.

    Adición del componente RemoteRenderedModel

  5. Rellene Model Display Name y Model Path con "TestModel" y "builtin://Engine", respectivamente.

    Especificación de los detalles del modelo

  6. Coloque el objeto TestModel delante de la cámara, en la posición x = 0, y = 0, z = 3.

    Colocación del objeto

  7. Asegúrese de que AutomaticallyLoad está activado.

  8. Presione Play (Reproducir) en el editor de Unity para probar la aplicación.

  9. Para conceder autorización, haga clic en el botón Conectar para permitir que la aplicación cree una sesión, se conecte a ella y cargue automáticamente el modelo.

Vea en la consola como la aplicación avanza por sus estados. Tenga en cuenta que algunos estados pueden tardar algún tiempo en completarse y es posible que no haya actualizaciones de progreso durante un tiempo. Finalmente, verá los registros de la carga del modelo y, poco después, el modelo de prueba representado en la escena.

Pruebe mover y rotar el objeto GameObject de TestModel a través de Transformación en el inspector o en la vista Escena y observe la transformación en la vista Game.

Registro de Unity

Aprovisionamiento de Blob Storage en Azure e ingesta de modelos personalizados

Ahora podemos intentar cargar su propio modelo. Para ello, deberá configurar Blob Storage en Azure, cargar y convertir un modelo y, después, cargarlo con el script RemoteRenderedModel. Los pasos de carga de modelos personalizados se pueden omitir de forma segura si no tiene su propio modelo para cargar en este momento.

Siga los pasos especificados en el Inicio rápido: Conversión de un modelo para su representación. Para este tutorial, omita la sección Insertar un nuevo modelo en la aplicación de ejemplo del inicio rápido. Cuando tenga el URI de Firma de acceso compartido (SAS) del modelo ingerido, continúe con los pasos.

Carga y representación de un modelo personalizado

  1. Cree un elemento GameObject vacío en la escena y llámelo de forma parecida al modelo personalizado.

  2. Agregue el script RemoteRenderedModel al objeto GameObject recién creado.

    Adición del componente RemoteRenderedModel

  3. Rellene Model Display Name con un nombre adecuado para el modelo.

  4. Rellene el Model Path con el URI de la Firma de acceso compartido (SAS) del modelo que creó en el paso de aprovisionamiento de Blob Storage en Azure e ingesta del modelo personalizado.

  5. Coloque el elemento GameObject delante de la cámara, en la posición x = 0, y = 0, z = 3.

  6. Asegúrese de que AutomaticallyLoad está activado.

  7. Presione Play (Reproducir) en el editor de Unity para probar la aplicación.

    La consola mostrará el estado de sesión actual y también los mensajes de progreso de carga del modelo, una vez conectada la sesión.

  8. Quite el objeto de modelo personalizado de la escena. La mejor experiencia con este tutorial es mediante el uso del modelo de prueba. Aunque se admiten varios modelos en ARR, este tutorial se ha escrito para admitir uno solo modelo remoto a la vez.

Pasos siguientes

Ahora puede cargar sus propios modelos en Azure Remote Rendering y verlos en la aplicación. A continuación, le guiaremos en la manipulación de los modelos.