Поделиться через


Руководство по просмотру удаленно отрисоваемой модели

В этом руководстве описано следующее:

  • Подготовка экземпляра Удаленной отрисовки Azure
  • Создание и остановка сеанса отрисовки
  • Повторное использование имеющегося сеанса отрисовки
  • Подключение к сеансам и отключение от них
  • Загрузка моделей в сеанс отрисовки

Необходимые компоненты

Для работы с этим руководством необходимо следующее:

Подготовка экземпляра Удаленной отрисовки Azure

Чтобы получить доступ к службе "Удаленная отрисовка Azure", сначала необходимо создать учетную запись.

Создание проекта Unity

Совет

Репозиторий примеров Удаленной отрисовки Azure содержит проект, в котором выполнены инструкции всех учебников. Его можно использовать для справки. Готовый проект Unity можно найти в папке Unity\Tutorial-Complete.

Создайте проект в Unity Hub. В этом примере предполагается, что проект создается в папке с именем RemoteRendering.

Снимок экрана: Центр Unity, показывающий диалоговое окно

Включение пакетов Azure Удаленная отрисовка и OpenXR

Следуйте инструкциям по добавлению пакетов Azure Удаленная отрисовка и OpenXR в проект Unity.

Примечание.

Если Unity отображает диалоговое окно предупреждения после импорта пакета OpenXR с просьбой включить серверную часть собственной платформы для новой входной системы, нажмите кнопку "Нет ". Вы включите его на следующем шаге.

