Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I den här guiden får du lära dig att:
- Lägga till gränser för visuella objekt och manipulering runt fjärrrederade modeller
- Flytta, rotera och skala
- Raycast med rumsliga frågor
- Lägga till enkla animeringar för fjärrrederade objekt
Förutsättningar
- Den här självstudien bygger på Självstudier: Gränssnitt och anpassade modeller.
Fråga fjärrobjektsgräns och tillämpa på lokala gränser
För att interagera med fjärrobjekt behöver vi en lokal representation att interagera med först. Objektens gränser är användbara för snabb manipulering av ett fjärrobjekt. Fjärrgränsarna kan frågas från ARR med hjälp av den lokala entiteten som referens. Gränserna efterfrågas när modellen har lästs in i fjärrsessionen.
Gränserna för en modell definieras av rutan som innehåller hela modellen – precis som Unitys BoxCollider, som har en mittpunkt och storlek definierad för x-, y-, z-axlarna. I själva verket använder vi Unitys BoxCollider för att representera gränserna för fjärrmodellen.
Skapa ett nytt skript i samma katalog som RemoteRenderedModel och ge det namnet RemoteBounds.
Ersätt innehållet i skriptet med följande kod:
// 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 } }
Anteckning
Om du ser ett fel i Visual Studio som hävdar att funktionen "X" inte är tillgänglig i C# 6. Använd språkversion 7.0 eller senare. Det här felet kan ignoreras på ett säkert sätt. Detta är relaterat till Unitys lösning och projektgenerering.
Det här skriptet bör läggas till i samma GameObject som skriptet som implementerar BaseRemoteRenderedModel. I det här fallet betyder det RemoteRenderedModel. Precis som tidigare skript hanterar den här initiala koden alla tillståndsändringar, händelser och data som är relaterade till fjärranslutna gränser.
Det finns bara en metod kvar att implementera: QueryBounds. QueryBounds hämtar gränserna asynkront, tar resultatet av frågan och tillämpar den på den lokala BoxCollider.
Metoden QueryBounds är enkel: skicka en fråga till fjärråtergivningssessionen och vänta på resultatet.
Ersätt metoden QueryBounds med följande slutförda 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; } }
Vi kontrollerar frågeresultatet för att se om det lyckades. Om ja konverterar och tillämpar du de returnerade gränserna i ett format som BoxCollider kan acceptera.
Nu när RemoteBounds-skriptet läggs till i samma spelobjekt som RemoteRenderedModel läggs en BoxCollider till om det behövs och när modellen når sitt Loaded
tillstånd kommer gränserna automatiskt att efterfrågas och tillämpas på BoxCollider.
Använd TestModel GameObject som skapades tidigare och lägg till komponenten RemoteBounds .
Bekräfta att skriptet har lagts till.
Kör programmet igen. Kort efter att modellen har lästs in ser du gränserna för fjärrobjektet. Du ser något som liknar värdena nedan:
Nu har vi en lokal BoxCollider konfigurerad med korrekta gränser för Unity-objektet. Gränserna möjliggör visualisering och interaktion med samma strategier som vi skulle använda för ett lokalt renderat objekt. Till exempel skript som ändrar transformering, fysik med mera.
Flytta, rotera och skala
Att flytta, rotera och skala fjärrrederade objekt fungerar på samma sätt som andra Unity-objekt.
RemoteRenderingCoordinator anropar Update
i sin LateUpdate
metod den aktiva sessionen. En del av det som Update
gör det är att synkronisera lokala modellentitetstransformeringar med sina fjärrmotsvarigheter. Om du vill flytta, rotera eller skala en fjärransluten modell behöver du bara flytta, rotera eller skala omvandlingen av GameObject som representerar fjärrmodellen. Här ska vi ändra transformering av den överordnade GameObject som har RemoteRenderedModel-skriptet kopplat till sig.
Den här självstudien använder MRTK för objektinteraktion. Merparten av den MRTK-specifika implementeringen för att flytta, rotera och skala ett objekt ligger utanför omfånget för den här självstudien. Det finns en modellvykontrollant som är förkonfigurerad i AppMenu på menyn Modellverktyg .
- Kontrollera att TestModel GameObject som skapades tidigare finns i scenen.
- Kontrollera att AppMenu-prefab är i scenen.
- Tryck på Knappen Spela upp i Unity för att spela upp scenen och öppna menyn Modellverktyg i AppMenu.
AppMenu har en undermeny modellverktyg som implementerar en vykontrollant för bindning med modellen. När GameObject innehåller en RemoteBounds-komponent lägger vykontrollanten till en BoundingBox-komponent , som är en MRTK-komponent som renderar en avgränsningsruta runt ett objekt med en BoxCollider. En ObjectManipulator som ansvarar för handinteraktioner. De här skripten tillsammans gör att vi kan flytta, rotera och skala den fjärranslutna modellen.
Flytta musen till spelpanelen och klicka inuti den för att ge den fokus.
Med hjälp av MRTK:s handsimulering trycker du på och håller ned den vänstra Skift-tangenten.
Styr den simulerade handen så att handstrålen pekar på testmodellen.
Håll vänsterklicka och dra modellen för att flytta den.
Du bör se att det fjärranslutna innehållet flyttas tillsammans med avgränsningsrutan. Du kanske märker en viss fördröjning eller fördröjning mellan avgränsningsrutan och fjärrinnehållet. Den här fördröjningen beror på din internetfördröjning och bandbredd.
Ray cast och rumsliga frågor om fjärrmodeller
En ruta som kolliderar runt modeller är lämplig för att interagera med hela modellen, men inte tillräckligt detaljerad för att interagera med enskilda delar av en modell. För att lösa detta kan vi använda fjärrstrålningsgjutning. Fjärrstrålningsgjutning är ett API som tillhandahålls av Azure Remote Rendering för att kasta strålar till fjärrscenen och returnera träffresultat lokalt. Den här tekniken kan användas för att välja underordnade entiteter av en stor modell eller få träffresultatinformation som position, ytnormal och avstånd.
Testmodellen har ett antal underentiteter som kan efterfrågas och väljas. För tillfället matar markeringen ut namnet på den valda entiteten till Unity-konsolen. Kontrollera kapitlet Material, belysning och effekter för att markera den valda entiteten.
Först ska vi skapa en statisk omslutning runt de fjärrstyrda ray cast-frågorna. Det här skriptet accepterar en position och riktning i Unity-utrymmet, konverterar det till de datatyper som accepteras av fjärrstrålen och returnerar resultaten. Skriptet använder API:et RayCastQueryAsync
.
Skapa ett nytt skript med namnet RemoteRayCaster och ersätt innehållet med följande kod:
// 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(); } }
Anteckning
Unity har en klass med namnet RaycastHit och Azure Remote Rendering har en klass med namnet RayCastHit. Versaler C är en viktig skillnad för att undvika kompileringsfel.
RemoteRayCaster tillhandahåller en gemensam åtkomstpunkt för gjutning av fjärrstrålar till den aktuella sessionen. För att vara mer specifik implementerar vi en MRTK-pekarhanterare härnäst. Skriptet implementerar
IMixedRealityPointerHandler
gränssnittet, vilket talar om för MRTK att vi vill att det här skriptet ska lyssna efter Mixed Reality Pointer-händelser.Skapa ett nytt skript med namnet RemoteRayCastPointerHandler och ersätt koden med följande kod:
// 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); } }
RemoteRayCastPointerHandlers -metod anropas OnPointerClicked
av MRTK när en pekare "klickar" på en kolliderare, som vår box collider.
PointerDataToRemoteRayCast
Därefter anropas för att konvertera pekarens resultat till en punkt och riktning. Den punkten och riktningen används sedan för att kasta en fjärrstråle i fjärrsessionen.
Att skicka begäranden om ray casting vid klickning är en effektiv strategi för att fråga fjärrobjekt. Det är dock inte en idealisk användarupplevelse eftersom markören kolliderar med boxens kolliderare, inte själva modellen.
Du kan också skapa en ny MRTK-pekare som kastar sina strålar i fjärrsessionen oftare. Även om detta är en mer komplex metod skulle användarupplevelsen vara bättre. Den här strategin ligger utanför omfånget för den här självstudien, men ett exempel på den här metoden kan ses i Showcase-appen som finns i ARR-exempellagringsplatsen.
När en ray cast har slutförts i RemoteRayCastPointerHandler genereras träffen Entity
OnRemoteEntityClicked
från Unity-händelsen. För att svara på händelsen skapar vi ett hjälpskript som accepterar Entity
och utför en åtgärd på den. Vi börjar med att hämta skriptet för att skriva ut namnet Entity
på till felsökningsloggen.
Skapa ett nytt skript med namnet RemoteEntityHelper och ersätt innehållet med följande:
// 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); } }
På TestModel GameObject som skapades tidigare lägger du till både RemoteRayCastPointerHandler-komponenten och RemoteEntityHelper-komponenten .
EntityToDebugLog
Tilldela -metoden tillOnRemoteEntityClicked
händelsen. När händelsens utdatatyp och metods indatatyp matchar kan vi använda Unitys dynamiska händelsekoppling, som automatiskt skickar händelsevärdet till metoden .- Skapa ett nytt motringningsfält
- Dra hjälpkomponenten för fjärrentitet till fältet Objekt om du vill referera till det överordnade GameObject
-
EntityToDebugLog
Tilldela som motringning
- Skapa ett nytt motringningsfält
Tryck på Spela upp i Unity-redigeraren för att starta scenen, ansluta till en fjärrsession och läsa in testmodellen.
Använd MRTK:s handsimulering och håll ned den vänstra Skift-tangenten.
Styr den simulerade handen så att handstrålen pekar på testmodellen.
Långt klick för att simulera en lufttryckning och
OnPointerClicked
köra händelsen.Observera Unity-konsolen för ett loggmeddelande med namnet på den underordnade entiteten valt. Exempel:
Synkronisera fjärrobjektdiagrammet till Unity-hierarkin
Fram tills nu har vi bara sett ett enda lokalt GameObject som representerar hela modellen. Detta fungerar bra för återgivning och manipulering av hela modellen. Men om vi vill tillämpa effekter eller manipulera specifika underentiteter måste vi skapa lokala GameObjects för att representera dessa entiteter. Först kan vi utforska manuellt i testmodellen.
- Starta scenen och läs in testmodellen.
- Expandera underordnade till TestModel GameObject i Unity-hierarkin och välj TestModel_Entity GameObject.
- I Inspector klickar du på knappen Visa underordnade .
- Fortsätt att expandera underordnade objekt i hierarkin och klicka på Visa underordnade tills en stor lista över underordnade visas.
Nu fylls hierarkin i en lista med dussintals entiteter. Om du väljer en av dem visas komponenterna Transform
och RemoteEntitySyncObject
i Inspector. Som standard synkroniseras inte varje entitet automatiskt varje bildruta, så lokala ändringar i Transform
synkroniseras inte till servern. Du kan markera Synkronisera varje bildruta och sedan flytta, skala eller rotera transformeringen i scenvyn. Du ser inte den renderade modellen i scenvyn, watch spelvyn för att se modellens position och rotation uppdateras visuellt.
Samma process kan göras programmatiskt och är det första steget i att ändra specifika fjärrentiteter.
Ändra RemoteEntityHelper-skriptet så att det även innehåller följande metod:
public void MakeSyncedGameObject(Entity entity) { var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents); var sync = entityGameObject.GetComponent<RemoteEntitySyncObject>(); sync.SyncEveryFrame = true; }
Lägg till ytterligare ett återanrop till händelsen
OnRemoteEntityClicked
RemoteRayCastPointerHandler och ställ in den påMakeSyncedGameObject
.Använd MRTK:s handsimulering och håll ned den vänstra Skift-tangenten.
Styr den simulerade handen så att handstrålen pekar på testmodellen.
Långt klick för att simulera en lufttryckning och
OnPointerClicked
köra händelsen.Markera och expandera hierarkin för att se ett nytt underordnat objekt som representerar den klickade entiteten.
Efter testningen tar du bort återanropet för
MakeSyncedGameObject
, eftersom vi kommer att inkludera detta som en del av andra effekter senare.
Anteckning
Du behöver bara synkronisera varje bildruta när du behöver synkronisera transformeringsdata. Det finns vissa kostnader för att synkronisera transformeringar, så det bör användas sparsamt.
Att skapa en lokal instans och göra den automatiskt synkroniserad är det första steget i att manipulera underentiteter. Samma tekniker som vi har använt för att manipulera modellen som helhet kan även användas på underentiteterna. När du till exempel har skapat en synkroniserad lokal instans av en entitet kan du fråga dess gränser och lägga till manipulationshanterare så att den kan flyttas runt av användarens handstrålar.
Nästa steg
Nu kan du manipulera och interagera med dina fjärrmanipulerade modeller! I nästa självstudie går vi igenom hur du ändrar material, ändrar belysningen och tillämpar effekter på fjärrstyrda modeller.