教學課程:檢視遠端轉譯的模型

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

  • 布建 Azure 遠端轉譯 (ARR) 實例
  • 建立和停止轉譯會話
  • 重複使用現有的轉譯會話
  • 連線與會話中斷連線
  • 將模型載入轉譯會話

必要條件

在本教學課程中,您需要:

布建 Azure 遠端轉譯 (ARR) 實例

若要存取 Azure 遠端轉譯服務,您必須先 建立帳戶

建立新的 Unity 專案

提示

ARR 範例存放庫 包含已完成所有教學課程的專案,可作為參考。 查看 Unity\Tutorial-Complete 以取得完整的 Unity 專案。

從 Unity 中樞建立新的專案。 在此範例中,我們假設專案正在名為 RemoteRendering 的資料夾中建立。

Screenshot of Unity Hub showing the Create a New Unity Project dialog. The panel 3D is selected.

包含 Azure 遠端轉譯 和 OpenXR 套件

請遵循如何將 Azure 遠端轉譯 和 OpenXR 套件 新增至 Unity 專案的指示。

注意

如果 Unity 在匯入 OpenXR 套件之後顯示警告對話方塊,詢問是否要為新的輸入系統啟用原生平臺後端,請按一下 [立即否 ]。 您會在稍後的步驟中啟用它。

