البرنامج التعليمي: عرض النماذج المقدمة عن بعد

في هذا البرنامج التعليمي، تتعلم كيفية:

  • توفير مثيل Azure Remote Rendering (ARR)
  • إنشاء جلسة عرض وإيقافها
  • إعادة استخدام جلسة عرض موجودة
  • الاتصال وقطع الاتصال من الجلسات
  • تحميل النماذج في جلسة العرض

المتطلبات الأساسية

ستحتاج في هذا البرنامج التعليمي ما يأتي:

  • اشتراك Azure نشط في الدفع عند الانتقال إنشاء حساب
  • Windows SDK 10.0.18362.0 (تنزيل)
  • أحدث إصدار من Visual Studio 2022 (تنزيل)
  • Git (تنزيل)
  • المكون الإضافي Git LFS (تنزيل)
  • Unity (انظر متطلبات النظام لمعرفة الإصدارات المدعومة)
  • المعرفة المتوسطة لـ Unity ولغة C# (على سبيل المثال: إنشاء البرامج النصية والكائنات، واستخدام المباني الجاهزة، وتكوين أحداث Unity، وما إلى ذلك)

توفير مثيل Azure Remote Rendering (ARR)

للتمتع بالوصول إلى خدمة Azure Remote Rendering، ستحتاج أولًا إلى إنشاء حساب.

إنشاء مشروع Unity جديد

تلميح

يحتوي مستودع عينات ARR على مشروع مع الانتهاء من جميع البرامج التعليمية، ويمكن استخدامه كمرجع. ابحث في Unity\Tutorial-Complete لمشروع Unity الكامل.

من Unity Hub، قم بإنشاء مشروع جديد. في هذا المثال، نفترض أنه يتم إنشاء المشروع في مجلد يسمى RemoteRendering.

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

تضمين حزم Azure Remote Rendering وOpenXR

اتبع الإرشادات حول كيفية إضافة حزم Azure Remote Rendering وOpenXR إلى مشروع Unity الخاص بك.

إشعار

إذا عرض Unity مربع حوار تحذير بعد استيراد حزمة OpenXR يسأل ما إذا كان سيتم تمكين الواجهات الخلفية للنظام الأساسي الأصلي لنظام الإدخال الجديد، فانقر فوق لا في الوقت الحالي. ستقوم بتمكينه في خطوة لاحقة.

