Esercitazione: Manipolazione dei modelli

In questa esercitazione verranno illustrate le procedure per:

  • Aggiungere limiti visivi e di manipolazione intorno ai modelli sottoposti a rendering remoto
  • Spostare, ruotare e dimensionare
  • Raycast con query spaziali
  • Aggiungere animazioni semplici per oggetti sottoposti a rendering remoto

Prerequisiti

Eseguire query sui limiti degli oggetti remoti e applicare i risultati ai limiti locali

Per interagire con gli oggetti remoti, è prima necessaria una rappresentazione locale. I limiti degli oggetti sono utili per la manipolazione rapida di oggetti remoti. I limiti remoti possono essere sottoposti a query da Rendering remoto di Azure, usando l'entità locale come riferimento. I limiti vengono sottoposti a query dopo il caricamento del modello nella sessione remota.

I limiti di un modello sono definiti dal riquadro che lo contiene interamente, esattamente come BoxCollider di Unity, con un centro e dimensioni definiti per gli assi x, y, z. In realtà, si userà BoxCollider di Unity per rappresentare i limiti del modello remoto.

  1. Creare un nuovo script nella stessa directory di RemoteRenderedModel e assegnargli il nome RemoteBounds.

  2. Sostituire il contenuto dello script con il codice seguente:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.Azure.RemoteRendering;
    using Microsoft.Azure.RemoteRendering.Unity;
    using System;
    using UnityEngine;
    
    [RequireComponent(typeof(BaseRemoteRenderedModel))]
    public class RemoteBounds : BaseRemoteBounds
    {
        //Remote bounds works with a specific remotely rendered model
        private BaseRemoteRenderedModel targetModel = null;
    
        private RemoteBoundsState currentBoundsState = RemoteBoundsState.NotReady;
    
        public override RemoteBoundsState CurrentBoundsState
        {
            get => currentBoundsState;
            protected set
            {
                if (currentBoundsState != value)
                {
                    currentBoundsState = value;
                    BoundsStateChange?.Invoke(value);
                }
            }
        }
    
        public override event Action<RemoteBoundsState> BoundsStateChange;
    
        public void Awake()
        {
            BoundsStateChange += HandleUnityEvents;
            targetModel = GetComponent<BaseRemoteRenderedModel>();
    
            targetModel.ModelStateChange += TargetModel_OnModelStateChange;
            TargetModel_OnModelStateChange(targetModel.CurrentModelState);
        }
    
        private void TargetModel_OnModelStateChange(ModelState state)
        {
            switch (state)
            {
                case ModelState.Loaded:
                    QueryBounds();
                    break;
                default:
                    BoundsBoxCollider.enabled = false;
                    CurrentBoundsState = RemoteBoundsState.NotReady;
                    break;
            }
        }
    
        // Create an async query using the model entity
        async private void QueryBounds()
        {
            //Implement me
        }
    }
    

    Nota

    Se in Visual Studio viene visualizzato il messaggio di errore La funzionalità 'X' non è disponibile in C# 6. Usare la versione 7.0 o versioni successive del linguaggio, è possibile ignorarlo tranquillamente. Fa riferimento alla generazione di soluzioni e progetti di Unity.

    Questo script deve essere aggiunto allo stesso GameObject dello script che implementa BaseRemoteRenderedModel. In questo caso, significa RemoteRenderedModel. Analogamente agli script precedenti, questo codice iniziale gestisce tutte le modifiche, gli eventi e i dati relativi ai limiti remoti.

    È disponibile un solo metodo per implementare: QueryBounds. QueryBounds recupera i limiti in modo asincrono, accetta il risultato della query e lo applica a BoxCollider locale.

    Il metodo QueryBounds è semplice: inviare una query alla sessione di rendering remoto e attendere il risultato.

  3. Sostituire il metodo QueryBounds con il metodo completato seguente:

    // Create a query using the model entity
    async private void QueryBounds()
    {
        var remoteBounds = targetModel.ModelEntity.QueryLocalBoundsAsync();
        CurrentBoundsState = RemoteBoundsState.Updating;
        await remoteBounds;
    
        if (remoteBounds.IsCompleted)
        {
            var newBounds = remoteBounds.Result.toUnity();
            BoundsBoxCollider.center = newBounds.center;
            BoundsBoxCollider.size = newBounds.size;
            BoundsBoxCollider.enabled = true;
            CurrentBoundsState = RemoteBoundsState.Ready;
        }
        else
        {
            CurrentBoundsState = RemoteBoundsState.Error;
        }
    }
    

    Controllare il risultato della query per verificare se è stato eseguito correttamente. In caso affermativo, convertire e applicare i limiti restituiti in un formato accettabile da BoxCollider.

