Kurz: Zpřesňování materiálů, osvětlení a efektů

V tomto kurzu se naučíte:

  • Zvýraznění a osnova modelů a komponent modelů
  • Použití různých materiálů na modely
  • Průřezy mezi modely s vyříznutými rovinami
  • Přidání jednoduchých animací pro vzdáleně vykreslené objekty

Požadavky

Zvýraznění a osnova

Poskytování vizuální zpětné vazby uživateli je důležitou součástí uživatelského prostředí v každé aplikaci. Azure Remote Rendering poskytuje mechanismy vizuální zpětné vazby prostřednictvím přepsání hierarchického stavu. Přepsání hierarchického stavu se implementuje s komponentami připojenými k místním instancím modelů. Zjistili jsme, jak tyto místní instance vytvořit v tématu Synchronizace grafu vzdálených objektů do hierarchie Unity.

Nejprve vytvoříme obálku kolem komponenty HierarchicalStateOverrideComponent . HierarchicalStateOverrideComponent je místní skript, který řídí přepsání vzdálené entity. Prostředky kurzu obsahují abstraktní základní třídu s názvem BaseEntityOverrideController, kterou rozšíříme, abychom vytvořili obálku.

  1. Vytvořte nový skript s názvem EntityOverrideController 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;
    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);
            }
        }
    }
    

Hlavní úlohou LocalOverride je vytvořit propojení mezi sebou a jeho RemoteComponent. LocalOverride nám pak umožňuje nastavit příznaky stavu pro místní komponentu, které jsou vázané na vzdálenou entitu. Přepsání a jejich stavy jsou popsány na stránce Hierarchické přepsání stavu .

Tato implementace pouze přepíná po jednom stavu. Je však zcela možné kombinovat více přepsání u jednotlivých entit a vytvářet kombinace na různých úrovních v hierarchii. Například kombinace Selected a SeeThrough na jedné komponentě by poskytla osnovu a zároveň by ji zprůhlednilo. Nebo nastavením přepsání kořenové entity Hidden na ForceOn hodnotu při přepsání podřízené entity Hidden na ForceOff by se skrylo všechno kromě podřízené entity s přepsáním.

Abychom mohli použít stavy na entity, můžeme upravit remoteEntityHelper vytvořený dříve.

  1. Upravit RemoteEntityHelper třídy implementovat BaseRemoteEntityHelper abstraktní třídy. Tato úprava umožní použití kontroleru zobrazení, který je k dispozici v prostředcích kurzu. Při úpravě by měl vypadat takto:

    public class RemoteEntityHelper : BaseRemoteEntityHelper
    
  2. Přepište abstraktní metody pomocí následujícího kódu:

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

Tento kód zajišťuje, aby se do cílové entity přidala komponenta EntityOverrideController a pak volá jednu z přepínacích metod. Pokud je to žádoucí, na TestModel GameObject, volání těchto pomocných metod lze provést přidáním RemoteEntityHelper jako zpětné volání do OnRemoteEntityClicked události RemoteRayCastPointerHandler komponenta .

Zpětná volání ukazatele

Teď, když byly tyto skripty přidány do modelu, po připojení k modulu runtime by kontroler zobrazení AppMenu měl mít povolená další rozhraní pro interakci se skriptem EntityOverrideController . Podívejte se na nabídku Model Tools (Nástroje modelu ) a podívejte se na odemknuté kontrolery zobrazení.

V tomto okamžiku by komponenty objektu TestModel GameObject měly vypadat přibližně takto:

Test modelu s dalšími skripty

Tady je příklad přepsání zásobníku u jedné entity. Použili Select jsme a Tint k poskytnutí obrysu i barvení:

Výběr zabarvení testovacího modelu

Řezy roviny

Roviny vyjmutí jsou funkce, kterou je možné přidat do libovolné vzdálené entity. Nejčastěji vytvoříte novou vzdálenou entitu, která není přidružená k žádným datům sítě pro uložení komponenty roviny řezu. Pozice a orientace roviny řezu jsou určeny polohou a orientací vzdálené entity, ke které je připojena.