設定相機

  1. 選取 [ 主要相機 ] 節點。

  2. 以滑鼠右鍵按一下 [轉換 ] 元件,然後選取 [ 重設 ] 選項,以開啟操作功能表:

    Screenshot of the Unity inspector for a Transform component. The context menu is opened and Reset is selected.

  3. 將 [清除旗標 ] 設定 [純色]

  4. 將 Background 設定 Black (#0000000),且完全透明 (0) Alpha (A)

    Screenshot of the Unity Color wheel dialog. The color is set to 0 for all R G B A components.

  5. 將裁剪平面設定 Near = 0.1 Far = 20 。 此設定表示轉譯裁剪幾何,其距離超過 10 公分或遠超過 20 公尺。

    Screenshot of the Unity inspector for a Camera component.

調整專案設定

  1. 開啟 [ 編輯 > 專案] 設定...

  2. 從左側清單功能表選取 [品質 ]

    1. 將所有 平臺的預設品質等級 變更為 [低 ]。 此設定可讓您更有效率地轉譯本機內容,且不會影響遠端轉譯內容的品質。

      Screenshot of the Unity Project Settings dialog. The Quality entry is selected in the list on the left. The context menu for the default quality level is opened on the right. The low entry is selected.

    注意

    在本教學課程的範圍內,我們會堅持 Unity 內建轉譯管線。 如果您想要使用通用轉譯管線,請參閱 Unity 轉譯管線 以取得其他設定步驟。

  3. 從左側清單功能表選取 [XR 外掛程式管理 ]

    1. 按一下 [ 安裝 XR 外掛程式管理 ] 按鈕。
    2. 選取 [ 通用 Windows 平臺設定 ] 索引標籤,以 Windows 圖示表示。
    3. 選取 [外掛程式提供者] 底下的 [ 開啟 XR] 核取方塊
    4. 如果開啟對話方塊,要求您為新的輸入系統啟用原生平臺後端,請選取 [否 ]。

    Screenshot of the Unity Project Settings dialog. The XR Plug-in Management entry is selected in the list on the left. The tab with the windows logo is highlighted on the right. The Open XR checkbox below it is also highlighted.

    注意

    如果 Microsoft HoloLens 功能群組 已停用,您的專案遺漏 Windows Mixed Reality OpenXR 外掛程式。 請遵循如何 新增 Azure 遠端轉譯 和 OpenXR 套件 以安裝它的指示。

  4. 從左側清單功能表選取 [OpenXR ]

    1. 將深度提交模式 設定 深度 16 位
    2. Microsoft Hand Interaction Profile 新增至 互動設定檔
    3. 啟用下列 OpenXR 功能:
      • Azure 遠端轉譯
      • 手部追蹤
      • 混合實境功能
      • 動作控制器模型

    Screenshot of the Unity Project Settings dialog. The Open XR subentry is selected in the list on the left. Highlights on the right side are placed on the Depth Submission Mode, Interaction Profiles, and Open XR feature settings.

    注意

    如果您沒有看到所需的 OpenXR 功能,則專案遺漏 Windows Mixed Reality OpenXR 外掛程式。 請遵循如何 新增 Azure 遠端轉譯 和 OpenXR 套件 以安裝它的指示。

  5. 從左側清單功能表選取 [播放機 ]

    1. 選取 [ 通用 Windows 平臺設定 ] 索引標籤,以 Windows 圖示表示。
    2. 展開 [其他設定
    3. 在 [轉譯 色彩空間] 底下 ,將 [色彩空間 ] 變更 [線性 ],並在要求您時重新開機 Unity。
    4. 在 [組態] 下 ,將 [作用中輸入處理 ] 變更 [兩者] ,並在要求您時重新開機 Unity。 Screenshot of the Unity Project Settings dialog. The Player entry is selected in the list on the left. Highlights on the right side are placed on the tab with the Windows logo, the Color Space setting, and the Active input Handling setting.
    5. 展開 [發佈設定
    6. 向下捲動至 [ 功能 ],然後選取:
      • InternetClient
      • InternetClientServer
      • SpatialPerception
      • PrivateNetworkClientServer 選擇性 )。 如果您想要將 Unity 遠端偵錯程式連線到您的裝置,請選取此選項。
    7. 在 [支援的裝置系列 ] 下 ,啟用 全像攝影 桌面Screenshot of the Unity Project Settings dialog. The Player entry is selected in the list on the left. Highlights on the right side are placed on the Capabilities and the Supported Device Families settings.
  6. 關閉或停駐 [專案設定 ] 面板

  7. 開啟 檔案 > 建置設定

    1. 選取 通用 Windows 平臺
    2. 設定您的設定以符合以下找到的設定
    3. 按下 [ 切換平臺] 按鈕。
      Screenshot of the Unity Build Settings dialog. The Universal Windows Platform entry is selected in the list on the left. Highlights on the right side are placed on the settings dropdown boxes and the Switch Platform button.
  8. 在 Unity 變更平臺之後,關閉組建面板。

驗證專案設定

執行下列步驟來驗證專案設定是否正確。

  1. Unity 編輯器工具列中的 RemoteRendering 功能表選擇 ValidateProject 專案。

  2. 檢閱 [ 專案驗證程式 ] 視窗是否有錯誤,並視需要修正專案設定。

    Screenshot of the Unity Project Validator dialog. The dialog shows a list of required, recommended, and development rules that are all successful checked.

注意

如果您在專案中使用 MRTK 並啟用相機子系統,MRTK 將會覆寫套用至相機的手動變更。 這包括 ValidateProject 工具的修正程式。

建立腳本以協調 Azure 遠端轉譯連線和狀態

有四個基本階段可顯示遠端轉譯的模型,如下流程圖中所述。 每個階段都必須依序執行。 下一個步驟是建立腳本來管理應用程式狀態,並繼續進行每個必要階段。

Diagram of the four stages required to load a model.

  1. 在 [專案 ] 窗格的 [ 資產 ] 底下 ,建立名為 RemoteRenderingCore 的新資料夾。 然後在 RemoteRenderingCore ,建立另一個名為 Scripts 的資料夾

  2. 建立 名為 RemoteRenderingCoordinator 的新 C# 腳本 。 您的專案看起來應該像這樣:

    Screenshot of Unity Project hierarchy containing the new script.

    此協調器腳本會追蹤和管理遠端轉譯狀態。 請注意,此程式碼中的部分用於維護狀態、向其他元件公開功能、觸發事件,以及儲存與 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 遠端轉譯 GameObject

遠端轉譯協調器及其必要的腳本 ( ARRServiceUnity ) 都是必須附加至場景中 GameObject 的 MonoBehaviours。 ARR 所提供的 ARRServiceUnity 腳本會公開大部分 ARR 的功能,以便連線和管理遠端會話。

  1. 在場景中建立新的 GameObject(Ctrl+Shift+N 或 GameObject-Create > Empty ),並將它命名為 RemoteRenderingCoordinator
  2. RemoteRenderingCoordinator 腳本新增至 RemoteRenderingCoordinator GameObject。
    Screenshot of the Unity Add Component dialog. The search text field contains the text RemoteRenderingCoordinator.
  3. 確認 ARRServiceUnity 腳本在偵測器中顯示為 服務 ,會自動新增至 GameObject。 如果您想知道,這是 RemoteRenderingCoordinator 腳本頂端 的結果 [RequireComponent(typeof(ARRServiceUnity))]
  4. 將 Azure 遠端轉譯認證、您的帳戶網域和遠端轉譯網域新增至協調器腳本:
    Screenshot of the Unity inspector of the Remote Rendering Coordinator Script. The credential input fields are highlighted.

初始化 Azure 遠端轉譯

既然我們有協調器架構,接下來我們將從 Initialize 遠端轉譯 開始 ,實作四個階段中的每一個階段。

Diagram of the four stages required to load a model. The first stage

Initialize 會告訴 Azure 遠端轉譯要用於轉譯和將狀態機器進度為 NotAuthorized 的相機物件。 此狀態表示其已初始化,但尚未獲得連線到會話的授權。 由於啟動 ARR 會話會產生成本,因此我們需要確認使用者想要繼續。

輸入 NotAuthorized 狀態時, 會呼叫 CheckAuthorization,這會叫 用 RequestingAuthorization 事件並判斷要使用的帳號憑證( AccountInfo 定義在類別頂端附近,並使用您在上述步驟中透過 Unity Inspector 定義的認證)。

注意

ARR 不支援執行時間重新編譯。 在播放模式為作用中時修改腳本並加以儲存,可能會導致 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 ,我們通常會向使用者呈現強制回應對話方塊,以便他們選擇 (而且我們在另一章中這麼做)。 目前,只要觸發 RequestingAuthorization 事件,我們就會呼叫 ByPassAuthentication 來自動略過授權檢查

  1. 選取 RemoteRenderingCoordinator GameObject,並尋找 RemoteRenderingCoordinator 元件偵測器中公開的 OnRequestingAuthorization Unity 事件。

  2. 按下右下角的 '+' 以新增事件。

  3. 將元件拖曳至自己的事件,以參考本身。 Screenshot of the Unity inspector of the Remote Rendering Coordinator Script. The title bar of the component is highlighted and an arrow connects it to the On Requesting Authorization event.

  4. 在下拉式清單中,選取 [RemoteRenderingCoordinator - > BypassAuthorization ]。
    Screenshot of the On Requesting Authorization event.

建立或加入遠端會話

第二個階段是建立或加入遠端轉譯會話(如需轉譯會話的詳細資訊,請參閱 遠端轉譯會話 )。

Diagram of the four stages required to load a model. The second stage

遠端會話是轉譯模型的位置。 JoinRemoteSession( ) 方法會嘗試聯結現有的會話、使用 LastUsedSessionID 屬性追蹤,或在 SessionIDOverride 上有指派的作用中 會話識別碼。 SessionIDOverride 僅供偵錯之用,只有在您知道會話存在且想要明確連線時,才應該使用它。

如果沒有可用的會話,則會建立新的會話。 不過,建立新的會話是耗時的作業。 因此,您應該只在需要時建立會話,並盡可能重複使用會話(請參閱 商業就緒:會話共用、排程和最佳做法 ,以取得管理會話的詳細資訊)。

提示

StopRemoteSession() 將會結束使用中的會話。 若要避免不必要的費用,您應該一律在不再需要會話時停止會話。

狀態機器現在會根據可用的會話,前進到 連線ingToNewRemoteSession 連線IngToExistingRemoteSession 。 開啟現有的會話或建立新的會話都會觸發 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();
    }
}