Ora, quando lo script RemoteBounds viene aggiunto allo stesso oggetto di gioco del RemoteRenderedModel, un BoxCollider viene aggiunto se necessario e quando il modello raggiunge Loaded lo stato, i limiti verranno automaticamente sottoposti a query e applicati al BoxCollider.

  1. Usando il GameObject di TestModel creato in precedenza, aggiungere il componente RemoteBounds.

  2. Verificare che lo script venga aggiunto.

    Aggiungere il componente RemoteBounds

  3. Eseguire di nuovo l'applicazione. Subito dopo il caricamento del modello, verranno visualizzati i limiti per l'oggetto remoto. Sotto i valori verrà visualizzato qualcosa di simile:

    Screenshot che mostra l'esempio per i limiti dell'oggetto remoto.

A questo punto è disponibile un oggetto BoxCollider locale configurato con limiti accurati nell'oggetto Unity. I limiti consentono la visualizzazione e l'interazione usando le stesse strategie applicate per un oggetto sottoposto a rendering in locale, ad esempio script che modificano la trasformazione, la fisica e altro ancora.

Spostare, ruotare e dimensionare

Lo spostamento, la rotazione e il dimensionamento di oggetti sottoposti a rendering remoto funzionano allo stesso modo di qualsiasi altro oggetto Unity. RemoteRenderingCoordinator, nel relativo metodo LateUpdate, chiama Update nella sessione attiva. Update, tra l'altro, provvede a sincronizzare le trasformazioni delle entità dei modelli locali con le relative controparti remote. Per spostare, ruotare o dimensionare un modello sottoposto a rendering remoto, è necessario solo spostare, ruotare o dimensionare la trasformazione del GameObject che rappresenta il modello remoto. In questo caso, verrà modificata la trasformazione del GameObject padre a cui è associato lo script RemoteRenderedModel.

Questa esercitazione usa MRTK per l'interazione degli oggetti. La maggior parte dell'implementazione specifica di MRTK per lo spostamento, la rotazione e il dimensionamento di un oggetto esula dall'ambito di questa esercitazione. È disponibile un controller di visualizzazione dei modelli preconfigurato all'interno di AppMenu, nel menu Model Tools (Strumenti modello).

  1. Assicurarsi che il GameObject TestModel creato in precedenza sia nella scena.
  2. Assicurarsi che il prefab AppMenu sia nella scena.
  3. Premere il pulsante Play di Unity per riprodurre la scena e aprire il menu Model Tools (Strumenti modello) all'interno di AppMenu. Visualizzare il controller

AppMenu include il sottomenu Model Tools (Strumenti modello) che implementa un controller di visualizzazione per il binding con il modello. Quando il GameObject contiene un componente RemoteBounds, il controller di visualizzazione aggiungerà un componente BoundingBox, ovvero un componente MRTK che esegue il rendering di un rettangolo di delimitazione intorno a un oggetto con BoxCollider. ObjectManipulator, responsabile delle interazioni con la mano. Questi script combinati consentiranno di spostare, ruotare e dimensionare il modello sottoposto a rendering remoto.

  1. Spostare il mouse sul pannello del gioco e fare clic al suo interno per attivarlo.

  2. Usando la simulazione della mano di MRTK, tenere premuto MAIUSC sinistro.

  3. Spostare la mano simulata in modo che il raggio punti al modello di test.

    Raggio della mano puntato

  4. Tenere premuto il pulsante sinistro del mouse e trascinare il modello per spostarlo.