Vytvoříme skript, který automaticky vytvoří vzdálenou entitu, přidá komponentu roviny vyjmutí a synchronizuje transformaci místního objektu s entitou roviny vyjmutí. Pak můžeme použít CutPlaneViewController k zabalení roviny řezu v rozhraní, které nám umožní manipulovat s ním.

  1. Vytvořte nový skript s názvem RemoteCutPlane a nahraďte jeho 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.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
        }
    }
    

    Tento kód rozšiřuje BaseRemoteCutPlane třídy zahrnuté v Tutorial Assets. Podobně jako vzdáleně vykreslený model tento skript připojí a naslouchá RemoteRenderingState změnám vzdáleného koordinátoru. Když koordinátor dosáhne RuntimeConnected stavu, pokusí se automaticky připojit, pokud má. Budeme také sledovat proměnnou CutPlaneComponent . Toto je komponenta Azure Remote Rendering, která se synchronizuje s rovinou řezu ve vzdálené relaci. Pojďme se podívat, co musíme udělat, abychom vytvořili rovinu řezu.

  2. Nahraďte metodu CreateCutPlane() dokončenou verzí níže:

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

    Tady vytváříme vzdálenou entitu a svážeme ji s místním objektem GameObject. Zajistíme, že vzdálená entita bude mít svou transformaci synchronizovanou s místní transformací nastavením SyncEveryFrame na true. Potom použijeme CreateComponent volání k přidání objektu CutPlaneComponent do vzdáleného objektu. Nakonec nakonfigurujeme rovinu řezu s nastavením definovaným v horní části monobehaviour. Podívejme se, co je potřeba k vyčištění roviny řezu implementací DestroyCutPlane() metody.

  3. Nahraďte metodu DestroyCutPlane() dokončenou verzí níže:

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

Vzhledem k tomu, že vzdálený objekt je poměrně jednoduchý a pouze čistíme vzdálený konec (a udržujeme místní objekt), je jednoduché volat Destroy vzdálený objekt a vymazat odkaz na něj.

AppMenu obsahuje kontroler zobrazení, který se automaticky připojí k rovině řezu a umožní vám s ním pracovat. Není nutné, abyste používali AppMenu ani žádný z řadičů zobrazení, ale pro zajištění lepšího prostředí. Teď otestujte rovinu řezu a její ovladač zobrazení.

  1. Ve scéně vytvořte nový, prázdný Objekt GameObject a pojmenujte ho CutPlane.

  2. Přidejte komponentu RemoteCutPlane do Objektu CutPlane GameObject.

    Konfigurace komponenty Vyjmout rovinu

  3. Stisknutím přehrát v Editoru Unity načtěte vzdálenou relaci a připojte se k němu.

  4. Pomocí simulace rukou MRTK chytněte a otáčejte (podržením Klávesy Ctrl otáčejte) CutPlane a pohybujte ho po scéně. Podívejte se na průřez do modelu TestModel a odhalte interní komponenty.

Příklad vyjmout rovinu

Konfigurace vzdáleného osvětlení

Relace vzdáleného vykreslování podporuje celé spektrum možností osvětlení. Vytvoříme skripty pro sky texture a jednoduchou mapu pro dva typy světla Unity, které se budou používat se vzdáleným vykreslováním.

Textura oblohy

Při změně textury oblohy si můžete vybrat z několika předdefinovaných map cubemap. Ty se načtou do relace a použijí se na texturu oblohy. Je také možné načíst vlastní textury , které můžete použít jako světlo na obloze.

