Samouczek: manipulowanie modelami

Ten samouczek zawiera informacje na temat wykonywania następujących czynności:

  • Dodawanie granic wizualizacji i manipulowania nimi wokół zdalnie renderowanych modeli
  • Przenoszenie, obracanie i skalowanie
  • Raycast z zapytaniami przestrzennymi
  • Dodawanie prostych animacji dla zdalnie renderowanych obiektów

Wymagania wstępne

Wykonywanie zapytań o granice obiektów zdalnych i stosowanie ich do granic lokalnych

Aby wchodzić w interakcje z obiektami zdalnymi, potrzebujemy lokalnej reprezentacji do pierwszej interakcji. Granice obiektów są przydatne do szybkiego manipulowania obiektem zdalnym. Zapytania dotyczące zdalnych granic można wykonywać z poziomu usługi ARR, używając lokalnej jednostki jako odwołania. Granice są odpytywane po załadowaniu modelu do sesji zdalnej.

Granice modelu są definiowane przez pole zawierające cały model — podobnie jak boxCollider aparatu Unity, który ma środek i rozmiar zdefiniowany dla osi x, y, z. W rzeczywistości użyjemy klasy BoxCollider aparatu Unity do reprezentowania granic modelu zdalnego.

  1. Utwórz nowy skrypt w tym samym katalogu co RemoteRenderedModel i nadaj mu nazwę RemoteBounds.

  2. Zastąp zawartość skryptu następującym kodem:

    // 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
        }
    }
    

    Uwaga

    Jeśli w programie Visual Studio zostanie wyświetlony błąd z oświadczeniem , że funkcja "X" nie jest dostępna w języku C# 6. Użyj języka w wersji 7.0 lub nowszej. Ten błąd można bezpiecznie zignorować. Jest to związane z generowaniem rozwiązań i projektów aparatu Unity.

    Ten skrypt należy dodać do tego samego obiektu GameObject co skrypt implementujący model BaseRemoteRenderedModel. W tym przypadku oznacza to model RemoteRenderedModel. Podobnie jak w poprzednich skryptach, ten początkowy kod obsługuje wszystkie zmiany stanu, zdarzenia i dane związane z granicami zdalnymi.

    Do zaimplementowania pozostało tylko jedna metoda: QueryBounds. Funkcja QueryBounds pobiera granice asynchronicznie, pobiera wynik zapytania i stosuje ją do lokalnego obiektu BoxCollider.

    Metoda QueryBounds jest prosta: wyślij zapytanie do sesji renderowania zdalnego i poczekaj na wynik.

  3. Zastąp metodę QueryBounds następującą ukończoną metodą:

    // 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;
        }
    }
    

    Sprawdzamy wynik zapytania, aby sprawdzić, czy to się powiodło. Jeśli tak, przekonwertuj i zastosuj zwrócone ograniczenia w formacie, który może zaakceptować pole BoxCollider .

Teraz, gdy skrypt RemoteBounds zostanie dodany do tego samego obiektu gry co RemoteRenderedModel, pole BoxCollider zostanie dodane w razie potrzeby, a gdy model osiągnie swój Loaded stan, granice zostaną automatycznie odpytywane i stosowane do kolumny BoxCollider.

  1. Korzystając z utworzonego wcześniej obiektu GameObject Modelu TestModel , dodaj składnik RemoteBounds .

  2. Upewnij się, że skrypt został dodany.

    Dodawanie składnika RemoteBounds

  3. Uruchom ponownie aplikację. Wkrótce po załadowaniu modelu zobaczysz ograniczenia dla obiektu zdalnego. Zobaczysz podobne do poniższych wartości:

    Zrzut ekranu przedstawiający przykład ograniczenia obiektu zdalnego.

Teraz mamy lokalną aplikację BoxCollider skonfigurowaną z dokładnymi granicami obiektu aparatu Unity. Granice umożliwiają wizualizację i interakcję przy użyciu tych samych strategii, których użyjemy dla obiektu renderowanego lokalnie. Na przykład skrypty, które zmieniają transformację, fizykę i nie tylko.

Przenoszenie, obracanie i skalowanie