Il contenuto sottoposto a rendering remoto si sposterà insieme al rettangolo delimitatore. Si potrebbe osservare un ritardo tra il rettangolo delimitatore e il contenuto remoto. Questo ritardo dipenderà dalla latenza e dalla larghezza di banda di Internet.

Raycast e query spaziali dei modelli remoti

Un oggetto BoxCollider intorno ai modelli è indicato per l'interazione con l'intero modello, ma non è sufficientemente dettagliato per l'interazione con le singole parti. Per risolvere questo problema, è possibile usare la funzionalità di raycasting remoto. Si tratta di un'API fornita da Rendering remoto di Azure per eseguire il raycasting nella scena remota e restituire i risultati della collisione in locale. Questa tecnica può essere usata per selezionare le entità figlio di un modello di grandi dimensioni o per ottenere informazioni sui risultati delle collisioni, ad esempio posizione, superficie normale e distanza.

Il modello di test include diverse sottoentità che è possibile sottoporre a query e selezionare. Per il momento, la selezione genererà il nome dell'entità selezionata nella console Unity. Per informazioni su come evidenziare l'entità selezionata, vedere la sezione Materiali, illuminazione ed effetti.

Creare prima di tutto un wrapper statico intorno alle query di raycast remoto. Questo script accetterà una posizione e una direzione nello spazio Unity, le convertirà nei tipi di dati accettati dal raycast raggio remoto e restituirà i risultati. Lo script userà l'API RayCastQueryAsync.

  1. Creare un nuovo script denominato RemoteRaycaster e sostituirne l'intero contenuto con il codice seguente:

    // 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.Linq;
    using System.Threading.Tasks;
    using UnityEngine;
    
    /// <summary>
    /// Wraps the Azure Remote Rendering RayCast queries to easily send requests using Unity data types
    /// </summary>
    public class RemoteRayCaster
    {
        public static double maxDistance = 30.0;
    
        public static async Task<RayCastHit[]> RemoteRayCast(Vector3 origin, Vector3 dir, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            if(RemoteRenderingCoordinator.instance.CurrentCoordinatorState == RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected)
            {
                var rayCast = new RayCast(origin.toRemotePos(), dir.toRemoteDir(), maxDistance, hitPolicy);
                var result = await RemoteRenderingCoordinator.CurrentSession.Connection.RayCastQueryAsync(rayCast);
                return result.Hits;
            }
            else
            {
                return new RayCastHit[0];
            }
        }
    
        public static async Task<Entity[]> RemoteRayCastEntities(Vector3 origin, Vector3 dir, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            var hits = await RemoteRayCast(origin, dir, hitPolicy);
            return hits.Select(hit => hit.HitEntity).Where(entity => entity != null).ToArray();
        }
    }
    

    Nota

    Unity include una classe denominata RaycastHit e Rendering remoto di Azure una classe denominata RayCastHit. La C maiuscola è un'importante differenza per evitare errori di compilazione.

    RemoteRayCaster fornisce un punto di accesso comune per il raycasting remoto nella sessione corrente. Nello specifico, verrà implementato un Per essere più specifici, verrà implementato un gestore del puntatore MRTK. Lo script implementerà l'interfaccia IMixedRealityPointerHandler, che indica a MRTK che si vuole che lo script resti in ascolto di eventi del puntatore di Realtà mista.

  2. Creare un nuovo script denominato RemoteRayCastPointerHandler e sostituire il codice con il codice seguente:

    // 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.MixedReality.Toolkit.Input;
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using UnityEngine;
    
    public class RemoteRayCastPointerHandler : BaseRemoteRayCastPointerHandler, IMixedRealityPointerHandler
    {
        public UnityRemoteEntityEvent OnRemoteEntityClicked = new UnityRemoteEntityEvent();
    
        public override event Action<Entity> RemoteEntityClicked;
    
        public void Awake()
        {
            // Forward events to Unity events
            RemoteEntityClicked += (entity) => OnRemoteEntityClicked?.Invoke(entity);
        }
    
        public async void OnPointerClicked(MixedRealityPointerEventData eventData)
        {
            if (RemoteEntityClicked != null) //Ensure someone is listening before we do the work
            {
                var firstHit = await PointerDataToRemoteRayCast(eventData.Pointer);
                if (firstHit.success)
                    RemoteEntityClicked.Invoke(firstHit.hit.HitEntity);
            }
        }
    
        public void OnPointerDown(MixedRealityPointerEventData eventData) { }
    
        public void OnPointerDragged(MixedRealityPointerEventData eventData) { }
    
        public void OnPointerUp(MixedRealityPointerEventData eventData) { }
    
        private async Task<(bool success, RayCastHit hit)> PointerDataToRemoteRayCast(IMixedRealityPointer pointer, HitCollectionPolicy hitPolicy = HitCollectionPolicy.ClosestHit)
        {
            RayCastHit hit;
            var result = pointer.Result;
            if (result != null)
            {
                var endPoint = result.Details.Point;
                var direction = pointer.Rays[pointer.Result.RayStepIndex].Direction;
                Debug.DrawRay(endPoint, direction, Color.green, 0);
                hit = (await RemoteRayCaster.RemoteRayCast(endPoint, direction, hitPolicy)).FirstOrDefault();
            }
            else
            {
                hit = new RayCastHit();
            }
            return (hit.HitEntity != null, hit);
        }
    }
    

