Oktatóanyag: Anyagok, világítás és effektusok finomítása

Eben az oktatóanyagban az alábbiakkal fog megismerkedni:

  • Modellek és modellösszetevők kiemelése és körvonalazása
  • Különböző anyagok alkalmazása modellekre
  • Szeletelés modelleken vágási síkokkal
  • Egyszerű animációk hozzáadása távolról renderelt objektumokhoz

Előfeltételek

Kiemelés és kiemelés

Ha vizuális visszajelzést ad a felhasználónak, az fontos része a felhasználói élménynek bármely alkalmazásban. Az Azure Remote Rendering hierarchikus állapot felülbírálásokkal biztosít vizuális visszajelzési mechanizmusokat. A hierarchikus állapot felülbírálások helyi modellekhez csatolt összetevőkkel vannak implementálva. A távoli objektumgráf szinkronizálása a Unity-hierarchiába című témakörben megtanultuk, hogyan hozhatja létre ezeket a helyi példányokat.

Először létrehozunk egy burkolót a HierarchicalStateOverrideComponent összetevő köré. A HierarchicalStateOverrideComponent a távoli entitás felülbírálásait vezérlő helyi szkript. Az Oktatóanyag-eszközök tartalmaz egy BaseEntityOverrideController nevű absztrakt alaposztályt, amelyet kiterjesztünk a burkoló létrehozásához.

  1. Hozzon létre egy EntityOverrideController nevű új szkriptet, és cserélje le annak tartalmát a következő kódra:

    // 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;
    
    public class EntityOverrideController : BaseEntityOverrideController
    {
        public override event Action<HierarchicalStates> FeatureOverrideChange;
    
        private ARRHierarchicalStateOverrideComponent localOverride;
        public override ARRHierarchicalStateOverrideComponent LocalOverride
        {
            get
            {
                if (localOverride == null)
                {
                    localOverride = gameObject.GetComponent<ARRHierarchicalStateOverrideComponent>();
                    if (localOverride == null)
                    {
                        localOverride = gameObject.AddComponent<ARRHierarchicalStateOverrideComponent>();
                    }
    
                    var remoteStateOverride = TargetEntity.Entity.FindComponentOfType<HierarchicalStateOverrideComponent>();
    
                    if (remoteStateOverride == null)
                    {
                        // if there is no HierarchicalStateOverrideComponent on the remote side yet, create one
                        localOverride.Create(RemoteManagerUnity.CurrentSession);
                    }
                    else
                    {
                        // otherwise, bind our local stateOverride component to the remote component
                        localOverride.Bind(remoteStateOverride);
    
                    }
                }
                return localOverride;
            }
        }
    
        private RemoteEntitySyncObject targetEntity;
        public override RemoteEntitySyncObject TargetEntity
        {
            get
            {
                if (targetEntity == null)
                    targetEntity = gameObject.GetComponent<RemoteEntitySyncObject>();
                return targetEntity;
            }
        }
    
        private HierarchicalEnableState ToggleState(HierarchicalStates feature)
        {
            HierarchicalEnableState setToState = HierarchicalEnableState.InheritFromParent;
            switch (LocalOverride.RemoteComponent.GetState(feature))
            {
                case HierarchicalEnableState.ForceOff:
                case HierarchicalEnableState.InheritFromParent:
                    setToState = HierarchicalEnableState.ForceOn;
                    break;
                case HierarchicalEnableState.ForceOn:
                    setToState = HierarchicalEnableState.InheritFromParent;
                    break;
            }
    
            return SetState(feature, setToState);
        }
    
        private HierarchicalEnableState SetState(HierarchicalStates feature, HierarchicalEnableState enableState)
        {
            if (GetState(feature) != enableState) //if this is actually different from the current state, act on it
            {
                LocalOverride.RemoteComponent.SetState(feature, enableState);
                FeatureOverrideChange?.Invoke(feature);
            }
    
            return enableState;
        }
    
        public override HierarchicalEnableState GetState(HierarchicalStates feature) => LocalOverride.RemoteComponent.GetState(feature);
    
        public override void ToggleHidden() => ToggleState(HierarchicalStates.Hidden);
    
        public override void ToggleSelect() => ToggleState(HierarchicalStates.Selected);
    
        public override void ToggleSeeThrough() => ToggleState(HierarchicalStates.SeeThrough);
    
        public override void ToggleTint(Color tintColor = default)
        {
            if (tintColor != default) LocalOverride.RemoteComponent.TintColor = tintColor.toRemote();
            ToggleState(HierarchicalStates.UseTintColor);
        }
    
        public override void ToggleDisabledCollision() => ToggleState(HierarchicalStates.DisableCollision);
    
        public override void RemoveOverride()
        {
            var remoteStateOverride = TargetEntity.Entity.FindComponentOfType<HierarchicalStateOverrideComponent>();
            if (remoteStateOverride != null)
            {
                remoteStateOverride.Destroy();
            }
    
            if (localOverride == null)
                localOverride = gameObject.GetComponent<ARRHierarchicalStateOverrideComponent>();
    
            if (localOverride != null)
            {
                Destroy(localOverride);
            }
        }
    }
    