Przenoszenie, obracanie i skalowanie zdalnie renderowanych obiektów działa tak samo jak każdy inny obiekt Aparatu Unity. RemoteRenderingCoordinator w swojej LateUpdate metodzie wywołuje Update obecnie aktywną sesję. Częścią tego, co Update robi, jest synchronizacja lokalnych przekształceń jednostki modelu ze swoimi zdalnymi odpowiednikami. Aby przenieść, obrócić lub skalować zdalnie renderowany model, wystarczy przenieść, obrócić lub skalować transformację obiektu GameObject reprezentującego model zdalny. W tym miejscu zmodyfikujemy przekształcenie nadrzędnego obiektu GameObject, który ma dołączony skrypt RemoteRenderedModel .

Ten samouczek używa zestawu narzędzi MRTK do interakcji z obiektami. Większość implementacji specyficznej dla zestawu narzędzi MRTK do przenoszenia, obracania i skalowania obiektu wykracza poza zakres tego samouczka. Istnieje kontroler widoku modelu, który jest wstępnie skonfigurowany wewnątrz narzędzia AppMenu w menu Narzędzia modelu .

  1. Upewnij się, że utworzony wcześniej obiekt GameObject modelu TestModel znajduje się w scenie.
  2. Upewnij się, że prefab appMenu znajduje się w scenie.
  3. Naciśnij przycisk Odtwarzania aparatu Unity, aby odtworzyć scenę i otworzyć menu Narzędzia modelu w aplikacji AppMenu. Wyświetlanie kontrolera

Narzędzie AppMenu ma narzędzia modelu podrzędnego, które implementuje kontroler widoku do powiązania z modelem. Gdy obiekt GameObject zawiera składnik RemoteBounds , kontroler widoku doda składnik BoundingBox , który jest składnikiem ZESTAWU NARZĘDZI MRTK, który renderuje pole ograniczenia wokół obiektu z polem BoxCollider. ObiektManipulator, który jest odpowiedzialny za interakcje ręczne. Te połączone skrypty umożliwią przenoszenie, obracanie i skalowanie zdalnie renderowanego modelu.

  1. Przenieś wskaźnik myszy na panel Game (Gra) i kliknij wewnątrz niego, aby skupić się na nim.

  2. Korzystając z symulacji ręcznej zestawu narzędzi MRTK, naciśnij i przytrzymaj lewy klawisz Shift.

  3. Kieruje symulowaną ręką, aby promienie ręki wskazywały model testowy.

    Promienie ręki spiczaste

  4. Przytrzymaj lewym przyciskiem myszy i przeciągnij model, aby go przenieść.

Powinna zostać wyświetlona zdalnie renderowana zawartość wraz z polem ograniczenia. Może wystąpić pewne opóźnienie lub opóźnienie między polem ograniczenia a zawartością zdalną. To opóźnienie będzie zależeć od opóźnienia internetowego i przepustowości.

Rzutowanie ray i zapytania przestrzenne modeli zdalnych

Zderzacz ramek wokół modeli jest odpowiedni do interakcji z całym modelem, ale nie jest wystarczająco szczegółowy, aby wchodzić w interakcje z poszczególnymi częściami modelu. Aby rozwiązać ten problem, możemy użyć zdalnego rzutowania promieni. Zdalne rzutowanie promieni jest interfejsem API udostępnianym przez usługę Azure Remote Rendering do rzutowania promieni do zdalnej sceny i zwracania wyników trafień lokalnie. Ta technika może służyć do wybierania jednostek podrzędnych dużego modelu lub uzyskiwania informacji o wynikach trafień, takich jak pozycja, normalna powierzchnia i odległość.

Model testowy ma wiele jednostek podrzędnych, które można wykonywać zapytania i wybierać. Na razie wybranie spowoduje wyprowadzenie nazwy wybranej jednostki do konsoli aparatu Unity. Sprawdź rozdział Materiały, oświetlenie i efekty , aby wyróżnić wybraną jednostkę.