Настройка камеры

  1. Выберите узел Main Camera (Основная камера).

  2. Откройте контекстное меню, щелкнув правой кнопкой мыши компонент преобразования и выберите параметр "Сброс ".

    Снимок экрана: инспектор Unity для компонента Преобразования. Откроется контекстное меню и выбран параметр

  3. Задайте свойству Clear flags (Очистить флаги) значение Solid Color (Сплошной цвет).

  4. Для параметра Background (Фон) задайте значение Black (Черный, #000000) с полностью прозрачным (0) альфа-каналом (A).

    Снимок экрана: диалоговое окно цветового колеса Unity. Цвет имеет значение 0 для всех компонентов R G B A.

  5. Установите для отрезки плоскостейблизко = 0,1 и Far = 20. Эта настройка означает, что геометрия клипов отрисовки приближается к 10 см или более чем на 20 метров.

    Снимок экрана: инспектор Unity для компонента Камера.

Настройка параметров проекта

  1. Откройте Параметры редактирования > проекта...

  2. Выберите в меню со списком слева пункт Quality (Качество).

    1. Измените настройки Default Quality Level (Уровень качества по умолчанию) для всех платформ на значение Low (Низкий). Этот параметр обеспечивает более эффективную отрисовку локального содержимого и не влияет на качество удаленно отрисованного содержимого.

      Снимок экрана: диалоговое окно

    Примечание.

    В область этого руководства мы придерживаемся встроенного конвейера отрисовки Unity. Если вы хотите использовать универсальный конвейер отрисовки, ознакомьтесь с конвейерами отрисовки Unity для дополнительных действий по настройке.

  3. Выберите управление подключаемыми модулями XR в меню слева

    1. Нажмите кнопку "Установить подключаемый модуль XR".
    2. Перейдите на вкладку с параметрами универсальной платформы Windows, обозначенную значком Windows.
    3. Выберите поле Open XR проверка box в разделе "Поставщики подключаемых модулей"
    4. Если откроется диалоговое окно с запросом включить серверные части собственной платформы для новой системы ввода, выберите "Нет".

    Снимок экрана: диалоговое окно

    Примечание.

    Если группа функций Microsoft HoloLens отключена, подключаемый модуль OpenXR Для Windows Смешанная реальность отсутствует. Следуйте инструкциям по добавлению пакетов Azure Удаленная отрисовка и OpenXR для его установки.

  4. Выберите OpenXR в меню слева

    1. Настройка режима отправки глубины в 16 бит
    2. Добавьте профиль взаимодействия с рукой Майкрософт в профили взаимодействия.
    3. Включите следующие функции OpenXR:
      • Удаленная отрисовка Azure
      • Отслеживание рук
      • функции Смешанная реальность
      • Motion Controller Model (Модель контроллера движений)

    Снимок экрана: диалоговое окно

    Примечание.

    Если вы не видите необходимые функции OpenXR, перечисленные в подключаемом модуле Windows Смешанная реальность OpenXR, отсутствуют в проекте. Следуйте инструкциям по добавлению пакетов Azure Удаленная отрисовка и OpenXR для его установки.

  5. Выберите в меню со списком слева пункт Player (Проигрыватель).

    1. Перейдите на вкладку с параметрами универсальной платформы Windows, обозначенную значком Windows.
    2. Разверните другие Параметры
    3. В разделе "Отрисовка " измените цветовое пространство на линейную и перезапустите Unity при запросе.
    4. В разделе "Настройка" измените активную обработку входных данных в Unity и перезапустите его при запросе. Снимок экрана: диалоговое окно
    5. Развертывание Параметры публикации
    6. Прокрутите вниз до возможностей и выберите:
      • InternetClient;
      • InternetClientServer;
      • SpatialPerception;
      • PrivateNetworkClientServer (необязательно). Выберите этот параметр, если вы хотите подключить к устройству удаленный отладчик Unity.
    7. В разделе Supported Device Families (Поддерживаемые семейства устройств) установите флажки Holographic (Голографические) и DesktopСнимок экрана: диалоговое окно (Компьютер).
  6. Закройте или закрепите панель Project Settings (Параметры проекта).

  7. Открытие Параметры сборки файлов>

    1. Выберите Universal Windows Platform (Универсальная платформа Windows).
    2. Настройте параметры так, как указанно ниже.
    3. Нажмите кнопку "Переключить платформу ".
      Снимок экрана: диалоговое окно
  8. После смены платформы в Unity закройте панель сборки.

Проверка настроек проекта

Выполните следующие действия, чтобы проверить правильность параметров проекта:

  1. Выберите запись ValidateProject в меню RemoteRendering на панели инструментов редактора Unity.

  2. Просмотрите окно проверки проекта для ошибок и исправьте параметры проекта, если это необходимо.

    Снимок экрана: диалоговое окно проверки проекта Unity. В диалоговом окне показан список обязательных, рекомендуемых и правил разработки, которые все успешно проверка.

Примечание.

Если вы используете в проекте MRTK и включаете подсистему камеры, МРТК будет переопределять изменения, применяемые к камере вручную. Это касается и исправлений из средства ValidateProject.

Создание скрипта для координирования подключения и состояния службы "Удаленная отрисовка Azure"

Отображение удаленно преобразованных для просмотра моделей состоит из четырех основных этапов, продемонстрированных на блок-схеме ниже. Все этапы должны выполняться по порядку. Следующим шагом является создание скрипта, который управляет состоянием приложения и проходит через каждый необходимый этап.

Схема четырех этапов, необходимых для загрузки модели.

  1. В области Project (Проект) в разделе Assets (Ресурсы) создайте новую папку с именем RemoteRenderingCore. Затем внутри RemoteRenderingCore создайте другую папку с именем Scripts.

  2. Создайте скрипт C# с именем RemoteRenderingCoordinator. Проект должен выглядеть так:

    Снимок экрана: иерархия проектов Unity, содержащая новый скрипт.

    Этот скрипт координатора отслеживает состояние удаленной отрисовки и управляет им. Обратите внимание, что некоторые из этих кодов используются для поддержания состояния, предоставления функциональных возможностей другим компонентам, активации событий и хранения данных, относящихся к приложению, которые не связаны напрямую с Azure Удаленная отрисовка. Используйте приведенный ниже код в качестве отправной точки. Реализацию конкретного кода Удаленной отрисовки Azure мы рассмотрим далее в этом учебнике.

  3. Откройте скрипт RemoteRenderingCoordinator в редакторе кода и замените все его содержимое следующим кодом:

// 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 System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;

#if UNITY_WSA
using UnityEngine.XR.WSA;
#endif

/// <summary>
/// Remote Rendering Coordinator is the controller for all Remote Rendering operations.
/// </summary>

// Require the GameObject with a RemoteRenderingCoordinator to also have the ARRServiceUnity component
[RequireComponent(typeof(ARRServiceUnity))]
public class RemoteRenderingCoordinator : MonoBehaviour
{
    public enum RemoteRenderingState
    {
        NotSet,
        NotInitialized,
        NotAuthorized,
        NoSession,
        ConnectingToExistingRemoteSession,
        ConnectingToNewRemoteSession,
        RemoteSessionReady,
        ConnectingToRuntime,
        RuntimeConnected
    }

    public static RemoteRenderingCoordinator instance;

    // Account
    // RemoteRenderingDomain must be '<region>.mixedreality.azure.com' - if no '<region>' is specified, connections will fail
    // For most people '<region>' is either 'westus2' or 'westeurope'
    [SerializeField]
    private string remoteRenderingDomain = "westus2.mixedreality.azure.com";
    public string RemoteRenderingDomain
    {
        get => remoteRenderingDomain.Trim();
        set => remoteRenderingDomain = value;
    }

    [Header("Development Account Credentials")]
    [SerializeField]
    private string accountDomain = "<enter your account domain here>";
    public string AccountDomain
    {
        get => accountDomain.Trim();
        set => accountDomain = value;
    }

    [SerializeField]
    private string accountId = "<enter your account id here>";
    public string AccountId {
        get => accountId.Trim();
        set => accountId = value;
    }

    [SerializeField]
    private string accountKey = "<enter your account key here>";
    public string AccountKey {
        get => accountKey.Trim();
        set => accountKey = value;
    }

    // These settings are important. All three should be set as low as possible, while maintaining a good user experience
    // See the documentation around session management and the technical differences in session VM size
    [Header("New Session Defaults")]

    public RenderingSessionVmSize renderingSessionVmSize = RenderingSessionVmSize.Standard;
    public uint maxLeaseHours = 0;
    public uint maxLeaseMinutes = 20;

    [Header("Other Configuration")]

    [Tooltip("If you have a known active SessionID, you can fill it in here before connecting")]
    [SerializeField]
    private string sessionIDOverride;
    public string SessionIDOverride {
        get => sessionIDOverride.Trim();
        set => sessionIDOverride = value;
    }

    // When Automatic Mode is true, the coordinator will attempt to automatically proceed through the process of connecting and loading a model
    public bool automaticMode = true;

    public event Action RequestingAuthorization;
    public UnityEvent OnRequestingAuthorization = new UnityEvent();

    public event Action AuthorizedChanged;
    public UnityEvent OnAuthorizationChanged = new UnityEvent();
    private bool authorized;
    public bool Authorized
    {
        get => authorized;
        set
        {
            if (value == true) //This is a one-way value, once we're authorized it lasts until the app is shutdown.
            {
                authorized = value;
                AuthorizedChanged?.Invoke();
            }
        }
    }

    public delegate Task<SessionConfiguration> AccountInfoGetter();

    public static AccountInfoGetter ARRCredentialGetter
    {
        private get;
        set;
    }

    private RemoteRenderingState currentCoordinatorState = RemoteRenderingState.NotSet;
    public RemoteRenderingState CurrentCoordinatorState
    {
        get => currentCoordinatorState;
        private set
        {
            if (currentCoordinatorState != value)
            {
                currentCoordinatorState = value;
                Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "{0}", $"State changed to: {currentCoordinatorState}");
                CoordinatorStateChange?.Invoke(currentCoordinatorState);
            }
        }
    }

    public static event Action<RemoteRenderingState> CoordinatorStateChange;

    public static RenderingSession CurrentSession => instance?.ARRSessionService?.CurrentActiveSession;

    private ARRServiceUnity arrSessionService;

    private ARRServiceUnity ARRSessionService
    {
        get
        {
            if (arrSessionService == null)
                arrSessionService = GetComponent<ARRServiceUnity>();
            return arrSessionService;
        }
    }

    private async Task<SessionConfiguration> GetDevelopmentCredentials()
    {
        Debug.LogWarning("Using development credentials! Not recommended for production.");
        return await Task.FromResult(new SessionConfiguration(AccountDomain, RemoteRenderingDomain, AccountId, AccountKey));
    }

    /// <summary>
    /// Keep the last used SessionID, when launching, connect to this session if its available
    /// </summary>
    private string LastUsedSessionID
    {
        get
        {
            if (!string.IsNullOrEmpty(SessionIDOverride))
                return SessionIDOverride;

            if (PlayerPrefs.HasKey("LastUsedSessionID"))
                return PlayerPrefs.GetString("LastUsedSessionID");
            else
                return null;
        }
        set
        {
            PlayerPrefs.SetString("LastUsedSessionID", value);
        }
    }

    public void Awake()
    {
        //Forward events to Unity events
        RequestingAuthorization += () => OnRequestingAuthorization?.Invoke();
        AuthorizedChanged += () => OnAuthorizationChanged?.Invoke();

        //Attach to event
        AuthorizedChanged += RemoteRenderingCoordinator_AuthorizedChanged;

        if (instance == null)
            instance = this;
        else
            Destroy(this);

        CoordinatorStateChange += AutomaticMode;

        CurrentCoordinatorState = RemoteRenderingState.NotInitialized;
    }

    private void RemoteRenderingCoordinator_AuthorizedChanged()
    {
        if (CurrentCoordinatorState != RemoteRenderingState.NotAuthorized)
            return; //This isn't valid from any other state than NotAuthorized


        //We just became authorized to connect to Azure
        InitializeSessionService();
    }

    /// <summary>
    /// Automatic mode attempts to automatically progress through the connection and loading steps. Doesn't handle error states.
    /// </summary>
    /// <param name="currentState">The current state</param>
    private async void AutomaticMode(RemoteRenderingState currentState)
    {
        if (!automaticMode)
            return;

        //Add a small delay for visual effect
        await Task.Delay(1500);
        switch (currentState)
        {
            case RemoteRenderingState.NotInitialized:
                InitializeARR();
                break;
            case RemoteRenderingState.NotAuthorized:
                RequestAuthorization();
                break;
            case RemoteRenderingState.NoSession:
                JoinRemoteSession();
                break;
            case RemoteRenderingState.RemoteSessionReady:
                ConnectRuntimeToRemoteSession();
                break;
        }
    }

    /// <summary>
    /// Initializes ARR, associating the main camera
    /// Note: This must be called on the main Unity thread
    /// </summary>
    public void InitializeARR()
    {
        //Implement me
    }

    /// <summary>
    /// Create a new remote session manager
    /// If the ARRCredentialGetter is set, use it as it, otherwise use the development credentials 
    /// </summary>
    public async void InitializeSessionService()
    {
        //Implement me
    }

    /// <summary>
    /// Trigger the event for checking authorization, respond to this event by prompting the user for authentication
    /// If authorized, set Authorized = true
    /// </summary>
    public void RequestAuthorization()
    {
        RequestingAuthorization?.Invoke();
    }

    public void BypassAuthorization()
    {
        Authorized = true;
    }

    /// <summary>
    /// Attempts to join an existing session or start a new session
    /// </summary>
    public async void JoinRemoteSession()
    {
        //Implement me
    }

    public async void StopRemoteSession()
    {
        //Implement me
    }

    private async Task<bool> IsSessionAvailable(string sessionID)
    {
        bool sessionAvailable = false;
        try
        {
            RenderingSessionPropertiesArrayResult result = await ARRSessionService.Client.GetCurrentRenderingSessionsAsync();
            if (result.ErrorCode == Result.Success)
            {
                RenderingSessionProperties[] properties = result.SessionProperties;
                if (properties != null)
                {
                    sessionAvailable = properties.Any(x => x.Id == sessionID && (x.Status == RenderingSessionStatus.Ready || x.Status == RenderingSessionStatus.Starting));
                }
            }
            else
            {
                Debug.LogError($"Failed to get current rendering sessions. Error: {result.Context.ErrorMessage}");
            }
        }
        catch (RRException ex)
        {
            Debug.LogError($"Failed to get current rendering sessions. Error: {ex.Message}");
        }
        return sessionAvailable;
    }

    /// <summary>
    /// Connects the local runtime to the current active session, if there's a session available
    /// </summary>
    public async void ConnectRuntimeToRemoteSession()
    {
        //Implement me
    }

    public void DisconnectRuntimeFromRemoteSession()
    {
        //Implement me
    }

    /// <summary>
    /// The session must have its runtime pump updated.
    /// The Connection.Update() will push messages to the server, receive messages, and update the frame-buffer with the remotely rendered content.
    /// </summary>
    private void LateUpdate()
    {
        ARRSessionService?.CurrentActiveSession?.Connection?.Update();
    }

    /// <summary>
    /// Loads a model into the remote session for rendering
    /// </summary>
    /// <param name="modelPath">The model's path</param>
    /// <param name="progress">A call back method that accepts a float progress value [0->1]</param>
    /// <param name="parent">The parent Transform for this remote entity</param>
    /// <returns>An awaitable Remote Rendering Entity</returns>
    public async Task<Entity> LoadModel(string modelPath, UnityEngine.Transform parent = null, Action<float> progress = null)
    {
        //Implement me
        return null;
    }

    private async void OnRemoteSessionStatusChanged(ARRServiceUnity caller, RenderingSession session)
    {
        var properties = await session.GetPropertiesAsync();

        switch (properties.SessionProperties.Status)
        {
            case RenderingSessionStatus.Error:
            case RenderingSessionStatus.Expired:
            case RenderingSessionStatus.Stopped:
            case RenderingSessionStatus.Unknown:
                CurrentCoordinatorState = RemoteRenderingState.NoSession;
                break;
            case RenderingSessionStatus.Starting:
                CurrentCoordinatorState = RemoteRenderingState.ConnectingToNewRemoteSession;
                break;
            case RenderingSessionStatus.Ready:
                CurrentCoordinatorState = RemoteRenderingState.RemoteSessionReady;
                break;
        }
    }

    private void OnLocalRuntimeStatusChanged(ConnectionStatus status, Result error)
    {
        switch (status)
        {
            case ConnectionStatus.Connected:
                CurrentCoordinatorState = RemoteRenderingState.RuntimeConnected;
                break;
            case ConnectionStatus.Connecting:
                CurrentCoordinatorState = RemoteRenderingState.ConnectingToRuntime;
                break;
            case ConnectionStatus.Disconnected:
                CurrentCoordinatorState = RemoteRenderingState.RemoteSessionReady;
                break;
        }
    }
}

