教學課程:檢視遠端轉譯的模型
在本教學課程中,您會了解如何:
- 布建 Azure 遠端轉譯 (ARR) 實例
- 建立和停止轉譯會話
- 重複使用現有的轉譯會話
- 連線與會話中斷連線
- 將模型載入轉譯會話
必要條件
在本教學課程中,您需要:
- 作用中的隨用隨付 Azure 訂 用帳戶建立帳戶
- Windows SDK 10.0.18362.0 (下載)
- 最新版的 Visual Studio 2022 (下載)
- Git (下載)
- Git LFS 外掛程式 (下載)
- Unity (請參閱 支援版本的系統需求 )
- Unity 和 C# 語言的中繼知識(例如:使用預製專案建立腳本和物件、設定 Unity 事件等)
布建 Azure 遠端轉譯 (ARR) 實例
若要存取 Azure 遠端轉譯服務,您必須先 建立帳戶 。
建立新的 Unity 專案
提示
ARR 範例存放庫 包含已完成所有教學課程的專案,可作為參考。 查看 Unity\Tutorial-Complete 以取得完整的 Unity 專案。
從 Unity 中樞建立新的專案。 在此範例中,我們假設專案正在名為 RemoteRendering 的資料夾中建立。
包含 Azure 遠端轉譯 和 OpenXR 套件
請遵循如何將 Azure 遠端轉譯 和 OpenXR 套件 新增至 Unity 專案的指示。
注意
如果 Unity 在匯入 OpenXR 套件之後顯示警告對話方塊,詢問是否要為新的輸入系統啟用原生平臺後端,請按一下 [立即否 ]。 您會在稍後的步驟中啟用它。
設定相機
選取 [ 主要相機 ] 節點。
以滑鼠右鍵按一下 [轉換 ] 元件,然後選取 [ 重設 ] 選項,以開啟操作功能表:
將 [清除旗標 ] 設定 為 [純色]
將 Background 設定 為 Black (#0000000),且完全透明 (0) Alpha (A)
將裁剪平面設定 為 Near = 0.1 和 Far = 20 。 此設定表示轉譯裁剪幾何,其距離超過 10 公分或遠超過 20 公尺。
調整專案設定
開啟 [ 編輯 > 專案] 設定...
從左側清單功能表選取 [品質 ]
將所有 平臺的預設品質等級 變更為 [低 ]。 此設定可讓您更有效率地轉譯本機內容,且不會影響遠端轉譯內容的品質。
注意
在本教學課程的範圍內,我們會堅持 Unity 內建轉譯管線。 如果您想要使用通用轉譯管線,請參閱 Unity 轉譯管線 以取得其他設定步驟。
從左側清單功能表選取 [XR 外掛程式管理 ]
- 按一下 [ 安裝 XR 外掛程式管理 ] 按鈕。
- 選取 [ 通用 Windows 平臺設定 ] 索引標籤,以 Windows 圖示表示。
- 選取 [外掛程式提供者] 底下的 [ 開啟 XR] 核取方塊
- 如果開啟對話方塊,要求您為新的輸入系統啟用原生平臺後端,請選取 [否 ]。
注意
如果 Microsoft HoloLens 功能群組 已停用,您的專案遺漏 Windows Mixed Reality OpenXR 外掛程式。 請遵循如何 新增 Azure 遠端轉譯 和 OpenXR 套件 以安裝它的指示。
從左側清單功能表選取 [OpenXR ]
- 將深度提交模式 設定 為 深度 16 位
- 將 Microsoft Hand Interaction Profile 新增至 互動設定檔 。
- 啟用下列 OpenXR 功能:
- Azure 遠端轉譯
- 手部追蹤
- 混合實境功能
- 動作控制器模型
注意
如果您沒有看到所需的 OpenXR 功能,則專案遺漏 Windows Mixed Reality OpenXR 外掛程式。 請遵循如何 新增 Azure 遠端轉譯 和 OpenXR 套件 以安裝它的指示。
從左側清單功能表選取 [播放機 ]
- 選取 [ 通用 Windows 平臺設定 ] 索引標籤,以 Windows 圖示表示。
- 展開 [其他設定
- 在 [轉譯 色彩空間] 底下 ,將 [色彩空間 ] 變更 為 [線性 ],並在要求您時重新開機 Unity。
- 在 [組態] 下 ,將 [作用中輸入處理 ] 變更 為 [兩者] ,並在要求您時重新開機 Unity。
- 展開 [發佈設定
- 向下捲動至 [ 功能 ],然後選取:
- InternetClient
- InternetClientServer
- SpatialPerception
- PrivateNetworkClientServer ( 選擇性 )。 如果您想要將 Unity 遠端偵錯程式連線到您的裝置,請選取此選項。
- 在 [支援的裝置系列 ] 下 ,啟用 全像攝影 和 桌面
關閉或停駐 [專案設定 ] 面板
開啟 檔案 > 建置設定
- 選取 通用 Windows 平臺
- 設定您的設定以符合以下找到的設定
- 按下 [ 切換平臺] 按鈕。
在 Unity 變更平臺之後,關閉組建面板。
驗證專案設定
執行下列步驟來驗證專案設定是否正確。
從 Unity 編輯器工具列中的 RemoteRendering 功能表選擇 ValidateProject 專案。
檢閱 [ 專案驗證程式 ] 視窗是否有錯誤,並視需要修正專案設定。
注意
如果您在專案中使用 MRTK 並啟用相機子系統,MRTK 將會覆寫套用至相機的手動變更。 這包括 ValidateProject 工具的修正程式。
建立腳本以協調 Azure 遠端轉譯連線和狀態
有四個基本階段可顯示遠端轉譯的模型,如下流程圖中所述。 每個階段都必須依序執行。 下一個步驟是建立腳本來管理應用程式狀態,並繼續進行每個必要階段。
在 [專案 ] 窗格的 [ 資產 ] 底下 ,建立名為 RemoteRenderingCore 的新資料夾。 然後在 RemoteRenderingCore 內 ,建立另一個名為 Scripts 的資料夾 。
建立 名為 RemoteRenderingCoordinator 的新 C# 腳本 。 您的專案看起來應該像這樣:
此協調器腳本會追蹤和管理遠端轉譯狀態。 請注意,此程式碼中的部分用於維護狀態、向其他元件公開功能、觸發事件,以及儲存與 Azure 遠端轉譯不 直接 相關的應用程式特定資料。 使用下列程式碼作為起點,我們將在稍後的教學課程中處理並實作特定的 Azure 遠端轉譯程式碼。
在程式碼編輯器中開啟 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 的功能,以便連線和管理遠端會話。
- 在場景中建立新的 GameObject(Ctrl+Shift+N 或 GameObject-Create > Empty ),並將它命名為 RemoteRenderingCoordinator 。
- 將 RemoteRenderingCoordinator 腳本新增至 RemoteRenderingCoordinator GameObject。
- 確認 ARRServiceUnity 腳本在偵測器中顯示為 服務 ,會自動新增至 GameObject。 如果您想知道,這是 RemoteRenderingCoordinator 腳本頂端 的結果
[RequireComponent(typeof(ARRServiceUnity))]
。 - 將 Azure 遠端轉譯認證、您的帳戶網域和遠端轉譯網域新增至協調器腳本:
初始化 Azure 遠端轉譯
既然我們有協調器架構,接下來我們將從 Initialize 遠端轉譯 開始 ,實作四個階段中的每一個階段。
Initialize 會告訴 Azure 遠端轉譯要用於轉譯和將狀態機器進度為 NotAuthorized 的相機物件。 此狀態表示其已初始化,但尚未獲得連線到會話的授權。 由於啟動 ARR 會話會產生成本,因此我們需要確認使用者想要繼續。
輸入 NotAuthorized 狀態時, 會呼叫 CheckAuthorization,這會叫 用 RequestingAuthorization 事件並判斷要使用的帳號憑證( AccountInfo 定義在類別頂端附近,並使用您在上述步驟中透過 Unity Inspector 定義的認證)。
注意
ARR 不支援執行時間重新編譯。 在播放模式為作用中時修改腳本並加以儲存,可能會導致 Unity 凍結,且需要強制透過工作管理員關閉。 請務必確定您已在編輯腳本之前停止播放模式。
以下列完成的程式碼取代 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 來自動略過授權檢查 。
選取 RemoteRenderingCoordinator GameObject,並尋找 RemoteRenderingCoordinator 元件偵測器中公開的 OnRequestingAuthorization Unity 事件。
按下右下角的 '+' 以新增事件。
將元件拖曳至自己的事件,以參考本身。
在下拉式清單中,選取 [RemoteRenderingCoordinator - > BypassAuthorization ]。
建立或加入遠端會話
第二個階段是建立或加入遠端轉譯會話(如需轉譯會話的詳細資訊,請參閱 遠端轉譯會話 )。
遠端會話是轉譯模型的位置。 JoinRemoteSession( ) 方法會嘗試聯結現有的會話、使用 LastUsedSessionID 屬性追蹤,或在 SessionIDOverride 上有指派的作用中 會話識別碼。 SessionIDOverride 僅供偵錯之用,只有在您知道會話存在且想要明確連線時,才應該使用它。
如果沒有可用的會話,則會建立新的會話。 不過,建立新的會話是耗時的作業。 因此,您應該只在需要時建立會話,並盡可能重複使用會話(請參閱 商業就緒:會話共用、排程和最佳做法 ,以取得管理會話的詳細資訊)。
提示
StopRemoteSession() 將會結束使用中的會話。 若要避免不必要的費用,您應該一律在不再需要會話時停止會話。
狀態機器現在會根據可用的會話,前進到 連線ingToNewRemoteSession 或 連線IngToExistingRemoteSession 。 開啟現有的會話或建立新的會話都會觸發 ARRSessionService.OnSessionStatusChanged 事件,並執行我們的 OnRemoteSessionStatusChanged 方法。 在理想情況下,這會導致將狀態機器提升至 RemoteSessionReady 。
- 若要加入新的會話,請修改程式碼,以將 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 的值 可以在 [新增會話預設值 ] 底下的 [遠端轉譯 協調器 ] 中修改。 另一方面,如果您在中斷連線時自動關閉每個會話,則必須等待每次啟動新的會話,這可以是冗長的程式。
注意
停止會話將立即生效,且無法復原。 停止之後,您必須建立具有相同啟動額外負荷的新會話。
將本機執行時間連線至遠端會話
接下來,應用程式必須將本機執行時間連線到遠端會話。
應用程式也需要接聽執行時間與目前會話之間連線的相關事件;這些狀態變更會在 OnLocalRuntimeStatusChanged 中 處理。 此程式碼會將我們的狀態前進到 連線ingToRuntime 。 在 OnLocalRuntimeStatusChanged 中 連線之後,狀態會前進到 Runtime連線ed 。 連線執行時間是協調器本身所關注的最後一種狀態,這表示應用程式會使用所有萬用群組態來完成,並準備好開始載入和轉譯模型的會話特定工作。
- 以下列已完成的版本取代 連線RuntimeToRemoteSession( ) 和 DisconnectRuntimeFromRemoteSession( ) 方法。
- 請務必記下 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 。
載入模型
備妥必要的基礎之後,您就可以將模型載入遠端會話並開始接收畫面。
LoadModel 方法的設計目的是要接受模型路徑、進度處理常式和父轉換。 這些引數可用來將模型載入遠端會話、在載入進度上更新使用者,並根據父轉換來設定遠端轉譯的模型。
將 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; }
上述程式碼會執行下列步驟:
- 建立 遠端實體 。
- 建立本機 GameObject 來代表遠端實體。
- 將本機 GameObject 設定為將它的狀態(也就是轉換)同步至每個畫面的遠端實體。
- 將模型資料從 Blob 儲存體載入遠端實體。
- 傳回父實體,以供稍後參考。
檢視測試模型
我們現在擁有檢視遠端轉譯模型所需的所有程式碼,遠端轉譯的所有四個必要階段都已完成。 現在我們需要新增一些程式碼來啟動模型載入程式。
將下列程式碼新增至 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 遠端轉譯內建的資產,用於測試轉譯。
儲存您的程式碼。
按下 Unity 編輯器中的 [播放] 按鈕,開始連線到 Azure 遠端轉譯並建立新會話的程式。
不過,主控台不會在 [遊戲] 檢視中看到太多內容,會顯示應用程式變更的狀態。 它可能會進展到
ConnectingToNewRemoteSession
,並留在那裡,可能長達五分鐘。選取 RemoteRenderingCoordinator GameObject,以查看其在偵測器中的附加腳本。 觀看服務 元件在進行初始化和連線步驟時進行更新。
監視主控台輸出 - 等候狀態變更為 Runtime連線ed 。
連接執行時間之後,以滑鼠右鍵按一下 偵測器中的 RemoteRenderingCoordinator 以公開操作功能表。 然後,選取 操作功能表中的 [負載測試模型] 選項,由
[ContextMenu("Load Test Model")]
上述程式碼的一部分新增。觀看 Console 以取得我們傳入 LoadModel 方法的 ProgressHandler 輸出。
請參閱遠端轉譯的模型!
注意
遠端模型永遠不會顯示在場景檢視中,只會顯示在遊戲檢視中。 這是因為 ARR 會特別針對遊戲檢視相機的視角來轉譯畫面格,而且不知道編輯器相機(用來轉譯場景檢視)。
下一步
恭喜! 您已建立能夠使用 Azure 遠端轉譯檢視遠端轉譯模型的基本應用程式。 在下一個教學課程中,我們將整合 MRTK 並匯入自己的模型。