Najpierw utwórzmy statyczną otokę wokół zdalnych zapytań rzutowania promieniami. Ten skrypt zaakceptuje położenie i kierunek w przestrzeni aparatu Unity, przekonwertuje go na typy danych akceptowane przez rzutowanie promieniami zdalnymi i zwróci wyniki. Skrypt użyje interfejsu RayCastQueryAsync API.

  1. Utwórz nowy skrypt o nazwie RemoteRayCaster i zastąp jego zawartość następującym kodem:

    // 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();
        }
    }
    

    Uwaga

    Aparat Unity ma klasę o nazwie RaycastHit, a usługa Azure Remote Rendering ma klasę o nazwie RayCastHit. Wielkie litery C to ważna różnica, aby uniknąć błędów kompilacji.

    RemoteRayCaster zapewnia wspólny punkt dostępu do rzutowania zdalnych promieni do bieżącej sesji. Aby uzyskać bardziej szczegółowe informacje, zaimplementujemy procedurę obsługi wskaźnika MRTK w następnej kolejności. Skrypt zaimplementuje IMixedRealityPointerHandler interfejs, który poinformuje mrTK, że ten skrypt ma nasłuchiwać zdarzeń wskaźnika Mixed Reality.

  2. Utwórz nowy skrypt o nazwie RemoteRayCastPointerHandler i zastąp kod następującym kodem:

    // 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);
        }
    }
    

Metoda RemoteRayCastPointerHandler jest wywoływana OnPointerClicked przez zestaw narzędzi MRTK, gdy wskaźnik "klika" na zderzaku, podobnie jak nasz zderzacz skrzynkowy. Następnie jest wywoływana, PointerDataToRemoteRayCast aby przekonwertować wynik wskaźnika na punkt i kierunek. Ten punkt i kierunek są następnie używane do rzutowania promienia zdalnego w sesji zdalnej.

Ograniczenia zaktualizowane

Wysyłanie żądań rzutowania promieni po kliknięciu jest efektywną strategią wykonywania zapytań dotyczących obiektów zdalnych. Nie jest to jednak idealne środowisko użytkownika, ponieważ kursor zderza się z zderzaczem skrzynkowym, a nie samym modelem.

Można również utworzyć nowy wskaźnik MRTK, który rzuca jego promienie w sesji zdalnej częściej. Chociaż jest to bardziej złożone podejście, środowisko użytkownika byłoby lepsze. Ta strategia wykracza poza zakres tego samouczka, ale przykład tego podejścia można zobaczyć w aplikacji showcase w repozytorium przykładów usługi ARR.

Po pomyślnym zakończeniu rzutowania promieniem w programie RemoteRayCastPointerHandler trafienie Entity jest emitowane ze OnRemoteEntityClicked zdarzenia aparatu Unity. Aby odpowiedzieć na to zdarzenie, utworzymy skrypt pomocnika, który akceptuje Entity i wykonuje na nim akcję. Zacznijmy od wyświetlenia nazwy Entity skryptu w dzienniku debugowania.

  1. Utwórz nowy skrypt o nazwie RemoteEntityHelper i zastąp jego zawartość poniższym kodem:

    // 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. Na utworzonym wcześniej obiekcie TestModel GameObject dodaj zarówno składnik RemoteRayCastPointerHandler , jak i składnik RemoteEntityHelper .

  3. Przypisz metodę EntityToDebugLogOnRemoteEntityClicked do zdarzenia. Gdy typ danych wyjściowych zdarzenia i typ danych wejściowych metody są zgodne, możemy użyć dynamicznego podpinania zdarzeń aparatu Unity, który automatycznie przekaże wartość zdarzenia do metody .

    1. Tworzenie nowego pola wywołania zwrotnego Dodaj wywołanie zwrotne
    2. Przeciągnij składnik Pomocnika jednostki zdalnej do pola Obiekt, aby odwołać się do nadrzędnego obiektu GameObject Assign
    3. Przypisz element EntityToDebugLog jako wywołanie zwrotne Przypisz wywołanie zwrotne
  4. Naciśnij przycisk odtwarzania w edytorze aparatu Unity, aby rozpocząć scenę, nawiązać połączenie z sesją zdalną i załadować model testowy.

  5. Za pomocą ręcznego naciśnięcia klawisza MRTK i przytrzymanie lewego klawisza Shift.

  6. Kieruje symulowaną ręką, aby promienie ręki wskazywały model testowy.

  7. Kliknij długo, aby zasymulować naciśnięcie powietrza, wykonując OnPointerClicked zdarzenie.

  8. Obserwuj konsolę aparatu Unity dla komunikatu dziennika z wybraną nazwą jednostki podrzędnej. Na przykład: Przykład jednostki podrzędnej