Создание игрового объекта Удаленной отрисовки Azure

Координатор удаленной отрисовки и его обязательный скрипт (ARRServiceUnity) реализуют классы MonoBehaviour, которые должны быть присоединены к игровому объекту в сцене. Скрипт ARRServiceUnity предоставляется службой "Удаленная отрисовка Azure", реализуя многие из ее функциональных возможностей для подключения к удаленным сеансам и управления ими.

  1. Создайте новый GameObject на сцене (CTRL+SHIFT+N или GameObject-Create> Empty) и присвойте ему имя RemoteRenderingCoordinator.
  2. Добавьте скрипт RemoteRenderingCoordinator в RemoteRenderingCoordinator GameObject.
    Снимок экрана: диалоговое окно добавления компонента Unity. Текстовое поле поиска содержит текст RemoteRenderingCoordinator.
  3. Убедитесь, что скрипт ARRServiceUnity, который отображается в инспекторе как служба, автоматически добавлен в игровой объект. Интересно, почему так должно быть? Это следствие размещения [RequireComponent(typeof(ARRServiceUnity))] в верхней части скрипта RemoteRenderingCoordinator.
  4. Добавьте учетные данные Azure Удаленная отрисовка, домен учетной записи и домен Удаленная отрисовка в скрипт координатора:
    Снимок экрана: инспектор Unity скрипта Удаленная отрисовка координатора. Выделены поля ввода учетных данных.