تكوين الكاميرا

  1. حدد عقدة الكاميرا الرئيسية.

  2. افتح قائمة السياق بالنقر بزر الماوس الأيمن فوق مكون Transform وحدد الخيار Reset :

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

  3. تعيين مسح الإشارات إلى لون ثابت

  4. تعيين الخلفية إلى الأسود (#000000)، مع لون شفاف تماماً (0) ألفا (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. إذا كنت ترغب في استخدام Universal Render Pipeline، فشاهد Unity Render Pipelines للحصول على خطوات إعداد إضافية.

  3. حدد XR Plugin Management من القائمة اليسرى

    1. انقر فوق الزر Install XR Plugin Management.
    2. حدد علامة التبويب إعدادات النظام الأساسي Windows العالمي، ممثلة كرمز Windows.
    3. حدد خانة الاختيار Open XR ضمن Plug-In Providers
    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 Remote Rendering وOpenXR لتثبيتها.

  4. حدد OpenXR من القائمة اليسرى

    1. تعيين وضع إرسال العمق إلى العمق 16 بت
    2. أضف ملف تعريف التفاعل اليدوي من Microsoft إلى ملفات تعريف التفاعل.
    3. تمكين ميزات OpenXR هذه:
      • Azure Remote Rendering
      • Hand Tracking
      • ميزات الحقيقة المختلطة
      • Motion Controller Model

    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 Remote Rendering و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. حدد Universal Windows Platform
    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. اختر إدخال التحقق من ValidateProject من القائمة RemoteRendering في شريط أدوات محرر Unity.

  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 بإلغاء التغييرات اليدوية التي تقوم بتطبيقها على الكاميرا. يتضمن هذا إصلاحات من أداة التحقق من الصحة.

قم بإنشاء برنامج نصي لتنسيق اتصال وحالة Azure Remote Rendering

هناك أربع مراحل أساسية لإظهار النماذج المقدمة عن بعد، المبينة في المخطط الانسيابي أدناه. يجب أن يتم تنفيذ كل مرحلة بالترتيب. الخطوة التالية هي إنشاء برنامج نصي يدير حالة التطبيق ومتابعة كل مرحلة مطلوبة.

Diagram of the four stages required to load a model.

  1. في جزء Project، ضمن Assets، قم بإنشاء مجلد جديد يسمى RemoteRenderingCore. ثم داخل RemoteRenderingCore، قم بإنشاء مجلد آخر يسمى النصوص.

  2. إنشاء نص C# جديد يسمى RemoteRenderingCoordinator. يجب أن يبدو مشروعك هكذا:

    Screenshot of Unity Project hierarchy containing the new script.

    يتتبع هذا البرنامج النصي المنسق حالة العرض عن بعد ويديرها. ملاحظة، يتم استخدام بعض هذه التعليمات البرمجية للحفاظ على الحالة، وعرض الوظائف لمكونات أخرى، وتشغيل الأحداث، وتخزين البيانات الخاصة بالتطبيق التي لا ترتبط مباشرة ب Azure Remote Rendering. استخدم الكود أدناه كنقطة بداية، وسنقوم بمعالجة وتنفيذ رمز Azure Remote Rendering المحدد لاحقاً في البرنامج التعليمي.

  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 Remote Rendering GameObject

يعد منسق العرض عن بُعد والنص المطلوب (ARRServiceUnity) MonoBehaviours يجب إرفاقهما بكائن GameObject في المشهد. يتم توفير البرنامج النصي ARRServiceUnity بواسطة ARR لعرض الكثير من وظائف 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. إذا كنت تتساءل، فهذا نتيجة وجود [RequireComponent(typeof(ARRServiceUnity))] في الجزء العلوي من النص البرمجي RemoteRenderingCoordinator.
  4. أضف بيانات اعتماد Azure Remote Rendering ومجال الحساب الخاص بك ومجال العرض البعيد إلى البرنامج النصي المنسق:
    Screenshot of the Unity inspector of the Remote Rendering Coordinator Script. The credential input fields are highlighted.

تهيئة Azure Remote Rendering

الآن بعد أن أصبح لدينا إطار عمل لمنسقنا، سننفذ كل مرحلة من المراحل الأربع بدءا من تهيئة العرض عن بعد.

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

تخبر التهيئة Azure Remote Rendering عن كائن الكاميرا الذي يجب استخدامه للعرض وتقدم جهاز الحالة إلى NotAuthorized. تعني هذه الحالة أنها تمت تهيئتها ولكن لم يتم تخويلها بعد للاتصال بجلسة عمل. نظراً لأن بدء جلسة ARR ينطوي على تكلفة، فنحن بحاجة إلى تأكيد رغبة المستخدم في المتابعة.

عند إدخال حالة NotAuthorized، يتم استدعاء CheckAuthorization، والتي تستدعي حدث RequestingAuthorization وتحدد بيانات اعتماد الحساب التي يجب استخدامها (يتم تعريف AccountInfo بالقرب من أعلى الفصل ويستخدم بيانات الاعتماد التي حددتها عبر "Unity Inspector" في الخطوة أعلاه).

إشعار

إعادة التحويل البرمجي لوقت التشغيل غير معتمد من قبل ARR. قد يؤدي تعديل البرنامج النصي وحفظه أثناء تنشيط وضع التشغيل إلى تجميد الوحدة والحاجة إلى فرض إيقاف التشغيل من خلال مدير المهام. تأكد ‏‏دوماً من إيقاف وضع التشغيل قبل تحرير البرامج النصية.

  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 GameObject وابحث عن حدث Unity OnRequestingAuthorization المعروض في مفتش المكون RemoteRenderingCoordinator.

  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() سينهي الجلسة النشطة. لمنع فرض رسوم غير ضرورية، يجب عليك دائما إيقاف الجلسات عندما لا تكون هناك حاجة إليها.

سيتقدم جهاز الحالة الآن إما إلى 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();
    }
}