A LocalOverride fő feladata, hogy kapcsolatot hozzon létre saját maga és a között.RemoteComponent A LocalOverride ezután lehetővé teszi az állapotjelzők beállítását a helyi összetevőn, amelyek a távoli entitáshoz vannak kötve. A felülbírálások és állapotuk leírása a Hierarchikus állapot felülbírálások oldalon található .

Ez az implementáció egyszerre csak egy állapotot vált ki. Azonban teljes mértékben kombinálható több felülbírálás egyetlen entitáson, és a hierarchia különböző szintjein is létrehozhat kombinációkat. Ha például egyetlen összetevőt egyesítünk SelectedSeeThrough , akkor körvonalat adnánk neki, miközben transzparenssé is tennék. Vagy ha a gyökérentitás Hidden felülbírálását úgy állítja Hidden be, hogy ForceOnForceOff a gyermekentitás felülbírálása közben mindent elrejt, kivéve a felülbírálással rendelkező gyermeket.

Ha állapotokat szeretne alkalmazni az entitásokra, módosíthatja a korábban létrehozott RemoteEntityHelpert .

  1. Módosítsa a RemoteEntityHelper osztályt a BaseRemoteEntityHelper absztrakt osztály implementálásához. Ez a módosítás lehetővé teszi az oktatóanyag-eszközökben megadott nézetvezérlő használatát. Módosításkor a következőképpen kell kinéznie:

    public class RemoteEntityHelper : BaseRemoteEntityHelper
    
  2. Felülbírálja az absztrakt metódusokat a következő kóddal:

    public override BaseEntityOverrideController EnsureOverrideComponent(Entity entity)
    {
        var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
        var overrideComponent = entityGameObject.GetComponent<EntityOverrideController>();
        if (overrideComponent == null)
            overrideComponent = entityGameObject.AddComponent<EntityOverrideController>();
        return overrideComponent;
    }
    
    public override HierarchicalEnableState GetState(Entity entity, HierarchicalStates feature)
    {
        var overrideComponent = EnsureOverrideComponent(entity);
        return overrideComponent.GetState(feature);
    }
    
    public override void ToggleHidden(Entity entity)
    {
        var overrideComponent = EnsureOverrideComponent(entity);
        overrideComponent.ToggleHidden();
    }
    
    public override void ToggleSelect(Entity entity)
    {
        var overrideComponent = EnsureOverrideComponent(entity);
        overrideComponent.ToggleSelect();
    }
    
    public override void ToggleSeeThrough(Entity entity)
    {
        var overrideComponent = EnsureOverrideComponent(entity);
        overrideComponent.ToggleSeeThrough();
    }
    
    public Color TintColor = new Color(0.0f, 1.0f, 0.0f, 0.1f);
    public override void ToggleTint(Entity entity)
    {
        var overrideComponent = EnsureOverrideComponent(entity);
        overrideComponent.ToggleTint(TintColor);
    }
    
    public override void ToggleDisableCollision(Entity entity)
    {
        var overrideComponent = EnsureOverrideComponent(entity);
        overrideComponent.ToggleHidden();
    }
    
    public override void RemoveOverrides(Entity entity)
    {
        var entityGameObject = entity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
        var overrideComponent = entityGameObject.GetComponent<EntityOverrideController>();
        if (overrideComponent != null)
        {
            overrideComponent.RemoveOverride();
            Destroy(overrideComponent);
        }
    }
    

Ez a kód biztosítja, hogy az EntityOverrideController összetevő hozzá legyen adva a cél entitáshoz, majd meghívja az egyik váltó metódust. Ha szükséges, a TestModel GameObject tesztkörnyezetben a RemoteEntityHelper meghívásával hívhatja meg ezeket a segítő metódusokat a OnRemoteEntityClickedRemoteRayCastPointerHandler összetevő eseményéhez való visszahívásként.

Visszahívások mutatója

Most, hogy ezeket a szkripteket hozzáadta a modellhez, miután csatlakozott a futtatókörnyezethez, az AppMenu nézetvezérlőnek további interfészekkel kell rendelkeznie az EntityOverrideController szkripttel való interakcióhoz. A fel nem oldott nézetvezérlők megtekintéséhez tekintse meg a Modelleszközök menüt.

Ezen a ponton a TestModel GameObject összetevőinek a következőképpen kell kinéznie:

Modell tesztelése további szkriptekkel

Íme egy példa az egyetlen entitásra vonatkozó felülbírálások halmozására. Vázlatot és Tint színezést is használtunk Select és használtunk:

Modell színárnyalatának kiválasztása

Síkok kivágása

A kivágott síkok olyan funkciók, amelyek bármely távoli entitáshoz hozzáadhatók. A leggyakrabban olyan új távoli entitást hoz létre, amely nincs hálóadatokhoz társítva a kivágott sík összetevő tárolásához. A vágósík helyzetét és tájolását a hozzá csatolt távoli entitás pozíciója és tájolása határozza meg.

