Självstudie: Manipulera modeller

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ärrre renderade modeller
  • Flytta, rotera och skala
  • Raycast med rumsliga frågor
  • Lägga till enkla animeringar för fjärrre renderade objekt

Förutsättningar

Köra frågor mot fjärrobjektsbunden 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ärrbundna gränser kan efterfrå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 som definierats för x-, y- och z-axlarna. I själva verket använder vi Unitys BoxCollider för att representera gränserna för fjärrmodellen.

  1. Skapa ett nytt skript i samma katalog som RemoteRenderedModel och ge det namnet RemoteBounds.

  2. 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 att funktionen "X" inte är tillgänglig i C# 6. Använd språkversion 7.0 eller senare. Dessa fel kan ignoreras på ett säkert sätt. Detta är relaterat till Unitys lösning och projektgenerering.

    Det här skriptet ska läggas till i samma GameObject som skriptet som implementerar BaseRemoteRenderedModel. I det här fallet innebär det RemoteRenderedModel. På samma sätt som med tidigare skript hanterar den här inledande koden alla tillståndsändringar, händelser och data som är relaterade till fjärrbundna 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 det på den lokala BoxCollider.

    Metoden QueryBounds är enkel: skicka en fråga till fjärrrenderingssessionen och vänta på resultatet.

  3. Ersätt queryBounds-metoden 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, konvertera och tillämpa 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 tillstånd, kommer gränserna automatiskt att efterfrågas och tillämpas på Loaded BoxCollider.

  1. Med hjälp av TestModel GameObject som skapades tidigare lägger du till komponenten RemoteBounds.

  2. Bekräfta att skriptet har lagts till.

    Lägg till RemoteBounds-komponent

  3. Kör programmet igen. Strax efter att modellen har laddats visas gränserna för fjärrobjektet. Du ser något som liknar nedanstående värden:

    Skärmbild som visar exemplet med fjärrobjektsbundna objekt.

Nu har vi en lokal BoxCollider som konfigurerats med korrekta gränser för Unity-objektet. Gränserna tillåter visualisering och interaktion med samma strategier som vi använder för ett lokalt renderat objekt. Till exempel skript som ändrar transformeringen, fysiken med mera.

Flytta, rotera och skala

Att flytta, rotera och skala fjärrre renderade objekt fungerar på samma sätt som andra Unity-objekt. RemoteRenderingCoordinator anropar i sin LateUpdate -metod Update för den aktiva sessionen. En del av det Update som gör är att synkronisera lokala modellentitetstransformeringar med deras fjärrmotsvarsvarare. Om du vill flytta, rotera eller skala en fjärrre renderad modell behöver du bara flytta, rotera eller skala transformeringen av GameObject som representerar fjärrmodellen. Här ska vi ändra transformeringen av det ö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 modellvisningskontrollant som är förkonfigurerad i AppMenumenyn Modellverktyg.

  1. Se till att TestModel GameObject som skapades tidigare finns i scenen.
  2. Se till att Prefab för AppMenu finns i scenen.
  3. Tryck på Unitys uppspelningsknapp för att spela upp scenen och öppna menyn Modellverktyg i AppMenu. Visa kontrollant

AppMenu har en undermeny Modellverktyg som implementerar en visningskontrollant för bindning med modellen. När GameObject innehåller en RemoteBounds-komponent lägger visningskontrollanten till en BoundingBox-komponent, som är en MRTK-komponent som återger en begränsningsruta runt ett objekt med en BoxCollider. En ObjectManipulator, som ansvarar för handinteraktioner. Med dessa kombinerade skript kan vi flytta, rotera och skala den fjärrregivna modellen.

  1. Flytta musen till spelpanelen och klicka inuti den för att ge den fokus.

  2. Med hjälp av MRTK:s handsimuleringtrycker du på och håller ned den vänstra Skift-tangenten.

  3. Styr den simulerade handen så att handbilden pekar på testmodellen.

    Pekande hand ray

  4. Håll kvar vänsterklick och dra modellen för att flytta den.

Du bör se att det fjärrregivna innehållet flyttas tillsammans med markeringsrutan. Du kan märka en fördröjning eller fördröjning mellan begrä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 för fjärrmodeller

En box-krockande 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ärr ray casting. Fjärrstrimmning är ett API som tillhandahålls av Azure Remote Rendering för att kastar bilder i fjärrscenen och returnerar träffar lokalt. Den här tekniken kan användas för att välja underordnade entiteter i en stor modell eller för att få resultatinformation som position, yt normal och avstånd.

Testmodellen har ett antal underordnade entiteter som kan efterfrågas och väljas. För tillfället matar valet ut namnet på den valda entiteten till Unity-konsolen. Markera den valda entiteten i kapitlet Material, belysning och effekter.

