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
- Tento kurz vychází z kurzu: Rozhraní a vlastní modely.
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.
Vytvořte nový skript ve stejném adresáři jako RemoteRenderedModel a pojmenujte ho RemoteBounds.
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.
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.
Pomocí objektu TestModel GameObject vytvořeného dříve přidejte komponentu RemoteBounds .
Ověřte, že je skript přidaný.
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:
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 .
- Ujistěte se, že testovací objekt GameObject vytvořený dříve je ve scéně.
- Ujistěte se, že je ve scéně prefab AppMenu .
- 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.
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.
Přesuňte myš na herní panel a kliknutím na něj přesuňte fokus.
Pomocí simulace ruky MRTK stiskněte a podržte levou klávesu Shift.
Nasměrujte simulovanou ruku tak, aby paprsek ruky ukazoval na testovací model.
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.
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.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.
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í.
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); } }
Do objektu GameObject TestModel vytvořeného dříve přidejte komponentu RemoteRayCastPointerHandler i komponentu RemoteEntityHelper .
Přiřaďte
EntityToDebugLog
k události metoduOnRemoteEntityClicked
. 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ě.- Vytvoření nového pole zpětného volání
- Přetažením komponenty Remote Entity Helper do pole Object (Objekt) odkazujte na nadřazený objekt GameObject
EntityToDebugLog
Přiřadit jako zpětné volání
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.
Pomocí simulace rukou MRTK stiskněte a podržte levou klávesu Shift.
Nasměrujte simulovanou ruku tak, aby paprsek ruky ukazoval na testovací model.
Dlouhé kliknutí pro simulaci klepnutí vzduchem, spuštění
OnPointerClicked
události.Podívejte se na konzolu Unity, kde najdete zprávu protokolu s vybraným názvem podřízené entity. Příklad:
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ě.
- Spusťte scénu a načtěte testovací model.
- Rozbalte podřízené objekty TestModel GameObject v hierarchii Unity a vyberte TestModel_Entity GameObject.
- V inspektoru klikněte na tlačítko Zobrazit podřízené položky .
- 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.
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.
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; }
Přidejte další zpětné volání do události
OnRemoteEntityClicked
RemoteRayCastPointerHandler a nastavte ji naMakeSyncedGameObject
.Pomocí simulace rukou MRTK stiskněte a podržte levou klávesu Shift.
Nasměrujte simulovanou ruku tak, aby paprsek ruky ukazoval na testovací model.
Dlouhé kliknutí pro simulaci klepnutí vzduchem, spuštění
OnPointerClicked
události.Zaškrtněte a rozbalte hierarchii a zobrazte nový podřízený objekt představující entitu, na které jste klikli.
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.