Инициализация Удаленной отрисовки Azure

Теперь, когда у нас есть платформа для нашего координатора, мы реализуем каждый из четырех этапов, начиная с инициализации Удаленная отрисовка.

Схема четырех этапов, необходимых для загрузки модели. Выделен первый этап

При инициализации служба "Удаленная отрисовка Azure" получает сведения о том, какой объект "Камера" следует использовать для отрисовки, и переводит конечный автомат в состояние NotAuthorized. Это состояние означает, что он инициализирован, но еще не авторизован для подключения к сеансу. Поскольку запуск сеанса Удаленной отрисовки Azure сопряжен с затратами, нам необходимо убедиться, что пользователь хочет продолжить работу.

При переходе в состояние NotAuthorized вызывается метод CheckAuthorization, который инициирует событие RequestingAuthorization и определяет, какие учетные данные нужно использовать (AccountInfo определяется в верхней части класса и использует учетные данные, определенные с помощью инспектора Unity на предыдущем шаге).

Примечание.

Удаленная отрисовка Azure не поддерживает повторную компиляцию во время выполнения. Изменение и сохранение скрипта в режиме воспроизведения может привести к зависанию Unity, что потребует принудительного завершения работы Unity через диспетчер задач. Перед редактированием скриптов всегда проверяйте, остановлен ли режим воспроизведения.

  1. Замените содержимое InitializeARR и InitializeSessionService готовым кодом, приведенным ниже.

    /// <summary>
    /// Initializes ARR, associating the main camera
    /// Note: This must be called on the main Unity thread
    /// </summary>
    public void InitializeARR()
    {
        RemoteManagerUnity.InitializeManager(new RemoteUnityClientInit  (Camera.main));
    
        CurrentCoordinatorState = RemoteRenderingState.NotAuthorized;
    }
    
    /// <summary>
    /// Create a new remote session manager
    /// If the ARRCredentialGetter is set, use it as it, otherwise use  the development credentials 
    /// </summary>
    public async void InitializeSessionService()
    {
        if (ARRCredentialGetter == null)
            ARRCredentialGetter = GetDevelopmentCredentials;
    
        var sessionConfiguration = await ARRCredentialGetter.Invoke();
    
        ARRSessionService.OnSessionStatusChanged +=     OnRemoteSessionStatusChanged;
    
        try
        {
            ARRSessionService.Initialize(sessionConfiguration);
        }
        catch (ArgumentException argumentException)
        {
            Debug.LogError(argumentException.Message);
            CurrentCoordinatorState = RemoteRenderingState. NotAuthorized;
            return;
        }
    
        CurrentCoordinatorState = RemoteRenderingState.NoSession;
    }
    