Létrehozunk egy szkriptet, amely automatikusan létrehoz egy távoli entitást, hozzáad egy vágássík-összetevőt, és szinkronizálja egy helyi objektum átalakítását a kivágott sík entitással. Ezután használhatjuk a CutPlaneViewControllert , hogy a vágósíkot egy olyan felületen burkoljuk, amely lehetővé teszi számunkra, hogy manipuláljuk.

  1. Hozzon létre egy RemoteCutPlane nevű új szkriptet, és cserélje le a kódját az alábbi kódra:

    // 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;
    
    public class RemoteCutPlane : BaseRemoteCutPlane
    {
        public Color SliceColor = new Color(0.5f, 0f, 0f, .5f);
        public float FadeLength = 0.01f;
        public Axis SliceNormal = Axis.NegativeY;
    
        public bool AutomaticallyCreate = true;
    
        private CutPlaneComponent remoteCutPlaneComponent;
        private bool cutPlaneReady = false;
    
        public override bool CutPlaneReady 
        { 
            get => cutPlaneReady;
            set 
            { 
                cutPlaneReady = value;
                CutPlaneReadyChanged?.Invoke(cutPlaneReady);
            }
        }
    
        public override event Action<bool> CutPlaneReadyChanged;
    
        public UnityBoolEvent OnCutPlaneReadyChanged = new UnityBoolEvent();
    
        public void Start()
        {
            // Hook up the event to the Unity event
            CutPlaneReadyChanged += (ready) => OnCutPlaneReadyChanged?.Invoke(ready);
    
            RemoteRenderingCoordinator.CoordinatorStateChange += RemoteRenderingCoordinator_CoordinatorStateChange;
            RemoteRenderingCoordinator_CoordinatorStateChange(RemoteRenderingCoordinator.instance.CurrentCoordinatorState);
        }
    
        private void RemoteRenderingCoordinator_CoordinatorStateChange(RemoteRenderingCoordinator.RemoteRenderingState state)
        {
            switch (state)
            {
                case RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected:
                    if (AutomaticallyCreate)
                        CreateCutPlane();
                    break;
                default:
                    DestroyCutPlane();
                    break;
            }
        }
    
        public override void CreateCutPlane()
        {
            //Implement me
        }
    
        public override void DestroyCutPlane()
        {
            //Implement me
        }
    }
    

    Ez a kód kiterjeszti a Tutorial Assetsben található BaseRemoteCutPlane osztályt. A távolról renderelt modellhez hasonlóan ez a szkript is csatolja és figyeli RemoteRenderingState a távoli koordinátor módosításait. Amikor a koordinátor eléri az állapotot RuntimeConnected , megpróbál automatikusan csatlakozni, ha azt szeretné. Van egy CutPlaneComponent változó is, amit nyomon fogunk követni. Ez az Azure Remote Rendering összetevő, amely szinkronizálódik a távoli munkamenet vágási síkjával. Nézzük meg, mit kell tennünk a vágási sík létrehozásához.

  2. Cserélje le a metódust CreateCutPlane() az alábbi befejezett verzióra:

    public override void CreateCutPlane()
    {
        if (remoteCutPlaneComponent != null)
            return; //Nothing to do!
    
        //Create a root object for the cut plane
        var cutEntity = RemoteRenderingCoordinator.CurrentSession.Connection.CreateEntity();
    
        //Bind the remote entity to this game object
        cutEntity.BindToUnityGameObject(this.gameObject);
    
        //Sync the transform of this object so we can move the cut plane
        var syncComponent = this.gameObject.GetComponent<RemoteEntitySyncObject>();
        syncComponent.SyncEveryFrame = true;
    
        //Add a cut plane to the entity
        remoteCutPlaneComponent = RemoteRenderingCoordinator.CurrentSession.Connection.CreateComponent(ObjectType.CutPlaneComponent, cutEntity) as CutPlaneComponent;
    
        //Configure the cut plane
        remoteCutPlaneComponent.Normal = SliceNormal;
        remoteCutPlaneComponent.FadeColor = SliceColor.toRemote();
        remoteCutPlaneComponent.FadeLength = FadeLength;
        CutPlaneReady = true;
    }
    

    Itt létrehozunk egy távoli entitást, és egy helyi GameObjecthez kötjük. A beállítással SyncEveryFrame biztosítjuk, hogy a távoli entitás átalakítása szinkronizálva legyen a helyi átalakítással true. Ezután a CreateComponent hívással a távoli objektumhoz adunk hozzá egy CutPlaneComponent elemet. Végül konfiguráljuk a vágási síkot a MonoBehaviour tetején megadott beállításokkal. Nézzük meg, mire van szükség a vágási sík eltávolításához a DestroyCutPlane() metódus implementálásával.

  3. Cserélje le a metódust DestroyCutPlane() az alábbi befejezett verzióra:

    public override void DestroyCutPlane()
    {
        if (remoteCutPlaneComponent == null)
            return; //Nothing to do!
    
        remoteCutPlaneComponent.Owner.Destroy();
        remoteCutPlaneComponent = null;
        CutPlaneReady = false;
    }
    

Mivel a távoli objektum meglehetősen egyszerű, és csak a távoli végpontot tisztítjuk meg (és megtartjuk a helyi objektumot), egyszerűen csak meghívjuk Destroy a távoli objektumot, és töröljük a rá mutató hivatkozást.