Vytvoříme skript RemoteSky , který obsahuje seznam předdefinovaných dostupných map cubemap ve formě parametrů načítání. Pak uživateli umožníme vybrat a načíst jednu z možností.

  1. Vytvořte nový skript s názvem RemoteSky a nahraďte celý 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 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");
            }
        }
    }
    

    Nejdůležitější částí tohoto kódu je jen několik řádků:

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

    Tady získáme odkaz na texturu, která se má použít, tak, že ji načtete do relace z integrovaného úložiště objektů blob. Pak stačí přiřadit tuto texturu k relacím SkyReflectionTexture , abychom ji mohli použít.

  2. Vytvořte ve scéně prázdný objekt GameObject a pojmenujte ho SkyLight.

  3. Přidejte do objektu SkyLight GameObject skript RemoteSky.

    Přepínání mezi světly sky je možné provést voláním SetSky pomocí jednoho z řetězcových kláves definovaných v AvailableCubemaps. Kontroler zobrazení integrovaný do AppMenu automaticky vytvoří tlačítka a připojí jejich události k volání SetSky pomocí příslušné klávesy.

  4. V Editoru Unity stiskněte Přehrát a povolte připojení.

  5. Po připojení místního modulu runtime ke vzdálené relaci přejděte na AppMenu –> Nástroje pro relace –> Remote Sky , prozkoumejte různé možnosti sky a podívejte se, jak ovlivňují TestModel.

Světla scény

Vzdálená světla scény zahrnují: bod, bod a směr. Podobně jako u roviny řezu, kterou jsme vytvořili výše, jsou tato světla scény vzdálenými entitami, ke kterým jsou připojené komponenty. Důležitým aspektem při osvětlení vzdálené scény je pokus o přizpůsobení osvětlení v místní scéně. Tato strategie není vždy možná, protože mnoho aplikací Unity pro HoloLens 2 nepoužívá fyzicky založené vykreslování pro místně vykreslené objekty. Na určité úrovni ale můžeme simulovat jednodušší výchozí osvětlení Unity.

  1. Vytvořte nový skript s názvem RemoteLight a nahraďte jeho 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.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();
        }
    }
    

    Tento skript vytvoří různé typy vzdálených světel v závislosti na typu místního světla Unity, ke kterému je skript připojený. Vzdálené světlo duplikuje místní světlo ve své poloze, otočení, barvě a intenzitě. Pokud je to možné, kontrolka dálkového ovládání také nastaví další konfiguraci. To není ideální shoda, protože světla Unity nejsou PBR.

  2. Najděte ve scéně Objekt GameObject DirectionalLight . Pokud jste ze scény odebrali výchozí directionalLight : V horním řádku nabídek vyberte GameObject -> Light -> DirectionalLight a vytvořte ve scéně nové světlo.

  3. Vyberte DirectionalLight GameObject a pomocí tlačítka Přidat komponentu přidejte skript RemoteLight .

  4. Vzhledem k tomu, že tento skript implementuje základní třídu BaseRemoteLight, můžete k interakci se vzdáleným světlem použít kontroler zobrazení AppMenu . Přejděte na AppMenu –> Nástroje relace –> Směrové světlo.

    Poznámka

    Uživatelské rozhraní v AppMenu je kvůli jednoduchosti omezené na jednosměrné světlo. Stále je však možné a doporučujeme přidat bodová světla a připojit k nim skript RemoteLight . Tato další světla se dají upravit úpravou vlastností světla Unity v editoru. Místní změny vzdáleného světla budete muset synchronizovat ručně pomocí místní nabídky RemoteLight v inspektoru:

    Ruční synchronizace vzdáleného světla

  5. V Unity Editoru stiskněte Přehrát a autorizujte připojení.

  6. Po připojení modulu runtime ke vzdálené relaci umístěte a namiřte fotoaparát (použijte WASD a pravé tlačítko + pohyb myši), aby se zobrazil ovladač pro zobrazení směrového světla.

  7. Pomocí ovladače vzdáleného zobrazení světla upravte vlastnosti světla. Pomocí simulace rukou MRTK uchopte a otočte (podržením klávesy Ctrl otáčejte) směrové světlo, abyste viděli efekt na osvětlení scény.

    Směrové světlo

Úprava materiálů