如果您想要重複使用會話來節省時間,請務必停用 ARRServiceUnity 元件中的 [自動停止會話 ] 選項 請記住,即使沒有人連線到會話,這也會讓會話保持執行狀態。 您的會話可以在伺服器關閉 MaxLeaseTime 之前執行一段時間 (MaxLeaseTime 的值 可以在 [新增會話預設值 ] 底下的 [遠端轉譯 協調器 ] 中修改。 另一方面,如果您在中斷連線時自動關閉每個會話,則必須等待每次啟動新的會話,這可以是冗長的程式。

注意

停止會話將立即生效,且無法復原。 停止之後,您必須建立具有相同啟動額外負荷的新會話。

將本機執行時間連線至遠端會話

接下來,應用程式必須將本機執行時間連線到遠端會話。

Diagram of the four stages required to load a model. The third stage

應用程式也需要接聽執行時間與目前會話之間連線的相關事件;這些狀態變更會在 OnLocalRuntimeStatusChanged 處理。 此程式碼會將我們的狀態前進到 連線ingToRuntime 。 在 OnLocalRuntimeStatusChanged 中 連線之後,狀態會前進到 Runtime連線ed 連線執行時間是協調器本身所關注的最後一種狀態,這表示應用程式會使用所有萬用群組態來完成,並準備好開始載入和轉譯模型的會話特定工作。

  1. 以下列已完成的版本取代 連線RuntimeToRemoteSession( ) DisconnectRuntimeFromRemoteSession( ) 方法。
  2. 請務必記下 Unity 方法 LateUpdate ,並更新目前的使用中會話。 這可讓目前的會話傳送/接收訊息,並使用從遠端會話接收的畫面格更新框架緩衝區。 ARR 正常運作非常重要。
/// <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;
}