Az AppMenu tartalmaz egy nézetvezérlőt, amely automatikusan csatlakozik a vágósíkhoz, és lehetővé teszi, hogy használja azt. Nem szükséges, hogy az AppMenu-t vagy a nézetvezérlőket használja, de ezek jobb élményt biztosítanak. Most tesztelje a vágósíkot és annak nézetvezérlőjét.

  1. Hozzon létre egy új, üres GameObject-et a jelenetben, és nevezze el CutPlane-nak.

  2. Adja hozzá a RemoteCutPlane összetevőt a CutPlane GameObject elemhez.

    Kivágott sík összetevő konfigurációja

  3. A Unity-szerkesztőben nyomja le a Play billentyűt egy távoli munkamenet betöltéséhez és csatlakozásához.

  4. Az MRTK kézszimulációját használva ragadja meg és forgassa el (a Ctrl billentyűt lenyomva forgassa) a CutPlane-ot a jelenet körül mozgatásához. Tekintse meg a TestModel szeletelt elemét a belső összetevők megjelenítéséhez.

Példa sík kivágása

A távoli világítás konfigurálása

A távoli renderelési munkamenet a világítási lehetőségek teljes spektrumát támogatja. Létrehozunk szkripteket a Sky Textúra számára, és egy egyszerű térképet két Unity fénytípushoz, amelyek távoli rendereléssel használhatók.

Sky Textúra

Számos beépített kockatérkép közül választhat az égbolt szerkezetének módosításakor. Ezek betöltődnek a munkamenetbe, és az égi anyagmintára lesznek alkalmazva. Az is lehetséges, hogy betöltse a saját anyagmintáit , hogy égi fényként használhassa.

Létrehozunk egy RemoteSky-szkriptet , amely betöltési paraméterek formájában tartalmazza a beépített elérhető kockatérképek listáját. Ezután lehetővé tesszük a felhasználó számára az egyik lehetőség kiválasztását és betöltését.

  1. Hozzon létre egy RemoteSky nevű új szkriptet, és cserélje le a teljes tartalmát az alábbi kódra:

    // 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 System;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class RemoteSky : BaseRemoteSky
    {
        public override Dictionary<string, LoadTextureFromSasOptions> AvailableCubemaps => builtInTextures;
    
        private bool canSetSky;
        public override bool CanSetSky
        {
            get => canSetSky;
            set
            {
                canSetSky = value;
                CanSetSkyChanged?.Invoke(canSetSky);
            }
        }
    
        private string currentSky = "DefaultSky";
        public override string CurrentSky
        {
            get => currentSky;
            protected set
            {
                currentSky = value;
                SkyChanged?.Invoke(value);
            }
        }
    
        private Dictionary<string, LoadTextureFromSasOptions> builtInTextures = new Dictionary<string, LoadTextureFromSasOptions>()
        {
            {"Autoshop",new LoadTextureFromSasOptions("builtin://Autoshop", TextureType.CubeMap)},
            {"BoilerRoom",new LoadTextureFromSasOptions("builtin://BoilerRoom", TextureType.CubeMap)},
            {"ColorfulStudio",new LoadTextureFromSasOptions("builtin://ColorfulStudio", TextureType.CubeMap)},
            {"Hangar",new LoadTextureFromSasOptions("builtin://Hangar", TextureType.CubeMap)},
            {"IndustrialPipeAndValve",new LoadTextureFromSasOptions("builtin://IndustrialPipeAndValve", TextureType.CubeMap)},
            {"Lebombo",new LoadTextureFromSasOptions("builtin://Lebombo", TextureType.CubeMap)},
            {"SataraNight",new LoadTextureFromSasOptions("builtin://SataraNight", TextureType.CubeMap)},
            {"SunnyVondelpark",new LoadTextureFromSasOptions("builtin://SunnyVondelpark", TextureType.CubeMap)},
            {"Syferfontein",new LoadTextureFromSasOptions("builtin://Syferfontein", TextureType.CubeMap)},
            {"TearsOfSteelBridge",new LoadTextureFromSasOptions("builtin://TearsOfSteelBridge", TextureType.CubeMap)},
            {"VeniceSunset",new LoadTextureFromSasOptions("builtin://VeniceSunset", TextureType.CubeMap)},
            {"WhippleCreekRegionalPark",new LoadTextureFromSasOptions("builtin://WhippleCreekRegionalPark", TextureType.CubeMap)},
            {"WinterRiver",new LoadTextureFromSasOptions("builtin://WinterRiver", TextureType.CubeMap)},
            {"DefaultSky",new LoadTextureFromSasOptions("builtin://DefaultSky", TextureType.CubeMap)}
        };
    
        public UnityBoolEvent OnCanSetSkyChanged;
        public override event Action<bool> CanSetSkyChanged;
    
        public UnityStringEvent OnSkyChanged;
        public override event Action<string> SkyChanged;
    
        public void Start()
        {
            // Hook up the event to the Unity event
            CanSetSkyChanged += (canSet) => OnCanSetSkyChanged?.Invoke(canSet);
            SkyChanged += (key) => OnSkyChanged?.Invoke(key);
    
            RemoteRenderingCoordinator.CoordinatorStateChange += ApplyStateToView;
            ApplyStateToView(RemoteRenderingCoordinator.instance.CurrentCoordinatorState);
        }
    
        private void ApplyStateToView(RemoteRenderingCoordinator.RemoteRenderingState state)
        {
            switch (state)
            {
                case RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected:
                    CanSetSky = true;
                    break;
                default:
                    CanSetSky = false;
                    break;
            }
        }
    
        public override async void SetSky(string skyKey)
        {
            if (!CanSetSky)
            {
                Debug.Log("Unable to set sky right now");
                return;
            }
    
            if (AvailableCubemaps.ContainsKey(skyKey))
            {
                Debug.Log("Setting sky to " + skyKey);
                //Load the texture into the session
                var texture = await RemoteRenderingCoordinator.CurrentSession.Connection.LoadTextureFromSasAsync(AvailableCubemaps[skyKey]);
    
                //Apply the texture to the SkyReflectionSettings
                RemoteRenderingCoordinator.CurrentSession.Connection.SkyReflectionSettings.SkyReflectionTexture = texture;
                SkyChanged?.Invoke(skyKey);
            }
            else
            {
                Debug.Log("Invalid sky key");
            }
        }
    }
    

    A kód legfontosabb része csupán néhány sor:

    //Load the texture into the session
    var texture = await RemoteRenderingCoordinator.CurrentSession.Connection.LoadTextureFromSasAsync(AvailableCubemaps[skyKey]);
    
    //Apply the texture to the SkyReflectionSettings
    RemoteRenderingCoordinator.CurrentSession.Connection.SkyReflectionSettings.SkyReflectionTexture = texture;
    

    Itt egy hivatkozást kapunk a használni kívánt anyagmintára, ha betöltjük a munkamenetbe a beépített blobtárolóból. Ezután csak ezt az anyagmintát kell hozzárendelnünk a munkamenethez SkyReflectionTexture , hogy alkalmazhassuk.

  2. Hozzon létre egy üres GameObjectet a jelenetben, és nevezze el SkyLightnak.

  3. Adja hozzá a RemoteSky szkriptet a SkyLight GameObject elemhez.

    Az égi fények közötti váltás a következőben AvailableCubemapsdefiniált sztringkulcsok egyikével végezhető elSetSky: . Az AppMenu beépített nézetvezérlője automatikusan gombokat hoz létre, és összekapcsolja az eseményeket a megfelelő kulccsal való híváshoz SetSky .

  4. Nyomja le a Play billentyűt a Unity-szerkesztőben, és engedélyezze a kapcsolatot.

  5. Miután csatlakoztatta a helyi futtatókörnyezetet egy távoli munkamenethez, navigáljon az AppMenu – Munkamenet-eszközök –>> Távoli égbolt területen a különböző égi lehetőségek megismeréséhez, és nézze meg, hogyan befolyásolják a TestModel-et.