Il metodo di OnPointerClicked di RemoteRayCastPointerHandler viene chiamato da MRTK quando un puntatore fa clic su un collider come BoxCollider. Successivamente, viene chiamato PointerDataToRemoteRayCast per convertire il risultato del puntatore in un punto e in una direzione. Il punto e la direzione vengono quindi usati per eseguire il raycasting remoto nella sessione remota.

Limiti aggiornati

L'invio di richieste di raycasting al clic è una strategia efficace per l'esecuzione di query sugli oggetti remoti. Tuttavia, non si tratta di un'esperienza utente ideale perché il cursore entra in collisione con BoxCollider, non con il modello stesso.

È anche possibile creare un nuovo puntatore MRTK che esegue il raycasting nella sessione remota con maggiore frequenza. Anche se è un approccio più complesso, l'esperienza utente sarà più soddisfacente. Questa strategia esula dall'ambito di questa esercitazione, ma è possibile vedere un esempio di questo approccio nell'app di presentazione disponibile nel repository di esempi di Rendering remoto di Azure.

Al termine di un raycast in RemoteRayCastPointerHandler, il risultato Entity viene emesso dall'evento OnRemoteEntityClicked di Unity. Per rispondere a tale evento, viene creato uno script helper che accetta Entity ed esegue un'azione. Per iniziare, ottenere lo script per stampare il nome di Entity nel log di debug.

  1. Creare un nuovo script denominato RemoteEntityHelper e sostituirne il contenuto con il seguente:

    // 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 UnityEngine;
    
    public class RemoteEntityHelper : MonoBehaviour
    {
        public void EntityToDebugLog(Entity entity)
        {
            Debug.Log(entity.Name);
        }
    }
    
  2. Nel GameObject TestModel creato in precedenza, aggiungere il componente RemoteRayCastPointerHandler e il componente RemoteEntityHelper.

  3. Assegnare il metodo EntityToDebugLog all'evento OnRemoteEntityClicked. Quando il tipo di output dell'evento e il tipo di input del metodo corrispondono, è possibile usare il collegamento di eventi dinamici di Unity, che passerà automaticamente il valore dell'evento al metodo.

    1. Creare un nuovo campo di callback Aggiungi callback
    2. Trascinare il componente Helper entità remota nel campo Oggetto per fare riferimento all'oggetto GameObject Assign padre
    3. Assegnare come EntityToDebugLogcallback Assegnare il callback
  4. Premere Play nell'editor di Unity per avviare la scena, connettersi a una sessione remota e caricare il modello di test.

  5. Usando la simulazione della mano di MRTK, tenere premuto MAIUSC sinistro.

  6. Spostare la mano simulata in modo che il raggio punti al modello di test.

  7. Fare un clic lungo per simulare il tocco, eseguendo l'evento OnPointerClicked.

  8. Cercare nella console di Unity un messaggio di log con il nome dell'entità figlio selezionato. Ad esempio: Esempio di entità figlio