Vzdáleně vykreslené materiály lze upravit tak, aby poskytovaly další vizuální efekty, vyladily vizuály vykreslených modelů nebo poskytovaly uživatelům další zpětnou vazbu. Existuje mnoho způsobů a mnoha důvodů, proč upravit materiál. Zde vám ukážeme, jak změnit barvu albeda materiálu a změnit drsnost a kovnost materiálu PBR.

Poznámka

V mnoha případech, pokud lze funkci nebo efekt implementovat pomocí HierarchicalStateOverrideComponent, je ideální použít tuto funkci místo úprav materiálu.

Vytvoříme skript, který přijme cílovou entitu a nakonfiguruje několik OverrideMaterialProperty objektů pro změnu vlastností materiálu cílové entity. Začneme získáním komponenty MeshComponent cílové entity, která obsahuje seznam materiálů použitých v síti. Pro jednoduchost použijeme jenom první nalezený materiál. Tato naivní strategie může velmi snadno selhat v závislosti na tom, jak byl obsah vytvořený, takže byste pravděpodobně chtěli zvolit složitější přístup k výběru vhodného materiálu.

Z materiálu můžeme získat přístup k běžným hodnotám, jako je albedo. Nejprve musí být materiály přetypován ve svém příslušném typu, PbrMaterial nebo ColorMaterial, k načtení jejich hodnot, jak je vidět v GetMaterialColor metoda. Jakmile budeme mít odkaz na požadovaný materiál, stačí nastavit hodnoty a funkce ARR zpracuje synchronizaci mezi místními vlastnostmi materiálu a vzdáleným materiálem.

  1. Vytvořte skript s názvem EntityMaterialController 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;
    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;
        }
    }
    

Typ OverrideMaterialProperty by měl být dostatečně flexibilní, aby bylo možné v případě potřeby změnit několik dalších materiálových hodnot. Typ OverrideMaterialProperty sleduje stav přepsání, udržuje starou a novou hodnotu a k nastavení přepsání používá delegáta. Podívejte se například na ColorOverride:

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

Tím se vytvoří nový OverrideMaterialProperty , kde přepsání zabalí typ Color. Aktuální nebo původní barvu poskytujeme v okamžiku vytvoření přepsání. Dáváme mu také materiál ARR, podle něhož se má jednat. Nakonec je k dispozici delegát, který použije přepsání. Delegát je metoda, která přijímá materiál ARR a typ přepsání zabalí. Tato metoda je nejdůležitější součástí porozumění tomu, jak funkce ARR upravuje hodnoty materiálu.

Metoda ColorOverride používá metodu ApplyMaterialColor ke své práci:

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

Tento kód přijímá materiál a barvu. Zkontroluje, o jaký druh materiálu se jedná, pak provede odlitek materiálu, aby se použila barva.

A RoughnessOverrideMetalnessOverride fungují podobně – k jejich práci se používají ApplyRoughnessValue metody a ApplyMetalnessValue .

Teď otestujeme kontroler materiálu.

  1. Přidejte skript EntityMaterialController do objektu TestModel GameObject.
  2. Stisknutím přehrát v Unity spusťte scénu a připojte se k ARR.
  3. Po připojení modulu runtime ke vzdálené relaci a načtení modelu přejděte na AppMenu –> Model Tools –> Upravit materiál.
  4. Pomocí simulovaných rukou vyberte entitu z modelu a klikněte na model TestModel.
  5. Ověřte, že se kontroler zobrazení materiálu (AppMenu-Model> Tools-Edit> Material) aktualizoval na cílovou entitu.
  6. Pomocí kontroleru zobrazení materiálu upravte materiál v cílové entitě.

Vzhledem k tomu, že upravujeme pouze první materiál sítě, nemusí se materiál měnit. Pomocí hierarchického přepisu SeeThrough zjistěte, jestli se materiál, který měníte, nachází uvnitř sítě.

Příklad úprav materiálu

Další kroky

Gratulujeme! Teď jste implementovali všechny základní funkce Azure Remote Rendering. V další kapitole se dozvíte o zabezpečení azure Remote Rendering a úložiště objektů blob. Toto budou první kroky k vydání komerční aplikace, která používá Azure Remote Rendering.