Jelenetvilágítások

A távoli jelenet fényei a következők: pont, pont és irány. A fent létrehozott Kivágott síkhoz hasonlóan ezek a jelenetvilágítások távoli entitások, amelyekhez összetevők vannak csatolva. A távoli jelenet megvilágításának fontos szempontja a helyi jelenet megvilágításának egyeztetése. Ez a stratégia nem mindig lehetséges, mert a HoloLens 2 számos Unity-alkalmazása nem használ fizikailag alapú renderelést helyileg renderelt objektumokhoz. Bizonyos szinten azonban szimulálhatjuk a Unity egyszerűbb alapértelmezett világítását.

  1. Hozzon létre egy RemoteLight nevű új szkriptet, és cserélje le a kódját az alábbi kódra:

    // 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(Light))]
    public class RemoteLight : BaseRemoteLight
    {
        public bool AutomaticallyCreate = true;
    
        private bool lightReady = false;
        public override bool LightReady 
        {
            get => lightReady;
            set
            {
                lightReady = value;
                LightReadyChanged?.Invoke(lightReady);
            }
        }
    
        private ObjectType remoteLightType = ObjectType.Invalid;
        public override ObjectType RemoteLightType => remoteLightType;
    
        public UnityBoolEvent OnLightReadyChanged;
    
        public override event Action<bool> LightReadyChanged;
    
        private Light localLight; //Unity Light
    
        private Entity lightEntity;
        private LightComponentBase remoteLightComponent; //Remote Rendering Light
    
        private void Awake()
        {
            localLight = GetComponent<Light>();
            switch (localLight.type)
            {
                case LightType.Directional:
                    remoteLightType = ObjectType.DirectionalLightComponent;
                    break;
                case LightType.Point:
                    remoteLightType = ObjectType.PointLightComponent;
                    break;
                case LightType.Spot:
                case LightType.Area:
                    //Not supported in tutorial
                case LightType.Disc:
                    // No direct analog in remote rendering
                    remoteLightType = ObjectType.Invalid;
                    break;
            }
        }
    
        public void Start()
        {
            // Hook up the event to the Unity event
            LightReadyChanged += (ready) => OnLightReadyChanged?.Invoke(ready);
    
            RemoteRenderingCoordinator.CoordinatorStateChange += RemoteRenderingCoordinator_CoordinatorStateChange;
            RemoteRenderingCoordinator_CoordinatorStateChange(RemoteRenderingCoordinator.instance.CurrentCoordinatorState);
        }
    
        public void OnDestroy()
        {
            lightEntity?.Destroy();
        }
    
        private void RemoteRenderingCoordinator_CoordinatorStateChange(RemoteRenderingCoordinator.RemoteRenderingState state)
        {
            switch (state)
            {
                case RemoteRenderingCoordinator.RemoteRenderingState.RuntimeConnected:
                    if (AutomaticallyCreate)
                        CreateLight();
                    break;
                default:
                    DestroyLight();
                    break;
            }
        }
    
        public override void CreateLight()
        {
            if (remoteLightComponent != null)
                return; //Nothing to do!
    
            //Create a root object for the light
            if(lightEntity == null)
                lightEntity = RemoteRenderingCoordinator.CurrentSession.Connection.CreateEntity();
    
            //Bind the remote entity to this game object
            lightEntity.BindToUnityGameObject(this.gameObject);
    
            //Sync the transform of this object so we can move the light
            var syncComponent = this.gameObject.GetComponent<RemoteEntitySyncObject>();
            syncComponent.SyncEveryFrame = true;
    
            //Add a light to the entity
            switch (RemoteLightType)
            {
                case ObjectType.DirectionalLightComponent:
                    var remoteDirectional = RemoteRenderingCoordinator.CurrentSession.Connection.CreateComponent(ObjectType.DirectionalLightComponent, lightEntity) as DirectionalLightComponent;
                    //No additional properties
                    remoteLightComponent = remoteDirectional;
                    break;
    
                case ObjectType.PointLightComponent:
                    var remotePoint = RemoteRenderingCoordinator.CurrentSession.Connection.CreateComponent(ObjectType.PointLightComponent, lightEntity) as PointLightComponent;
                    remotePoint.Radius = 0;
                    remotePoint.Length = localLight.range;
                    //remotePoint.AttenuationCutoff = //No direct analog in Unity legacy lights
                    //remotePoint.ProjectedCubeMap = //No direct analog in Unity legacy lights
    
                    remoteLightComponent = remotePoint;
                    break;
                default:
                    LightReady = false;
                    return;
            }
    
            // Set the common values for all light types
            UpdateRemoteLightSettings();
    
            LightReady = true;
        }
    
        public override void UpdateRemoteLightSettings()
        {
            remoteLightComponent.Color = localLight.color.toRemote();
            remoteLightComponent.Intensity = localLight.intensity;
        }
    
        public override void DestroyLight()
        {
            if (remoteLightComponent == null)
                return; //Nothing to do!
    
            remoteLightComponent.Destroy();
            remoteLightComponent = null;
            LightReady = false;
        }
    
        [ContextMenu("Sync Remote Light Configuration")]
        public override void RecreateLight()
        {
            DestroyLight();
            CreateLight();
        }
    
        public override void SetIntensity(float intensity)
        {
            localLight.intensity = Mathf.Clamp(intensity, 0, 1);
            UpdateRemoteLightSettings();
        }
    
        public override void SetColor(Color color)
        {
            localLight.color = color;
            UpdateRemoteLightSettings();
        }
    }
    

    Ez a szkript különböző típusú távoli fényeket hoz létre attól függően, hogy a szkript milyen típusú helyi Unity-fényt csatol. A távoli fény megkettőzi a helyi fényt a saját helyzetében, forgatásában, színében és intenzitásában. Ahol lehetséges, a távoli fény további konfigurációt is beállít. Ez nem tökéletes egyezés, mivel a Unity fényei nem PBR fények.

  2. Keresse meg a DirectionalLight GameObject-et a jelenetben. Ha eltávolította az alapértelmezett DirectionalLightot a jelenetből: a felső menüsávon válassza a GameObject - Light ->> DirectionalLight lehetőséget, hogy új fényt hozzon létre a jelenetben.

  3. Válassza a DirectionalLight GameObject lehetőséget, és az Összetevő hozzáadása gombbal adja hozzá a RemoteLight szkriptet.

  4. Mivel ez a szkript implementálja az alaposztályt BaseRemoteLight, a megadott AppMenu nézetvezérlővel kommunikálhat a távoli fényekkel. Lépjen az AppMenu –> Munkamenet-eszközök –> Irányfény elemre.

    Megjegyzés

    Az AppMenu felhasználói felülete egyetlen irányfényre van korlátozva az egyszerűség kedvéért. Azonban továbbra is lehetséges és javasolt pontfények hozzáadása és a RemoteLight-szkript csatlakoztatása hozzájuk. Ezek a további fények módosíthatók a Unity fény tulajdonságainak szerkesztésével a szerkesztőben. A helyi módosításokat manuálisan kell szinkronizálnia a távoli fényre az ellenőr RemoteLight helyi menüjével:

    Távoli fény manuális szinkronizálása

  5. Nyomja le a Play billentyűt a Unity-szerkesztőben, és engedélyezze a kapcsolatot.

  6. Miután csatlakoztatta a futtatókörnyezetet egy távoli munkamenethez, helyezze és irányítsa a kamerát (használja a WASD-t, és kattintson a jobb gombbal + egérmozgatás) az irányfény-vezérlő megtekintéséhez.

  7. A fény tulajdonságainak módosításához használja a távoli fénynézet-vezérlőt. Az MRTK kézi szimulációjával megragadhatja és elforgathatja (a Ctrl billentyűt lenyomva elforgathatja) az irányfényt, hogy lássa a jelenet megvilágítására gyakorolt hatást.

    Irányfény