إذا كنت تريد توفير الوقت عن طريق إعادة استخدام الجلسات، فتأكد من إلغاء تنشيط خيار الإيقاف التلقائي للجلسة في مكون ARRServiceUnity. ضع في اعتبارك أن هذا سيترك جلسات العمل قيد التشغيل، حتى عندما لا يكون أحد متصلاً بها. قد تستمر جلستك لمدة MaxLeaseTime قبل أن يغلقها الخادم (يمكن تعديل قيمة MaxLeaseTime في Remote Rendering Coordinator، ضمن New Session Defaults). من ناحية أخرى، إذا قمت تلقائيا بإيقاف تشغيل كل جلسة عمل عند قطع الاتصال، يتعين عليك الانتظار حتى يتم بدء جلسة عمل جديدة في كل مرة، والتي يمكن أن تكون عملية طويلة.

إشعار

سيكون إيقاف الجلسة ساري المفعول على الفور ولا يمكن التراجع عنه. بمجرد التوقف، يجب عليك إنشاء جلسة جديدة، بنفس حمل بدء التشغيل.

توصيل وقت التشغيل المحلي بالجلسة عن بعد

بعد ذلك، يحتاج التطبيق إلى توصيل وقت التشغيل المحلي الخاص به بالجلسة عن بعد.

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

يحتاج التطبيق أيضاً إلى الاستماع إلى الأحداث المتعلقة بالاتصال بين وقت التشغيل والجلسة الحالية؛ يتم التعامل مع هذه التغييرات في الحالة في OnLocalRuntimeStatusChanged. تقدم هذه التعليمة البرمجية حالتنا إلى الاتصال ToRuntime. بمجرد الاتصال في OnLocalRuntimeStatusChanged، تتقدم الحالة إلى وقت التشغيل الاتصال ed. يعد الاتصال بوقت التشغيل هو الحالة الأخيرة التي يهتم بها المنسق، مما يعني أن التطبيق يتم باستخدام التكوين المشترك بالكامل وجاهز لبدء العمل الخاص بالجلسة الخاص بنماذج التحميل والعرض.

  1. استبدل طريقتي ConnectRuntimeToRemoteSession () و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;
}

إشعار

يعتمد توصيل وقت التشغيل المحلي بجلسة عن بعد على استدعاء Update في الجلسة النشطة حالياً. إذا وجدت أن التطبيق الخاص بك لا يتقدم أبداً بعد حالة ConnectingToRuntime، فتأكد من أنك تستدعي 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. إنشاء Remote Entity.
  2. إنشاء GameObject محلي لتمثيل الكيان عن بعد.
  3. قم بتكوين GameObject المحلي لمزامنة حالته (أي Transform) إلى الكيان البعيد كل إطار.
  4. تحميل بيانات الطراز من Blob Storage إلى الكيان عن بعد.
  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 Remote Rendering لغرض اختبار العرض.

  2. تحقق من الرمز.

  3. اضغط على زر التشغيل في "محرر الوحدة" لبدء عملية الاتصال بـ Azure Remote Rendering وإنشاء جلسة جديدة.

  4. لن ترى الكثير في طريقة عرض اللعبة، ومع ذلك، تعرض وحدة التحكم حالة تغيير التطبيق. ومن المرجح أن تتقدم إلى ConnectingToNewRemoteSession، وتظل هناك، وربما لمدة تصل إلى خمس دقائق.

  5. حدد GameObject RemoteRenderingCoordinator لرؤية البرامج النصية المرفقة به في المفتش. شاهد تحديث مكون الخدمة أثناء تقدمه خلال خطوات التهيئة والاتصال.

  6. راقب إخراج وحدة التحكم - في انتظار تغيير الحالة إلى RuntimeConnected.

  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. شاهد وحدة التحكم لإخراج ProgressHandler الذي مررناه في طريقة LoadModel.

  9. انظر نموذج قُدِم عن بعد!

إشعار

لن يكون النموذج البعيد مرئياً أبداً في عرض المشهد، فقط في عرض اللعبة. هذا لأن ARR يعرض الإطارات عن بُعد خصوصاً لمنظور كاميرا عرض اللعبة ولا يكون على دراية بكاميرا المحرر (تُستخدم لتقديم عرض المشهد).

الخطوات التالية

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

تهانينا! لقد أنشأت تطبيقاً أساسياً قادراً على عرض النماذج المقدمة عن بعد باستخدام ميزة "Azure Remote Rendering". في البرنامج التعليمي التالي، سندمج MRTK ونستورد نماذجنا الخاصة.