Synchronizowanie grafu obiektów zdalnych z hierarchią aparatu Unity

Do tego momentu widzieliśmy tylko jeden lokalny obiekt GameObject reprezentujący cały model. Działa to dobrze w przypadku renderowania i manipulowania całym modelem. Jeśli jednak chcemy zastosować efekty lub manipulować określonymi jednostkami podrzędnymi, musimy utworzyć lokalne obiekty GameObject, aby reprezentować te jednostki. Najpierw możemy zbadać ręcznie w modelu testowym.

  1. Uruchom scenę i załaduj model testowy.
  2. Rozwiń elementy podrzędne obiektu GameObject modelu TestModel w hierarchii aparatu Unity i wybierz TestModel_Entity GameObject.
  3. W inspektorze kliknij przycisk Pokaż elementy podrzędne . Pokaż elementy podrzędne
  4. Kontynuuj rozwijanie elementów podrzędnych w hierarchii i klikanie pozycji Pokaż elementy podrzędne do momentu wyświetlenia dużej listy elementów podrzędnych. Wszystkie elementy podrzędne

Lista kilkudziesięciu jednostek będzie teraz wypełniać hierarchię. Wybranie jednego z nich spowoduje wyświetlenie Transform składników i RemoteEntitySyncObject w inspektorze. Domyślnie każda jednostka nie jest automatycznie synchronizowana z każdą ramką, więc lokalne zmiany w Transform obiekcie nie są synchronizowane z serwerem. Możesz sprawdzić opcję Synchronizuj każdą ramkę, a następnie przenieść, skalować lub obracać przekształcenie w widoku sceny. Nie zobaczysz renderowanego modelu w widoku sceny, watch widoku Gra, aby zobaczyć wizualną aktualizację położenia i rotacji modelu.

Ten sam proces można wykonać programowo i jest pierwszym krokiem modyfikowania określonych jednostek zdalnych.

  1. Zmodyfikuj skrypt RemoteEntityHelper , aby zawierał również następującą metodę:

    public void MakeSyncedGameObject(Entity entity)
    {
        var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
        var sync = entityGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    }
    
  2. Dodaj dodatkowe wywołanie zwrotne do zdarzenia OnRemoteEntityClickedRemoteRayCastPointerHandler, ustawiając je na MakeSyncedGameObject. Dodatkowe wywołanie zwrotne

  3. Za pomocą ręcznego naciśnięcia klawisza MRTK i przytrzymanie lewego klawisza Shift.

  4. Kieruje symulowaną ręką, aby promienie ręki wskazywały model testowy.

  5. Kliknij długo, aby zasymulować naciśnięcie powietrza, wykonując OnPointerClicked zdarzenie.

  6. Sprawdź i rozwiń hierarchię, aby wyświetlić nowy obiekt podrzędny reprezentujący klikniętą jednostkę. Reprezentacja obiektu GameObject

  7. Po przetestowaniu usuń wywołanie zwrotne dla MakeSyncedGameObjectelementu , ponieważ uwzględnimy to jako część innych efektów później.

Uwaga

Synchronizacja każdej ramki jest wymagana tylko wtedy, gdy trzeba zsynchronizować dane przekształcenia. Istnieje pewne obciążenie związane z synchronizacją przekształceń, dlatego powinno być używane oszczędnie.

Tworzenie wystąpienia lokalnego i automatyczne synchronizowanie jest pierwszym krokiem w manipulowaniu jednostkami podrzędnym. Te same techniki, których użyliśmy do manipulowania modelem jako całością, mogą być również używane w jednostkach podrzędnych. Na przykład po utworzeniu zsynchronizowanego lokalnego wystąpienia jednostki można wykonać zapytanie dotyczące jego granic i dodać programy obsługi manipulowania, aby umożliwić jego przenoszenie przez promienie dłoni użytkownika.

Następne kroki

Teraz możesz manipulować modelami renderowanych zdalnie i wchodzić z nimi w interakcje. W następnym samouczku omówimy modyfikowanie materiałów, zmienianie oświetlenia i stosowanie efektów do zdalnie renderowanych modeli.