Kurz: Manipulace s modely

V tomto kurzu se naučíte:

  • Přidání vizuálních hranic a hranic manipulace kolem vzdáleně vykreslených modelů
  • Přesunutí, otočení a škálování
  • Raycast s prostorovými dotazy
  • Přidání jednoduchých animací pro vzdáleně vykreslené objekty

Požadavky

Dotazování na hranice vzdálených objektů a použití na místní hranice

K interakci se vzdálenými objekty potřebujeme nejprve místní reprezentaci. Hranice objektů jsou užitečné pro rychlou manipulaci se vzdáleným objektem. Vzdálené hranice se dají dotazovat z ARR pomocí místní entity jako odkazu. Hranice se dotazují po načtení modelu do vzdálené relace.

Hranice modelu jsou definovány polem, které obsahuje celý model – stejně jako BoxCollider Unity, který má střed a velikost definované pro osy x, y, z. Ve skutečnosti použijeme BoxCollider Unity k reprezentaci hranic vzdáleného modelu.

  1. Vytvořte nový skript ve stejném adresáři jako RemoteRenderedModel a pojmenujte ho RemoteBounds.

  2. Obsah skriptu nahraďte následujícím kódem:

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

    Poznámka

    Pokud se v sadě Visual Studio zobrazí chyba s tvrzením, že funkce X není v jazyce C# 6 dostupná. Použijte jazyk verze 7.0 nebo vyšší, tyto chyby můžete bezpečně ignorovat. To souvisí s generováním řešení a projektu Unity.

    Tento skript by měl být přidán do stejného objektu GameObject jako skript, který implementuje BaseRemoteRenderedModel. V tomto případě to znamená RemoteRenderedModel. Podobně jako u předchozích skriptů zpracovává tento počáteční kód všechny změny stavu, události a data související se vzdálenými hranicemi.

    K implementaci zbývá pouze jedna metoda: QueryBounds. QueryBounds načte hranice asynchronně, vezme výsledek dotazu a použije ho na místní BoxCollider.

    Metoda QueryBounds je jednoduchá: odešle dotaz do vzdálené relace vykreslování a čeká na výsledek.

  3. Nahraďte metodu QueryBounds následující dokončenou metodou:

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

    Zkontrolujeme výsledek dotazu, abychom zjistili, jestli byl úspěšný. Pokud ano, převeďte a použijte vrácené hranice ve formátu, který BoxCollider může přijmout.

Nyní, když RemoteBounds skript je přidán do stejného herního objektu jako RemoteRenderedModel, BoxCollider je přidána v případě potřeby a když model dosáhne svého Loaded stavu, budou hranice automaticky dotazovány a aplikovány na BoxCollider.

  1. Pomocí objektu TestModel GameObject vytvořeného dříve přidejte komponentu RemoteBounds .

  2. Ověřte, že je skript přidaný.

    Přidání komponenty RemoteBounds

  3. Spusťte aplikaci znovu. Krátce po načtení modelu uvidíte hranice vzdáleného objektu. Zobrazí se něco jako následující hodnoty:

    Snímek obrazovky znázorňující příklad vzdálených hranic objektu

Teď máme nakonfigurovaný místní BoxCollider s přesnými hranicemi objektu Unity. Hranice umožňují vizualizaci a interakci pomocí stejných strategií, které bychom použili pro místně vykreslený objekt. Například skripty, které mění transformaci, fyziku a další.

Přesunutí, otočení a škálování

Přesouvání, otáčení a změna velikosti vzdáleně vykreslených objektů funguje stejně jako jakýkoli jiný objekt Unity. RemoteRenderingCoordinator, ve své LateUpdate metodě, volá Update v aktuálně aktivní relaci. Update Součástí je synchronizace transformací entit místního modelu s jejich vzdálenými protějšky. Pokud chcete přesunout, otočit nebo škálovat vzdáleně vykreslený model, stačí přesunout, otočit nebo škálovat transformaci Objektu GameObject představující vzdálený model. Tady upravíme transformaci nadřazeného objektu GameObject, ke kterému je připojený skript RemoteRenderedModel .

V tomto kurzu se používá MRTK pro interakci s objekty. Většina konkrétních implementací MRTK pro přesun, otáčení a škálování objektu je mimo rozsah tohoto kurzu. K dispozici je kontroler zobrazení modelu, který je předem nakonfigurovaný uvnitř AppMenu v nabídce Modelové nástroje .

  1. Ujistěte se, že testovací objekt GameObject vytvořený dříve je ve scéně.
  2. Ujistěte se, že je ve scéně prefab AppMenu .
  3. Stisknutím tlačítka Přehrát v Unity přehrajte scénu a otevřete nabídku Nástroje modelu v nabídce AppMenu. Kontroler zobrazení