Anyagok szerkesztése

A távolról renderelt anyagok módosíthatók további vizuális effektusok biztosításához, a renderelt modellek vizualizációinak finomhangolásához, vagy további visszajelzések küldéséhez a felhasználók számára. Az anyag módosításának számos módja és számos oka van. Itt bemutatjuk, hogyan módosíthatja az anyag albedo színét, és hogyan módosíthatja a PBR anyag érdességét és fémtartalmát.

Megjegyzés

Sok esetben, ha egy funkció vagy effektus implementálható hierarchikusStateOverrideComponent használatával, ideális, ha ezt használja az anyag módosítása helyett.

Létrehozunk egy szkriptet, amely elfogadja a célentitást, és konfigurál néhány OverrideMaterialProperty objektumot a cél entitás anyagának tulajdonságainak módosítására. Először lekértük a cél entitás MeshComponentjét, amely tartalmazza a hálóban használt anyagok listáját. Az egyszerűség kedvéért csak az első talált anyagot fogjuk használni. Ez a naiv stratégia nagyon könnyen meghiúsulhat a tartalom szerzői módjától függően, ezért valószínűleg összetettebb megközelítést szeretne használni a megfelelő anyag kiválasztásához.

Az anyagból hozzáférhetünk az olyan közös értékekhez, mint az albedo. Először az anyagokat a megfelelő típusba kell öntöttetni, PbrMaterial vagy ColorMaterialaz értéket lekérni a GetMaterialColor metódusban látható módon. Miután hivatkoztunk a kívánt anyagra, csak állítsa be az értékeket, és az ARR kezeli a szinkronizálást a helyi anyag tulajdonságai és a távoli anyag között.

  1. Hozzon létre egy EntityMaterialController nevű szkriptet, és cserélje le annak tartalmát a következő kódra:

    // 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 System.Linq;
    using UnityEngine;
    // to prevent namespace conflicts
    using ARRMaterial = Microsoft.Azure.RemoteRendering.Material;
    
    public class EntityMaterialController : BaseEntityMaterialController
    {
        public override bool RevertOnEntityChange { get; set; } = true;
    
        public override OverrideMaterialProperty<Color> ColorOverride { get; set; }
        public override OverrideMaterialProperty<float> RoughnessOverride { get; set; }
        public override OverrideMaterialProperty<float> MetalnessOverride { get; set; }
    
        private Entity targetEntity;
        public override Entity TargetEntity
        {
            get => targetEntity;
            set
            {
                if (targetEntity != value)
                {
                    if (targetEntity != null && RevertOnEntityChange)
                    {
                        Revert();
                    }
    
                    targetEntity = value;
                    ConfigureTargetEntity();
                    TargetEntityChanged?.Invoke(value);
                }
            }
        }
    
        private ARRMaterial targetMaterial;
        private ARRMeshComponent meshComponent;
    
        public override event Action<Entity> TargetEntityChanged;
        public UnityRemoteEntityEvent OnTargetEntityChanged;
    
        public void Start()
        {
            // Forward events to Unity events
            TargetEntityChanged += (entity) => OnTargetEntityChanged?.Invoke(entity);
    
            // If there happens to be a remote RayCaster on this object, assume we should listen for events from it
            if (GetComponent<BaseRemoteRayCastPointerHandler>() != null)
                GetComponent<BaseRemoteRayCastPointerHandler>().RemoteEntityClicked += (entity) => TargetEntity = entity;
        }
    
        protected override void ConfigureTargetEntity()
        {
            //Get the Unity object, to get the sync object, to get the mesh component, to get the material.
            var targetEntityGameObject = TargetEntity.GetOrCreateGameObject(UnityCreationMode.DoNotCreateUnityComponents);
    
            var localSyncObject = targetEntityGameObject.GetComponent<RemoteEntitySyncObject>();
            meshComponent = targetEntityGameObject.GetComponent<ARRMeshComponent>();
            if (meshComponent == null)
            {
                var mesh = localSyncObject.Entity.FindComponentOfType<MeshComponent>();
                if (mesh != null)
                {
                    targetEntityGameObject.BindArrComponent<ARRMeshComponent>(mesh);
                    meshComponent = targetEntityGameObject.GetComponent<ARRMeshComponent>();
                }
            }
    
            meshComponent.enabled = true;
    
            targetMaterial = meshComponent.RemoteComponent.Mesh.Materials.FirstOrDefault();
            if (targetMaterial == default)
            {
                return;
            }
    
            ColorOverride = new OverrideMaterialProperty<Color>(
                GetMaterialColor(targetMaterial), //The original value
                targetMaterial, //The target material
                ApplyMaterialColor); //The action to take to apply the override
    
            //If the material is a PBR material, we can override some additional values
            if (targetMaterial.MaterialSubType == MaterialType.Pbr)
            {
                var firstPBRMaterial = (PbrMaterial)targetMaterial;
    
                RoughnessOverride = new OverrideMaterialProperty<float>(
                    firstPBRMaterial.Roughness, //The original value
                    targetMaterial, //The target material
                    ApplyRoughnessValue); //The action to take to apply the override
    
                MetalnessOverride = new OverrideMaterialProperty<float>(
                    firstPBRMaterial.Metalness, //The original value
                    targetMaterial, //The target material
                    ApplyMetalnessValue); //The action to take to apply the override
            }
            else //otherwise, ensure the overrides are cleared out from any previous entity
            {
                RoughnessOverride = null;
                MetalnessOverride = null;
            }
        }
    
        public override void Revert()
        {
            if (ColorOverride != null)
                ColorOverride.OverrideActive = false;
    
            if (RoughnessOverride != null)
                RoughnessOverride.OverrideActive = false;
    
            if (MetalnessOverride != null)
                MetalnessOverride.OverrideActive = false;
        }
    
        private Color GetMaterialColor(ARRMaterial material)
        {
            if (material == null)
                return default;
    
            if (material.MaterialSubType == MaterialType.Color)
                return ((ColorMaterial)material).AlbedoColor.toUnity();
            else
                return ((PbrMaterial)material).AlbedoColor.toUnity();
        }
    
        private void ApplyMaterialColor(ARRMaterial material, Color color)
        {
            if (material == null)
                return;
    
            if (material.MaterialSubType == MaterialType.Color)
                ((ColorMaterial)material).AlbedoColor = color.toRemoteColor4();
            else
                ((PbrMaterial)material).AlbedoColor = color.toRemoteColor4();
        }
    
        private void ApplyRoughnessValue(ARRMaterial material, float value)
        {
            if (material == null)
                return;
    
            if (material.MaterialSubType == MaterialType.Pbr) //Only PBR has Roughness
                ((PbrMaterial)material).Roughness = value;
        }
    
        private void ApplyMetalnessValue(ARRMaterial material, float value)
        {
            if (material == null)
                return;
    
            if (material.MaterialSubType == MaterialType.Pbr) //Only PBR has Metalness
                ((PbrMaterial)material).Metalness = value;
        }
    }
    