Чтобы выполнить переход от NotAuthorized до NoSession, мы обычно представляем модальное диалоговое окно пользователю, чтобы они могли выбрать (и мы делаем это только в другой главе). Сейчас мы автоматически обходим проверка авторизации путем вызова ByPassAuthentication сразу после активации события RequestingAuthorization.

  1. Выберите игровой объект RemoteRenderingCoordinator и найдите событие Unity OnRequestingAuthorization, доступное в инспекторе компонента RemoteRenderingCoordinator.

  2. Добавьте новое событие, нажав кнопку "+" в правом нижнем углу.

  3. Перетащите компонент в собственное событие, чтобы ссылаться на себя. Снимок экрана: инспектор Unity скрипта Удаленная отрисовка координатора. Выделена строка заголовка компонента, а стрелка подключает ее к событию

  4. В раскрывающемся списке выберите RemoteRenderingCoordinator —> BypassAuthorization.
    Снимок экрана: событие

Создание удаленного сеанса или присоединение к нему

Второй этап — создание или присоединение сеанса Удаленная отрисовка (дополнительные сведения о сеансах отрисовки см. в разделе Удаленная отрисовка Сеансы).

Схема четырех этапов, необходимых для загрузки модели. Выделен второй этап

Удаленный сеанс — это место, где модели преобразовываются для просмотра. Метод JoinRemoteSession() пытается присоединиться к существующему сеансу, отслеживаемого с помощью свойства LastUsedSessionID или если в SessionIDOverride назначен идентификатор активного сеанса. Интерфейс SessionIDOverride предназначен только для отладки. Его следует использовать только в случае, если известно, что сеанс существует и необходимо подключиться к нему явно.