AppMenu má podnabídku Nástroje modelu, která implementuje kontroler zobrazení pro vazbu s modelem. Když GameObject obsahuje remoteBounds komponentu, přidá kontroler zobrazení komponentu BoundingBox , což je komponenta MRTK, která vykreslí ohraničující rámeček kolem objektu s BoxCollider. ObjectManipulator, který je zodpovědný za interakce rukou. Kombinace těchto skriptů nám umožní přesunout, otočit a škálovat vzdáleně vykreslený model.

  1. Přesuňte myš na herní panel a kliknutím na něj přesuňte fokus.

  2. Pomocí simulace ruky MRTK stiskněte a podržte levou klávesu Shift.

  3. Nasměrujte simulovanou ruku tak, aby paprsek ruky ukazoval na testovací model.

    Špičatý paprsek ruky

  4. Podržte levým kliknutím a přetažením model přesuňte.

Vzdáleně vykreslený obsah by se měl přesouvat spolu s ohraničovacím rámečkem. Mezi ohraničovacím rámečkem a vzdáleným obsahem si můžete všimnout určité prodlevy. Toto zpoždění bude záviset na latenci a šířce pásma internetu.

Ray cast a prostorové dotazy vzdálených modelů

Box collider kolem modelů je vhodný pro interakci s celým modelem, ale není dostatečně podrobný pro interakci s jednotlivými částmi modelu. K vyřešení tohoto problému můžeme použít vzdálené přetypování paprsků. Vzdálené přetypování paprsků je rozhraní API, které poskytuje Azure Remote Rendering k přetypování paprsků do vzdálené scény a k místnímu vrácení výsledků hledání. Tuto techniku můžete použít k výběru podřízených entit velkého modelu nebo k získání informací o výsledku hledání, jako je poloha, normální povrch a vzdálenost.

Testovací model má řadu dílčích entit, které je možné dotazovat a vybrat. Prozatím výběr vypíše název vybrané entity do konzoly Unity. V kapitole Materiály, osvětlení a efekty zkontrolujte zvýraznění vybrané entity.

Nejprve vytvoříme statickou obálku kolem dotazů vzdáleného přetypování paprsku. Tento skript přijme pozici a směr v prostoru Unity, převede ho na datové typy přijímané vzdáleným přetypováním paprsku a vrátí výsledky. Skript použije RayCastQueryAsync rozhraní API.

  1. Vytvořte nový skript s názvem RemoteRayCaster a nahraďte jeho obsah následujícím kódem:

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

    Poznámka

    Unity má třídu s názvem RaycastHit a Azure Remote Rendering má třídu s názvem RayCastHit. Velká písmena C jsou důležitým rozdílem, aby se zabránilo chybám kompilace.

    RemoteRayCaster poskytuje společný přístupový bod pro vysílání vzdálených paprsků do aktuální relace. Abychom byli konkrétnější, budeme dále implementovat obslužnou rutinu ukazatele MRTK. Skript implementuje IMixedRealityPointerHandler rozhraní, které mrtk oznámí, že chceme, aby tento skript naslouchal událostem Mixed Reality ukazatele.

  2. Vytvořte nový skript s názvem RemoteRayCastPointerHandler a nahraďte kód následujícím kódem:

    // 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 RemoteRayCastPointerHandlerOnPointerClicked je volána MRTK, když ukazatel "klikne" na s collider, jako je náš box s collider. PointerDataToRemoteRayCast Potom je volána k převodu výsledku ukazatele na bod a směr. Tento bod a směr se pak použijí k přetypování vzdáleného paprsku ve vzdálené relaci.

Aktualizovaly se hranice

Odesílání požadavků na přetypování paprsků při kliknutí je efektivní strategií pro dotazování vzdálených objektů. Nejedná se ale o ideální uživatelské prostředí, protože kurzor koliduje s uchytáčem rámečku, ne se samotným modelem.

Můžete také vytvořit nový ukazatel MRTK, který bude vysílat jeho paprsky ve vzdálené relaci častěji. I když se jedná o složitější přístup, uživatelské prostředí by bylo lepší. Tato strategie je nad rámec tohoto kurzu, ale příklad tohoto přístupu můžete vidět v showcase aplikaci, která se nachází v úložišti ukázek ARR.