Sincronizzazione del grafico di oggetti remoto nella gerarchia di Unity

Fino a questo punto, abbiamo visto solo un singolo GameObject locale che rappresenta l'intero modello. Questa opzione funziona bene per il rendering e la manipolazione dell'intero modello. Se però si vogliono applicare gli effetti o manipolare specifiche sottoentità, è necessario creare GameObject locali per rappresentarle. Prima di tutto, è possibile esplorare manualmente il modello di testo.

  1. Avviare la scena e caricare il modello di test.
  2. Espandere gli elementi figlio del GameObject TestModel nella gerarchia di Unity e selezionare l'elemento GameObject TestModel_Entity.
  3. Nella finestra di controllo fare clic sul pulsante Show Children (Mostra elementi figlio). Mostra elementi figlio
  4. Continuare a espandere gli elementi figlio nella gerarchia e fare clic su Show Children (Mostra elementi figlio) finché non viene visualizzato un elenco di elementi figlio. Tutti gli elementi figlio

Un elenco di decine di entità ora popola la gerarchia. Selezionandone una, nella finestra di controllo verranno visualizzati i componenti Transform e RemoteEntitySyncObject. Per impostazione predefinita, ogni entità non viene sincronizzata automaticamente con ogni fotogramma, quindi le modifiche locali apportate a Transform non vengono sincronizzate con il server. È possibile selezionare Sync Every Frame (Sincronizza ogni fotogramma) e quindi spostare, dimensionare o ruotare la trasformazione nella visualizzazione scena, Il modello sottoposto a rendering non verrà visualizzato nella visualizzazione scena. Per visualizzare l'aggiornamento visivo della posizione e della rotazione del modello, guardare la visualizzazione gioco.

Lo stesso processo può essere eseguito a livello di codice ed è il primo passaggio per la modifica di entità remote specifiche.

  1. Modificare lo script RemoteEntityHelper in modo che contenga anche il metodo seguente:

    public void MakeSyncedGameObject(Entity entity)
    {
        var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
        var sync = entityGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    }
    
  2. Aggiungere un callback aggiuntivo all'evento RemoteRayCastPointerHandlerOnRemoteEntityClicked, impostandolo su MakeSyncedGameObject. Callback aggiuntivo

  3. Usando la simulazione della mano di MRTK, tenere premuto MAIUSC sinistro.

  4. Spostare la mano simulata in modo che il raggio punti al modello di test.

  5. Fare un clic lungo per simulare il tocco, eseguendo l'evento OnPointerClicked.

  6. Controllare ed espandere la gerarchia per visualizzare un nuovo oggetto figlio, che rappresenta l'entità selezionata. Rappresentazione di GameObject

  7. Dopo il test, rimuovere il callback per MakeSyncedGameObject, dal momento che verrà incorporato con altri effetti in seguito.

Nota

La sincronizzazione di ogni fotogramma è richiesta solo quando è necessario sincronizzare i dati di trasformazione. La sincronizzazione delle trasformazioni comporta un sovraccarico, quindi è consigliabile eseguirla raramente.

La creazione di un'istanza locale e la relativa sincronizzazione automatica rappresentano il primo passaggio per la manipolazione di sottoentità. Le stesse tecniche usate per manipolare il modello interno possono essere usate anche per le sottoentità. Ad esempio, dopo aver creato un'istanza locale sincronizzata di un'entità, è possibile eseguire una query sui limiti e aggiungere i gestori di manipolazione per consentirne con i raggi della mano dell'utente.

Passaggi successivi

È ora possibile manipolare e interagire con i modelli sottoposti a rendering remoto. Nell'esercitazione successiva verranno descritte le procedure per modificare i materiali, alterare l'illuminazione e applicare effetti ai modelli sottoposti a rendering remoto.