Если сеансы недоступны, создается новый сеанс. Создание сеанса, однако, требует много времени. Поэтому следует пытаться создавать сеансы только при необходимости и повторно использовать их по возможности (см. статью "Коммерческий готовый: пул сеансов", "Планирование" и "Рекомендации " для получения дополнительных сведений об управлении сеансами).

Совет

StopRemoteSession() завершит активный сеанс. Во избежание ненужных расходов следует всегда останавливать сеансы, если они больше не нужны.

Теперь конечный автомат перейдет в состояние ConnectingToNewRemoteSession или ConnectingToExistingRemoteSession в зависимости от доступных сеансов. Открытие существующего сеанса или создание нового сеанса активирует событие ARRSessionService.OnSessionStatusChanged , выполняя наш метод OnRemoteSessionStatusChanged . В идеале это приводит к продвижению компьютера состояния на RemoteSessionReady.

  1. Чтобы присоединиться к новому сеансу, измените код, заменив методы JoinRemoteSession( ) и StopRemoteSession( ) приведенными ниже готовыми примерами.
/// <summary>
/// Attempts to join an existing session or start a new session
/// </summary>
public async void JoinRemoteSession()
{
    //If there's a session available that previously belonged to us, and it's ready, use it. Otherwise start a new session.
    RenderingSessionProperties joinResult;
    if (await IsSessionAvailable(LastUsedSessionID))
    {
        CurrentCoordinatorState = RemoteRenderingState.ConnectingToExistingRemoteSession;
        joinResult = await ARRSessionService.OpenSession(LastUsedSessionID);
    }
    else
    {
        CurrentCoordinatorState = RemoteRenderingState.ConnectingToNewRemoteSession;
        joinResult = await ARRSessionService.StartSession(new RenderingSessionCreationOptions(renderingSessionVmSize, (int)maxLeaseHours, (int)maxLeaseMinutes));
    }

    if (joinResult.Status == RenderingSessionStatus.Ready || joinResult.Status == RenderingSessionStatus.Starting)
    {
        LastUsedSessionID = joinResult.Id;
    }
    else
    {
        //The session should be ready or starting, if it's not, something went wrong
        await ARRSessionService.StopSession();
        if(LastUsedSessionID == SessionIDOverride)
            SessionIDOverride = "";
        CurrentCoordinatorState = RemoteRenderingState.NoSession;
    }
}

public async void StopRemoteSession()
{
    if (ARRSessionService.CurrentActiveSession != null)
    {
        await ARRSessionService.CurrentActiveSession.StopAsync();
    }
}

Если вы хотите сэкономить время, повторно использовав сеансы, отключите параметр Auto-Stop Session (Автоматическая остановка сеанса) в компоненте ARRServiceUnity. Помните, что при этом сеансы продолжают выполняться, даже если к ним ничто не подключено. Сеанс может выполняться до тех пор, пока сервер не завершит его по истечении времени MaxLeaseTime. Значение MaxLeaseTime можно изменить в координаторе Удаленной отрисовки в разделе New Session Defaults (Значения по умолчанию для нового сеанса). С другой стороны, если вы автоматически завершаете работу каждого сеанса при отключении, вам придется ждать запуска нового сеанса каждый раз, что может быть длительным процессом.