Först skapar vi en statisk omser runt de fjärranslutna ray cast-frågorna. Det här skriptet accepterar en position och riktning i Unity-utrymmet, konverterar den till de datatyper som accepteras av fjärrbilden och returnerar resultatet. Skriptet använder RayCastQueryAsync API:et.

  1. 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 RaycastHitoch 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 att försätta fjärrbilder i den aktuella sessionen. För att vara mer specifika implementerar vi en MRTK-pekarhanterare härnäst. Skriptet implementerar -gränssnittet, som talar om för MRTK att vi vill att skriptet ska IMixedRealityPointerHandler lyssna efter Mixed Reality Pekare.

  2. 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 av MRTK när en pekare "klickar" på en OnPointerClicked kollider, som vår box-kollider. Därefter PointerDataToRemoteRayCast anropas för att konvertera pekarens resultat till en punkt och riktning. Den punkten och riktningen används sedan för att kastar en fjärr ray i fjärrsessionen.

Uppdaterade gränser

Att skicka begäranden om ray casting vid klick är en effektiv strategi för att fråga fjärrobjekt. Det är dock inte en idealisk användarupplevelse eftersom markören krockar med box-krockaren, inte själva modellen.

Du kan också skapa en ny MRTK-pekare som kastar sina rockar i fjärrsessionen oftare. Även om det här ä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 finns i Showcase-appen som finns på lagringsplatsen ARR-exempel.

När en ray cast har slutförts i RemoteRayCastPointerHandler genereras Entity träffen från OnRemoteEntityClicked Unity-händelsen. För att svara på händelsen skapar vi ett hjälpskript som accepterar och Entity utför en åtgärd på den. Vi börjar med att hämta skriptet för att skriva ut namnet på Entity till felsökningsloggen.

  1. Skapa ett nytt skript med namnet RemoteEntityHelper och ersätt dess innehåll med nedanstående:

    // 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. den TestModel GameObject som skapades tidigare lägger du till både komponenten RemoteRayCastPointerHandler och komponenten RemoteEntityHelper.

  3. Tilldela EntityToDebugLog -metoden till OnRemoteEntityClicked händelsen. När händelsens utdatatyp och metods indatatyp matchar kan vi använda Unitys dynamiska händelsesammanställning, som automatiskt skickar händelsevärdet till metoden .

    1. Skapa ett nytt återanropsfält  Lägg till återanrop
    2. Dra komponenten Fjärrentitetshjälpkomponent till fältet Objekt för att referera till det överordnade GameObject  Assign-objektet
    3. Tilldela som EntityToDebugLog motringning  Tilldela återanrop
  4. Tryck på Spela upp i Unity Editor för att starta scenen, ansluta till en fjärrsession och läsa in testmodellen.

  5. Använd MRTK:s handsimulering och håll ned den vänstra Skift-tangenten.

  6. Styr den simulerade handen så att handbilden pekar på testmodellen.

  7. Lång klickning för att simulera en lufttryckning som kör OnPointerClicked händelsen.

  8. Observera Unity-konsolen för ett loggmeddelande med namnet på den underordnade entiteten valt. Till exempel: Exempel  på underordnad entitet

Synkronisera fjärrobjektdiagrammet i 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 ändra specifika underentiteter måste vi skapa lokala GameObjects för att representera dessa entiteter. Först kan vi utforska manuellt i testmodellen.

  1. Starta scenen och läs in testmodellen.
  2. Expandera underordnade objekt för TestModel GameObject i Unity-hierarkin och välj TestModel_Entity GameObject.
  3. I Kontroll klickar du på knappen Visa underordnade. Visa underordnade
  4. Fortsätt att expandera underordnade objekt i hierarkin och klicka på Visa underordnade objekt tills en stor lista över underordnade visas. Alla underordnade

Nu fylls hierarkin i en lista med dussintals entiteter. Om du väljer en av dem visas Transform RemoteEntitySyncObject komponenterna och i Kontroll. Som standard synkroniseras inte varje entitet automatiskt varje bildruta, så lokala ändringar Transform i synkroniseras inte till servern. Du kan kontrollera Synkronisera varje bildruta och sedan flytta, skala eller rotera transformeringen i scenvyn. Du kommer inte att se den renderade modellen i scenvyn, titta på spelvyn för att se modellens position och rotation visuellt uppdatera.

Samma process kan göras programmatiskt och är det första steget i att ändra specifika fjärrentiteter.

  1. Ä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;
    }
    
  2. Lägg till ytterligare ett återanrop till RemoteRayCastPointerHandler-händelsen OnRemoteEntityClicked och ange den till MakeSyncedGameObject . Ytterligare återanrop

  3. Använd MRTK:s handsimulering och håll ned den vänstra Skift-tangenten.

  4. Styr den simulerade handen så att handbilden pekar på testmodellen.

  5. Lång klickning för att simulera en lufttryckning som kör OnPointerClicked händelsen.

  6. Markera och expandera Hierarki för att se ett nytt underobjekt som representerar den klickade entiteten. GameObject-representation

  7. Efter testningen tar du bort motringningen MakeSyncedGameObject för , eftersom vi kommer att inkludera detta som en del av andra effekter senare.

Anteckning

Synkronisering av varje bildruta krävs bara 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 köra frågor mot dess gränser och lägga till manipulationshanterare så att den kan flyttas runt av användarens handkronor.

Nästa steg

Nu kan du manipulera och interagera med dina fjärrregivna modeller! I nästa självstudie tar vi upp hur du ändrar material, ändrar belysningen och tillämpar effekter på fjärrregivna modeller.