Po úspěšném dokončení přetypování paprsku v rutině RemoteRayCastPointerHandler je přístup Entity vygenerován z OnRemoteEntityClicked události Unity. Abychom na tuto událost reagovali, vytvoříme pomocný skript, který přijme Entity a provede akci. Začněme tím, že získáme skript, který vypíše název objektu Entity do protokolu ladění.

  1. Vytvořte nový skript s názvem RemoteEntityHelper a nahraďte jeho obsah následujícím kódem:

    // 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. Do objektu GameObject TestModel vytvořeného dříve přidejte komponentu RemoteRayCastPointerHandler i komponentu RemoteEntityHelper .

  3. Přiřaďte EntityToDebugLog k události metodu OnRemoteEntityClicked . Když se výstupní typ události a vstupní typ metody shodují, můžeme použít dynamické připojení událostí Unity, které automaticky předá hodnotu události metodě.

    1. Vytvoření nového pole zpětného volání Přidat zpětné volání
    2. Přetažením komponenty Remote Entity Helper do pole Object (Objekt) odkazujte na nadřazený objekt GameObject Assign.
    3. EntityToDebugLog Přiřadit jako zpětné volání Přiřadit zpětné volání
  4. Stisknutím tlačítka Přehrát v Unity Editoru spustíte scénu, připojíte se ke vzdálené relaci a načtete testovací model.

  5. Pomocí simulace rukou MRTK stiskněte a podržte levou klávesu Shift.

  6. Nasměrujte simulovanou ruku tak, aby paprsek ruky ukazoval na testovací model.

  7. Dlouhé kliknutí pro simulaci klepnutí vzduchem, spuštění OnPointerClicked události.

  8. Podívejte se na konzolu Unity, kde najdete zprávu protokolu s vybraným názvem podřízené entity. Příklad: Příklad podřízené entity

Synchronizace grafu vzdálených objektů do hierarchie Unity

Do této chvíle jsme viděli pouze jeden místní Objekt GameObject představující celý model. To funguje dobře při vykreslování a manipulaci s celým modelem. Pokud ale chceme použít efekty nebo manipulovat s konkrétními dílčími entitami, budeme muset vytvořit místní objekty GameObject, které budou tyto entity reprezentovat. Nejprve můžeme v testovacím modelu prozkoumat ručně.

  1. Spusťte scénu a načtěte testovací model.
  2. Rozbalte podřízené objekty TestModel GameObject v hierarchii Unity a vyberte TestModel_Entity GameObject.
  3. V inspektoru klikněte na tlačítko Zobrazit podřízené položky . Zobrazit podřízené položky
  4. Pokračujte v rozbalování podřízených položek v hierarchii a klikejte na Zobrazit podřízené položky , dokud se nezobrazí velký seznam podřízených položek. Všechny podřízené položky

Hierarchii teď naplní seznam desítek entit. Když vyberete jednu z nich, zobrazí se v inspektoru Transform komponenty a RemoteEntitySyncObject . Ve výchozím nastavení se každá entita automaticky nesynchronizuje každý snímek, takže se místní změny v objektu Transform nesynchronizují se serverem. Můžete zaškrtnout políčko Synchronizovat každý snímek a pak přesunout, změnit měřítko nebo otočit transformaci v zobrazení scény. Vykreslený model se v zobrazení scény nezobrazí, watch zobrazení Hry, aby se vizuálně aktualizovala pozice a otočení modelu.

Stejný proces lze provést programově a je prvním krokem při úpravě konkrétních vzdálených entit.

  1. Upravte skript RemoteEntityHelper tak, aby obsahoval také následující metodu:

    public void MakeSyncedGameObject(Entity entity)
    {
        var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
        var sync = entityGameObject.GetComponent<RemoteEntitySyncObject>();
        sync.SyncEveryFrame = true;
    }
    
  2. Přidejte další zpětné volání do události OnRemoteEntityClickedRemoteRayCastPointerHandler a nastavte ji na MakeSyncedGameObject. Další zpětné volání

  3. Pomocí simulace rukou MRTK stiskněte a podržte levou klávesu Shift.

  4. Nasměrujte simulovanou ruku tak, aby paprsek ruky ukazoval na testovací model.

  5. Dlouhé kliknutí pro simulaci klepnutí vzduchem, spuštění OnPointerClicked události.

  6. Zaškrtněte a rozbalte hierarchii a zobrazte nový podřízený objekt představující entitu, na které jste klikli. Znázornění objektu GameObject

  7. Po otestování odeberte zpětné volání pro MakeSyncedGameObject, protože ho později začleníme jako součást dalších efektů.

Poznámka

Synchronizace každého snímku se vyžaduje jenom v případě, že potřebujete synchronizovat transformační data. Synchronizace transformací má určitou režii, proto by se měla používat střídmě.

Vytvoření místní instance a automatické synchronizace je prvním krokem při manipulaci s dílčími entitami. Stejné techniky, které jsme použili k manipulaci s modelem jako celkem, se dají použít i u dílčích entit. Například po vytvoření synchronizované místní instance entity byste se mohli dotazovat na její hranice a přidat obslužné rutiny pro manipulaci, aby bylo možné ji přesouvat pomocí paprsků rukou uživatele.

Další kroky

Teď můžete manipulovat s vzdáleně vykreslenými modely a pracovat s nimi. V dalším kurzu se budeme zabývat úpravami materiálů, změnou osvětlení a použitím efektů na vzdáleně vykreslené modely.