Примечание.

Остановка сеанса произойдет мгновенно, и ее нельзя будет отменить. После остановки необходимо создать сеанс с теми же затратами на запуск.

Подключение локальной среды выполнения к удаленному сеансу

Теперь приложению необходимо подключить свою локальную среду выполнения к удаленному сеансу.

Схема четырех этапов, необходимых для загрузки модели. Выделен третий этап

Приложение также должно ожидать передачи данных о событиях, связанных с подключением между средой выполнения и текущим сеансом. Эти изменения состояния обрабатываются в OnLocalRuntimeStatusChanged. Этот код перемещает состояние в Подключение ingToRuntime. После подключения в OnLocalRuntimeStatusChanged состояние переходит в среду выполнения Подключение. Подключение к среде выполнения — это последнее состояние, с которым имеет дело координатор. Это означает, что для приложения выполнены все общие настройки и оно готово к началу работы в конкретном сеансе (загрузке моделей и преобразованию их для просмотра).

  1. Замените методы ConnectRuntimeToRemoteSession( ) и DisconnectRuntimeFromRemoteSession( ) приведенными ниже готовыми версиями.
  2. Обратите особое внимание на метод Unity под названием LateUpdate и на тот факт, что он обновляет текущий активный сеанс. Это позволяет текущему сеансу отправлять и получать сообщения и обновлять буфер кадров кадрами, полученными из удаленного сеанса. Очень важно обеспечить правильную работу Удаленной отрисовки Azure.
/// <summary>
/// Connects the local runtime to the current active session, if there's a session available
/// </summary>
public async void ConnectRuntimeToRemoteSession()
{
    if (ARRSessionService == null || ARRSessionService.CurrentActiveSession == null)
    {
        Debug.LogError("Not ready to connect runtime");
        return;
    }

    // Connect the local runtime to the currently connected session
    // This session is set when connecting to a new or existing session

    ARRSessionService.CurrentActiveSession.ConnectionStatusChanged += OnLocalRuntimeStatusChanged;
    await ARRSessionService.CurrentActiveSession.ConnectAsync(new RendererInitOptions());
}

public void DisconnectRuntimeFromRemoteSession()
{
    if (ARRSessionService == null || ARRSessionService.CurrentActiveSession == null || ARRSessionService.CurrentActiveSession.ConnectionStatus != ConnectionStatus.Connected)
    {
        Debug.LogError("Runtime not connected!");
        return;
    }

    ARRSessionService.CurrentActiveSession.Disconnect();
    ARRSessionService.CurrentActiveSession.ConnectionStatusChanged -= OnLocalRuntimeStatusChanged;
    CurrentCoordinatorState = RemoteRenderingState.RemoteSessionReady;
}

Примечание.

Подключение локальной среды выполнения к удаленному сеансу зависит от метода Update, вызываемого для текущего активного сеанса. Если вы обнаружите, что приложение никогда не переходит в состояние ConnectingToRuntime, убедитесь, что вы регулярно вызываете Update для активного сеанса.

Загрузка модели

На месте необходимой основы можно загрузить модель в удаленный сеанс и начать получать кадры.

Схема четырех этапов, необходимых для загрузки модели. Выделен четвертый этап

Метод LoadModel должен принимать путь к модели, обработчик выполнения и родительское преобразование. Эти аргументы используются для загрузки модели в удаленный сеанс, обновления пользователя на ход загрузки и ориентации удаленно отображаемой модели на основе родительского преобразования.

  1. Полностью замените метод LoadModel следующим кодом:

    /// <summary>
    /// Loads a model into the remote session for rendering
    /// </summary>
    /// <param name="modelName">The model's path</param>
    /// <param name="parent">The parent Transform for this remote entity</param>
    /// <param name="progress">A call back method that accepts a float progress value [0->1]</param>
    /// <returns>An awaitable Remote Rendering Entity</returns>
    public async Task<Entity> LoadModel(string modelPath, UnityEngine.Transform parent = null, Action<float> progress = null)
    {
        //Create a root object to parent a loaded model to
        var modelEntity = ARRSessionService.CurrentActiveSession.Connection.CreateEntity();
    
        //Get the game object representation of this entity
        var modelGameObject = modelEntity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
    
        //Ensure the entity will sync its transform with the server
        var sync = modelGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    
        //Parent the new object under the defined parent
        if (parent != null)
        {
            modelGameObject.transform.SetParent(parent, false);
            modelGameObject.name = parent.name + "_Entity";
        }
    
        //Load a model that will be parented to the entity
        var loadModelParams = new LoadModelFromSasOptions(modelPath, modelEntity);
        var loadModelAsync = ARRSessionService.CurrentActiveSession.Connection.LoadModelFromSasAsync(loadModelParams, progress);
        var result = await loadModelAsync;
        return modelEntity;
    }
    