A OverrideMaterialProperty típusnak elég rugalmasnak kell lennie ahhoz, hogy szükség esetén más anyagértékeket is módosíthasson. A OverrideMaterialProperty típus nyomon követi a felülbírálás állapotát, fenntartja a régi és az új értéket, és meghatalmazott használatával állítja be a felülbírálást. Példaként tekintse meg a következőt ColorOverride:

ColorOverride = new OverrideMaterialProperty<Color>(
    GetMaterialColor(targetMaterial), //The original value
    targetMaterial, //The target material
    ApplyMaterialColor); //The action to take to apply the override

Ez egy újat OverrideMaterialProperty hoz létre, amelyben a felülbírálás a típust Colorburkolja. A felülbírálás létrehozásakor az aktuális vagy az eredeti színt adjuk meg. Emellett adunk neki egy ARR-anyagot is, amely alapján cselekedhet. Végül meg kell adni egy meghatalmazottat, aki alkalmazza a felülbírálást. A delegált egy olyan metódus, amely elfogadja az ARR-anyagot, és a felülbírálási körbefuttatás típusát. Ez a módszer a legfontosabb része annak megértésének, hogy az ARR hogyan módosítja az anyagértékeket.

A ColorOverride metódust használja a ApplyMaterialColor munka elvégzésére:

private void ApplyMaterialColor(ARRMaterial material, Color color)
{
    if (material.MaterialSubType == MaterialType.Color)
        ((ColorMaterial)material).AlbedoColor = color.toRemoteColor4();
    else
        ((PbrMaterial)material).AlbedoColor = color.toRemoteColor4();
}

Ez a kód egy anyagot és egy színt fogad el. Ellenőrzi, hogy milyen anyagról van szó, majd elvégzi az anyag öntött színének alkalmazását.

A RoughnessOverride és MetalnessOverride a hasonlóan működik - a és ApplyMetalnessValue a ApplyRoughnessValue metódusok használatával végzi el a munkáját.

Ezután teszteljük az anyagvezérlőt.

  1. Adja hozzá az EntityMaterialController szkriptet a TestModel GameObject elemhez.
  2. Nyomja le a Play in Unity billentyűt a jelenet elindításához és az ARR-hez való csatlakozáshoz.
  3. Miután csatlakoztatta a futtatókörnyezetet egy távoli munkamenethez, és betöltötte a modellt, lépjen az AppMenu – Modelleszközök –>> Anyag szerkesztése elemre.
  4. Válasszon ki egy entitást a modellből a szimulált kezek használatával a TestModel elemre való kattintáshoz.
  5. Ellenőrizze, hogy az anyagnézet-vezérlő (AppMenu-Model Tools-Edit Material) frissült-e>> a megcélzott entitásra.
  6. Az anyagnézet-vezérlővel módosíthatja az anyagot a megcélzott entitáson.

Mivel csak a háló első anyagát módosítjuk, előfordulhat, hogy az anyag nem változik. A hierarchikus felülbírálási SeeThrough használatával ellenőrizze, hogy a módosított anyag a hálóban van-e.

Példa anyagszerkesztésre

Következő lépések

Gratulálunk! Most már implementálta az Azure Remote Rendering összes alapvető funkcióját. A következő fejezetben megismerkedünk az Azure Remote Rendering és a Blob Storage biztonságossá tételével. Ezek lesznek az első lépések az Azure Remote Rendering használó kereskedelmi alkalmazások kiadásához.