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
- Ez az oktatóanyag a következő oktatóanyagra épül: Modellek manipulálása.
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.
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 Selected
SeeThrough
, 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 ForceOn
ForceOff
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 .
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
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 OnRemoteEntityClicked
RemoteRayCastPointerHandler összetevő eseményéhez való visszahívásként.
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:
Í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:
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.
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 állapototRuntimeConnected
, megpróbál automatikusan csatlakozni, ha azt szeretné. Van egyCutPlaneComponent
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.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ássaltrue
. Ezután aCreateComponent
hívással a távoli objektumhoz adunk hozzá egyCutPlaneComponent
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 aDestroyCutPlane()
metódus implementálásával.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.
Hozzon létre egy új, üres GameObject-et a jelenetben, és nevezze el CutPlane-nak.
Adja hozzá a RemoteCutPlane összetevőt a CutPlane GameObject elemhez.
A Unity-szerkesztőben nyomja le a Play billentyűt egy távoli munkamenet betöltéséhez és csatlakozásához.
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.
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.
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.Hozzon létre egy üres GameObjectet a jelenetben, és nevezze el SkyLightnak.
Adja hozzá a RemoteSky szkriptet a SkyLight GameObject elemhez.
Az égi fények közötti váltás a következőben
AvailableCubemaps
definiá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áshozSetSky
.Nyomja le a Play billentyűt a Unity-szerkesztőben, és engedélyezze a kapcsolatot.
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.
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.
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.
Válassza a DirectionalLight GameObject lehetőséget, és az Összetevő hozzáadása gombbal adja hozzá a RemoteLight szkriptet.
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:
Nyomja le a Play billentyűt a Unity-szerkesztőben, és engedélyezze a kapcsolatot.
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.
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.
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 ColorMaterial
az é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.
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 Color
burkolja. 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.
- Adja hozzá az EntityMaterialController szkriptet a TestModel GameObject elemhez.
- Nyomja le a Play in Unity billentyűt a jelenet elindításához és az ARR-hez való csatlakozáshoz.
- 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.
- Válasszon ki egy entitást a modellből a szimulált kezek használatával a TestModel elemre való kattintáshoz.
- Ellenőrizze, hogy az anyagnézet-vezérlő (AppMenu-Model Tools-Edit Material) frissült-e>> a megcélzott entitásra.
- 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.
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.