Приведенный выше код выполняет следующие действия:

  1. Создает удаленную сущность.
  2. Создайте локальный игровой объект для представления удаленной сущности.
  3. Настройте локальный GameObject для синхронизации его состояния (то есть преобразования) с удаленной сущностью каждый кадр.
  4. Загружает данные модели из Хранилища BLOB-объектов в удаленную сущность.
  5. Возвращает родительскую сущность для ссылки на нее в дальнейшем.

Просмотр тестовой модели

Теперь у нас есть весь код, необходимый для просмотра удаленно преобразованной для просмотра модели — все четыре необходимых этапа удаленной отрисовки завершены. Осталось добавить немного кода для запуска процесса загрузки модели.

Схема четырех этапов, необходимых для загрузки модели. Все этапы помечены как завершенные.

  1. Добавьте следующий код в класс RemoteRenderingCoordinator. Его можно поместить под методом LoadModel.

    private bool loadingTestModel = false;
    [ContextMenu("Load Test Model")]
    public async void LoadTestModel()
    {
        if(CurrentCoordinatorState != RemoteRenderingState.RuntimeConnected)
        {
            Debug.LogError("Please wait for the runtime to connect before loading the test model. Try again later.");
            return;
        }
        if(loadingTestModel)
        {
            Debug.Log("Test model already loading or loaded!");
            return;
        }
        loadingTestModel = true;
    
        // Create a parent object to use for positioning
        GameObject testParent = new GameObject("TestModelParent");
        testParent.transform.position = new Vector3(0f, 0f, 3f);
    
        // The 'built in engine path' is a special path that references a test model built into Azure Remote Rendering.
        await LoadModel("builtin://Engine", testParent.transform, (progressValue) => Debug.Log($"Loading Test Model progress: {Math.Round(progressValue * 100, 2)}%"));
    }
    

    Этот код создает игровой объект, который будет служить родительским для тестовой модели. Затем вызовите метод LoadModel для загрузки модели "builtin://Engine". Это встроенный ресурс службы "Удаленная отрисовка Azure", предназначенный для тестирования отрисовки.

  2. Сохраните код.

  3. Нажмите кнопку воспроизведения в редакторе Unity, чтобы начать процесс подключения к Удаленной отрисовке Azure и создания нового сеанса.

  4. Однако в режиме игры вы не увидите большое количество изменений в консоли. Скорее всего, оно перейдет в состояние ConnectingToNewRemoteSession и, возможно, будет оставаться в нем до пяти минут.

  5. Выберите игровой объект RemoteRenderingCoordinator, чтобы просмотреть в инспекторе вложенные в него скрипты. Следите за обновлением компонента Service в процессе его инициализации и подключения.

  6. Дождитесь перехода в состояние RuntimeConnected, следя за выходными данными в консоли.

  7. После подключения среды выполнения щелкните в инспекторе правой кнопкой мыши RemoteRenderingCoordinator, чтобы открыть контекстное меню. Затем выберите параметр модели нагрузочного теста в контекстном меню, добавленный [ContextMenu("Load Test Model")] частью приведенного выше кода.

    Снимок экрана: инспектор Unity скрипта Удаленная отрисовка координатора. Выделена инструкция сначала щелкнуть правой кнопкой мыши заголовок и выбрать модель нагрузочного теста в контекстном меню.

  8. Просмотрите в консоли выходные данные ProgressHandler, переданные в метод LoadModel.

  9. Просмотрите модель, удаленно преобразованную для просмотра.

Примечание.

Удаленная модель никогда не отображается в представлении сцены. Она видна только в игровом представлении. Это связано с тем, что Удаленная отрисовка Azure удаленно преобразовывает кадры для просмотра (точнее, для перспективы камеры игрового представления) и не распознает камеру редактора (используемую для отрисовки представления сцены).

Следующие шаги

Снимок экрана: Unity, на котором выполняется проект в режиме воспроизведения. Двигатель автомобиля отрисовывается в центре окна просмотра.

Поздравляем! Вы создали базовое приложение, с помощью которого можно просматривать удаленно преобразованные для просмотра модели с помощью Удаленной отрисовки Azure. В следующем руководстве мы интегрируем MRTK и импортируем собственные модели.