注意

將本機執行時間連線遠端會話取決於 目前正在使用中的會話上呼叫更新 。 如果您發現應用程式從未進行過 連線ingToRuntime 狀態,請確定您在使用中的會話上定期呼叫 Update

載入模型

備妥必要的基礎之後,您就可以將模型載入遠端會話並開始接收畫面。

Diagram of the four stages required to load a model. The fourth stage

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. 建立本機 GameObject 來代表遠端實體。
  3. 將本機 GameObject 設定為將它的狀態(也就是轉換)同步至每個畫面的遠端實體。
  4. 將模型資料從 Blob 儲存體載入遠端實體。
  5. 傳回父實體,以供稍後參考。

檢視測試模型

我們現在擁有檢視遠端轉譯模型所需的所有程式碼,遠端轉譯的所有四個必要階段都已完成。 現在我們需要新增一些程式碼來啟動模型載入程式。

Diagram of the four stages required to load a model. All stages are marked as completed.

  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)}%"));
    }
    

    此程式碼會建立 GameObject,做為測試模型的父代。 然後,它會呼叫 LoadModel 方法來載入模型「builtin://Engine」,這是 Azure 遠端轉譯內建的資產,用於測試轉譯。

  2. 儲存您的程式碼。

  3. 按下 Unity 編輯器中的 [播放] 按鈕,開始連線到 Azure 遠端轉譯並建立新會話的程式。

  4. 不過,主控台不會在 [遊戲] 檢視中看到太多內容,會顯示應用程式變更的狀態。 它可能會進展到 ConnectingToNewRemoteSession ,並留在那裡,可能長達五分鐘。

  5. 選取 RemoteRenderingCoordinator GameObject,以查看其在偵測器中的附加腳本。 觀看服務 元件在進行初始化和連線步驟時進行更新。

  6. 監視主控台輸出 - 等候狀態變更為 Runtime連線ed

  7. 連接執行時間之後,以滑鼠右鍵按一下 偵測器中的 RemoteRenderingCoordinator 以公開操作功能表。 然後,選取 操作功能表中的 [負載測試模型] 選項,由 [ContextMenu("Load Test Model")] 上述程式碼的一部分新增。

    Screenshot of the Unity inspector of the Remote Rendering Coordinator Script. Highlights instruct to first right-click on the title bar and then select Load Test Model from the context menu.

  8. 觀看 Console 以取得我們傳入 LoadModel 方法的 ProgressHandler 輸出。

  9. 請參閱遠端轉譯的模型!

注意

遠端模型永遠不會顯示在場景檢視中,只會顯示在遊戲檢視中。 這是因為 ARR 會特別針對遊戲檢視相機的視角來轉譯畫面格,而且不知道編輯器相機(用來轉譯場景檢視)。

下一步

Screenshot of Unity running the project in Play mode. A car engine is rendered in the center of the viewport.

恭喜! 您已建立能夠使用 Azure 遠端轉譯檢視遠端轉譯模型的基本應用程式。 在下一個教學課程中,我們將整合 MRTK 並匯入自己的模型。