HoloLens (1. generációs) Spatial 230: Spatial mapping
Fontos
A Mixed Reality Academy oktatóanyagait a HoloLens (1. generációs), a Unity 2017 és Mixed Reality modern headsetek szem előtt tartásával terveztük. Ezért fontosnak tartjuk, hogy ezeket az oktatóanyagokat helyben hagyjuk azoknak a fejlesztőknek, akik továbbra is útmutatást keresnek az ilyen eszközök fejlesztéséhez. Ezek az oktatóanyagok nem frissülnek a legújabb eszközkészletekkel vagy interakciókkal, amelyeket a HoloLens 2 használnak, és előfordulhat, hogy nem kompatibilisek a Unity újabb verzióival. A támogatott eszközökön továbbra is működni fognak. A HoloLens 2 új oktatóanyag-sorozatot tett közzé.
A térbeli leképezés kombinálja a valós világot és a virtuális világot azáltal, hogy hologramokat tanít a környezetről. Az MR Spatial 230 -ban (Project Planetárium) megtanuljuk, hogyan:
- Vizsgálja meg a környezetet, és vigye át az adatokat a HoloLensből a fejlesztői gépre.
- Fedezze fel az árnyékolókat, és ismerje meg, hogyan használhatja őket a tér vizualizációjához.
- Bontsa le a helyiséghálót egyszerű síkokra hálófeldolgozással.
- Az MR Basics 101-ben elsajátított elhelyezési technikákon túl visszajelzést is adhat arról, hogy hol helyezhető el a hologram a környezetben.
- Fedezze fel az elzáródási effektusokat, így ha a hologram egy valós objektum mögött van, akkor is láthatja a röntgenlátással!
Eszköztámogatás
Tanfolyam | HoloLens | Modern headsetek |
---|---|---|
MR Spatial 230: Térbeli leképezés | ✔️ |
Előkészületek
Előfeltételek
- A megfelelő eszközökkel konfigurált Windows 10 számítógép.
- Néhány alapszintű C#-programozási képesség.
- El kellett volna végeznie az MR Basics 101-et.
- Fejlesztésre konfigurált HoloLens-eszköz.
Projektfájlok
- Töltse le a projekthez szükséges fájlokat . A Unity 2017.2-s vagy újabb verziójára van szükség.
- Ha továbbra is szüksége van a Unity 5.6-ra, használja ezt a kiadást.
- Ha továbbra is unity 5.5-támogatásra van szüksége, használja ezt a kiadást.
- Ha továbbra is unity 5.4-támogatásra van szüksége, használja ezt a kiadást.
- Törölje a fájlok archiválását az asztalra vagy más könnyen elérhető helyre.
Megjegyzés
Ha a letöltés előtt szeretné áttekinteni a forráskódot, az elérhető a GitHubon.
Jegyzetek
- A Visual Studióban az "Enable Just My Code" (Csak a kód engedélyezése) lehetőséget le kell tiltani (nincs bejelölve) az Eszközök > beállításai > hibakeresése területen a kód töréspontjainak eléréséhez.
Unity-beállítás
- Indítsa el a Unityt.
- Új projekt létrehozásához válassza az Új lehetőséget.
- Nevezze el a projektet Planetáriumnak.
- Ellenőrizze, hogy a 3D beállítás be van-e jelölve.
- Kattintson a Create Project (Projekt létrehozása) elemre.
- A Unity elindítása után lépjen a Projektbeállítások > lejátszójának szerkesztése >területre.
- Az Inspector panelen keresse meg és válassza a zöld Windows Áruház ikont.
- Bontsa ki az Egyéb beállítások elemet.
- A Renderelés szakaszban ellenőrizze a Virtuális valóság által támogatott beállítást.
- Ellenőrizze, hogy a Windows Holographic megjelenik-e a Virtual Reality SDK-k listájában. Ha nem, válassza a + lista alján található gombot, és válassza a Windows Holographic lehetőséget.
- Bontsa ki a Közzétételi beállítások elemet.
- A Képességek szakaszban ellenőrizze a következő beállításokat:
- InternetClientServer
- PrivateNetworkClientServer
- Mikrofon
- SpatialPerception
- Lépjen a Projektbeállítások > minőségének szerkesztése > területre
- Az Inspector panelEn, a Windows Áruház ikonja alatt válassza a fekete legördülő nyilat az "Alapértelmezett" sor alatt, és módosítsa az alapértelmezett beállítást Nagyon alacsony értékre.
- Lépjen az Eszközök > importálása csomag > egyéni csomagja elemre.
- Lépjen a ...\HolographicAcademy-Holograms-230-SpatialMapping\Starting mappába.
- Kattintson a Planetarium.unitypackage elemre.
- Kattintson a Megnyitás gombra.
- Ekkor megjelenik egy Unity-csomag importálása ablak, majd kattintson az Importálás gombra.
- Várja meg, amíg a Unity importálja a projekt befejezéséhez szükséges összes objektumot.
- A Hierarchia panelen törölje a fő kamerát.
- A Projekt panel HoloToolkit-SpatialMapping-230\Utilities\Prefabs mappájában keresse meg a Fő kamera objektumot.
- Húzza a Fő kamera előlapját a Hierarchia panelre.
- A Hierarchia panelen törölje a Directional Light objektumot.
- A Projekt panel Hologramok mappájában keresse meg a Cursor objektumot.
- Húzza & húzza a kurzor előlapját a hierarchiába.
- A Hierarchia panelen válassza a Kurzor objektumot.
- Az Inspector panelen kattintson a Réteg legördülő listára, és válassza a Rétegek szerkesztése... lehetőséget.
- Nevezze el a 31. felhasználói réteget "SpatialMapping" névvel.
- Az új jelenet mentése: Fájlmentési > jelenet másként...
- Kattintson az Új mappa elemre, és nevezze el a mappát Scenes (Jelenetek) névre.
- Nevezze el a fájlt "Planetárium" néven, és mentse a Scenes mappába.
1. fejezet – Vizsgálat
Célkitűzések
- Ismerje meg a SurfaceObservert, valamint azt, hogy a beállítások hogyan befolyásolják a felhasználói élményt és a teljesítményt.
- Hozzon létre egy helyiségvizsgálati felületet a szoba hálóinak gyűjtéséhez.
Utasítások
- A Projekt panel HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs mappájában keresse meg a SpatialMapping előfabot.
- Húzza & a SpatialMapping előtagot a Hierarchia panelre.
Buildelés és üzembe helyezés (1. rész)
- A Unityben válassza a Fájl > buildelési beállításai lehetőséget.
- Kattintson a Nyitott jelenetek hozzáadása elemre a Planetárium jelenet buildhez való hozzáadásához.
- Válassza a Univerzális Windows-platform lehetőséget a Platform listában, majd kattintson a Platformváltás elemre.
- Állítsa az SDK-tUniverzális 10 - és UWP-buildtípusraD3D értékre.
- Ellenőrizze a Unity C#-projekteket.
- Kattintson a Build (Build) elemre.
- Hozzon létre egy "App" nevű új mappát .
- Kattintson egyszer az Alkalmazás mappára.
- Nyomja le a Mappa kiválasztása gombot.
- Amikor a Unity elkészült, megjelenik egy Fájlkezelő ablak.
- Kattintson duplán az Alkalmazás mappára a megnyitásához.
- Kattintson duplán a Planetarium.sln fájlra a projekt Visual Studióban való betöltéséhez.
- A Visual Studióban a felső eszköztár használatával módosítsa a Konfigurációt Kiadásra.
- Módosítsa a platformot x86-ra.
- Kattintson a helyi gép jobb oldalán található legördülő nyílra, és válassza a Távoli gép lehetőséget.
- Írja be az eszköz IP-címét a Cím mezőbe, és módosítsa a hitelesítési módot Univerzális (titkosítatlan protokoll) módra.
- Kattintson a Hibakeresés –> Indítás hibakeresés nélkül parancsra, vagy nyomja le a Ctrl + F5 billentyűkombinációt.
- Tekintse meg a Visual Studio Kimenet paneljének buildelési és üzembe helyezési állapotát.
- Az alkalmazás üzembe helyezése után járja körbe a helyiséget. Látni fogja a fekete-fehér drótvázhálók által lefedett környező felületeket.
- Vizsgálja meg a környezetét. Ügyeljen arra, hogy megtekintse a falakat, a mennyezetet és a padlót.
Buildelés és üzembe helyezés (2. rész)
Most vizsgáljuk meg, hogyan befolyásolhatja a térbeli leképezés a teljesítményt.
- A Unityben válassza a Windows > Profiler lehetőséget.
- Kattintson a Profiler > GPU hozzáadása elemre.
- Kattintson az Active Profiler ><Enter IP (Ip-cím megadása) elemre>.
- Adja meg a HoloLens IP-címét .
- Kattintson a Csatlakozás gombra.
- Figyelje meg, hogy hány ezredmásodpercre van szükség ahhoz, hogy a GPU renderelje a keretet.
- Állítsa le az alkalmazás futtatását az eszközön.
- Térjen vissza a Visual Studióba, és nyissa meg a SpatialMappingObserver.cs fájlt. Ezt a Assembly-CSharp (Univerzális Windows) projekt HoloToolkit\SpatialMapping mappájában találja.
- Keresse meg a Awake() függvényt, és adja hozzá a következő kódsort: TrianglesPerCubicMeter = 1200;
- Telepítse újra a projektet az eszközén, majd csatlakoztassa újra a profilkészítőt. Figyelje meg a keret rendereléséhez tartozó ezredmásodpercek számának változását.
- Állítsa le az alkalmazás futtatását az eszközön.
Mentés és betöltés a Unityben
Végül mentsük meg a szobahálót, és töltsük be a Unitybe.
- Térjen vissza a Visual Studióba, és távolítsa el az előző szakaszban az Awake() függvényben hozzáadott TrianglesPerCubicMeter sort.
- Helyezze újra üzembe a projektet az eszközén. Most köbméterenként 500 háromszöggel kell futnunk.
- Nyisson meg egy böngészőt, és írja be a HoloLens IPAddress címet a Windows-eszközportálra való navigáláshoz.
- Válassza a 3D nézet lehetőséget a bal oldali panelen.
- A Surface rekonstrukciója területen válassza a Frissítés gombot.
- Figyelje meg, hogy a HoloLensen beolvasott területek megjelennek a megjelenítési ablakban.
- A szobavizsgálat mentéséhez nyomja le a Mentés gombot.
- Nyissa meg a Letöltések mappát a mentett helyiségmodell SRMesh.obj megkereséséhez.
- Másolja a SRMesh.obj fájlt a Unity-projekt Assets mappájába.
- A Unityben válassza ki a SpatialMapping objektumot a Hierarchia panelen.
- Keresse meg az Object Surface Observer (Script) összetevőt.
- Kattintson a Helyiségmodell tulajdonságtól jobbra lévő körre.
- Keresse meg és válassza ki az SRMesh objektumot, majd zárja be az ablakot.
- Ellenőrizze, hogy az Inspector panel Helyiségmodell tulajdonsága mostantól SRMesh értékre van-e állítva.
- Nyomja le a Lejátszás gombot a Unity előnézeti módjának megadásához.
- A SpatialMapping összetevő betölti a hálókat a mentett helyiségmodellből, így használhatja őket a Unityben.
- Váltson Jelenet nézetre, hogy az összes szobamodell megjelenik a drótváz árnyékolóval.
- Nyomja le ismét a Lejátszás gombot az előnézeti módból való kilépéshez.
MEGJEGYZÉS: Amikor legközelebb belép az előnézeti módba a Unityben, alapértelmezés szerint betölti a mentett helyiséghálót.
2. fejezet – Vizualizáció
Célkitűzések
- Ismerje meg az árnyékolók alapjait.
- Vizualizálja a környezetét.
Utasítások
- A Unity Hierarchy paneljén válassza ki a SpatialMapping objektumot.
- Az Inspector panelen keresse meg a Spatial Mapping Manager (Script) összetevőt.
- Kattintson a Surface Material tulajdonságtól jobbra lévő körre.
- Keresse meg és válassza ki a BlueLinesOnWalls anyagot, és zárja be az ablakot.
- A Project panel Shaders mappájában kattintson duplán a BlueLinesOnWalls elemre az árnyékoló Visual Studióban való megnyitásához.
- Ez egy egyszerű képpont (csúcstól töredezettig) árnyékoló, amely a következő feladatokat hajtja végre:
- Egy csúcs helyét világűrté alakítja.
- A csúcspont normál értékének ellenőrzése annak megállapításához, hogy egy képpont függőleges-e.
- Beállítja a képpont színét a rendereléshez.
Buildelés és üzembe helyezés
- Térjen vissza a Unitybe, és nyomja le a Play billentyűt az előnézeti módba való belépéshez.
- A kék vonalak a helyiségháló minden függőleges felületén megjelennek (amelyek automatikusan betöltődnek a mentett vizsgálati adatokból).
- Váltson a Jelenet lapra, és állítsa be a szoba nézetét, és nézze meg, hogyan jelenik meg a teljes helyiségháló a Unityben.
- A Projekt panelen keresse meg az Anyagok mappát, és válassza ki a BlueLinesOnWalls anyagot.
- Módosítson néhány tulajdonságot, és tekintse meg, hogyan jelennek meg a módosítások a Unity-szerkesztőben.
- Az Inspector panelen állítsa be a LineScale értéket, hogy a vonalak vastagabbak vagy vékonyabbak legyenek.
- Az Inspector panelen módosítsa a LinesPerMeter értéket, hogy megváltoztassa, hány sor jelenjen meg az egyes falakon.
- Kattintson ismét a Lejátszás gombra az előnézeti módból való kilépéshez.
- Buildelje és helyezze üzembe a HoloLensben, és figyelje meg, hogyan jelenik meg a árnyékoló renderelése valós felületeken.
A Unity kiválóan alkalmas az anyagok előnézetének megtekintésére, de mindig érdemes kivenni a renderelést az eszközön.
3. fejezet – Feldolgozás
Célkitűzések
- Megismerheti az alkalmazásban használható térbeli leképezési adatok feldolgozásának technikáját.
- Térbeli leképezési adatok elemzése síkok kereséséhez és háromszögek eltávolításához.
- Használjon síkokat a hologram elhelyezéséhez.
Utasítások
- A Unity Projekt paneljének Hologramok mappájában keresse meg a SpatialProcessing objektumot.
- Húzza & a SpatialProcessing objektumot a Hierarchia panelre.
A SpatialProcessing előfab a térbeli leképezési adatok feldolgozására szolgáló összetevőket tartalmaz. A SurfaceMeshesToPlanes.cs megkeresi és létrehozza a síkokat a térbeli leképezési adatok alapján. Az alkalmazásban síkokat használunk a falak, padlók és mennyezetek ábrázolására. Ez az előtag a RemoveSurfaceVertices.cs fájlt is tartalmazza, amely eltávolítja a csúcspontokat a térbeli leképezési hálóból. Ezzel lyukakat hozhat létre a hálóban, vagy eltávolíthatja a felesleges háromszögeket, amelyekre már nincs szükség (mivel a síkok használhatók helyette).
- A Unity Projekt paneljének Hologramok mappájában keresse meg a SpaceCollection objektumot.
- Húzza a SpaceCollection objektumot a Hierarchia panelre.
- A Hierarchia panelen válassza ki a SpatialProcessing objektumot.
- Az Inspector panelen keresse meg a Play Space Manager (Szkript) összetevőt.
- Kattintson duplán a PlaySpaceManager.cs fájlra a Visual Studióban való megnyitásához.
A PlaySpaceManager.cs alkalmazásspecifikus kódot tartalmaz. A következő viselkedés engedélyezéséhez funkciókat adunk hozzá ehhez a szkripthez:
- A térbeli leképezési adatok gyűjtésének leállítása a vizsgálati időkorlát (10 másodperc) túllépése után.
- A térbeli leképezési adatok feldolgozása:
- A SurfaceMeshesToPlanes használatával egyszerűbben ábrázolhatja a világot síkként (falak, padlók, mennyezetek stb.).
- A RemoveSurfaceVertices használatával eltávolíthatja a síkhatárokon belül eső felületi háromszögeket.
- Hozzon létre egy gyűjtemény hologramokat a világon, és helyezze őket a fal és a padló síkok közelében a felhasználó.
Végezze el a PlaySpaceManager.cs fájlban megjelölt kódolási gyakorlatokat, vagy cserélje le a szkriptet az alábbi kész megoldásra:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.Speech;
using Academy.HoloToolkit.Unity;
/// <summary>
/// The SurfaceManager class allows applications to scan the environment for a specified amount of time
/// and then process the Spatial Mapping Mesh (find planes, remove vertices) after that time has expired.
/// </summary>
public class PlaySpaceManager : Singleton<PlaySpaceManager>
{
[Tooltip("When checked, the SurfaceObserver will stop running after a specified amount of time.")]
public bool limitScanningByTime = true;
[Tooltip("How much time (in seconds) that the SurfaceObserver will run after being started; used when 'Limit Scanning By Time' is checked.")]
public float scanTime = 30.0f;
[Tooltip("Material to use when rendering Spatial Mapping meshes while the observer is running.")]
public Material defaultMaterial;
[Tooltip("Optional Material to use when rendering Spatial Mapping meshes after the observer has been stopped.")]
public Material secondaryMaterial;
[Tooltip("Minimum number of floor planes required in order to exit scanning/processing mode.")]
public uint minimumFloors = 1;
[Tooltip("Minimum number of wall planes required in order to exit scanning/processing mode.")]
public uint minimumWalls = 1;
/// <summary>
/// Indicates if processing of the surface meshes is complete.
/// </summary>
private bool meshesProcessed = false;
/// <summary>
/// GameObject initialization.
/// </summary>
private void Start()
{
// Update surfaceObserver and storedMeshes to use the same material during scanning.
SpatialMappingManager.Instance.SetSurfaceMaterial(defaultMaterial);
// Register for the MakePlanesComplete event.
SurfaceMeshesToPlanes.Instance.MakePlanesComplete += SurfaceMeshesToPlanes_MakePlanesComplete;
}
/// <summary>
/// Called once per frame.
/// </summary>
private void Update()
{
// Check to see if the spatial mapping data has been processed
// and if we are limiting how much time the user can spend scanning.
if (!meshesProcessed && limitScanningByTime)
{
// If we have not processed the spatial mapping data
// and scanning time is limited...
// Check to see if enough scanning time has passed
// since starting the observer.
if (limitScanningByTime && ((Time.time - SpatialMappingManager.Instance.StartTime) < scanTime))
{
// If we have a limited scanning time, then we should wait until
// enough time has passed before processing the mesh.
}
else
{
// The user should be done scanning their environment,
// so start processing the spatial mapping data...
/* TODO: 3.a DEVELOPER CODING EXERCISE 3.a */
// 3.a: Check if IsObserverRunning() is true on the
// SpatialMappingManager.Instance.
if(SpatialMappingManager.Instance.IsObserverRunning())
{
// 3.a: If running, Stop the observer by calling
// StopObserver() on the SpatialMappingManager.Instance.
SpatialMappingManager.Instance.StopObserver();
}
// 3.a: Call CreatePlanes() to generate planes.
CreatePlanes();
// 3.a: Set meshesProcessed to true.
meshesProcessed = true;
}
}
}
/// <summary>
/// Handler for the SurfaceMeshesToPlanes MakePlanesComplete event.
/// </summary>
/// <param name="source">Source of the event.</param>
/// <param name="args">Args for the event.</param>
private void SurfaceMeshesToPlanes_MakePlanesComplete(object source, System.EventArgs args)
{
/* TODO: 3.a DEVELOPER CODING EXERCISE 3.a */
// Collection of floor and table planes that we can use to set horizontal items on.
List<GameObject> horizontal = new List<GameObject>();
// Collection of wall planes that we can use to set vertical items on.
List<GameObject> vertical = new List<GameObject>();
// 3.a: Get all floor and table planes by calling
// SurfaceMeshesToPlanes.Instance.GetActivePlanes().
// Assign the result to the 'horizontal' list.
horizontal = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Table | PlaneTypes.Floor);
// 3.a: Get all wall planes by calling
// SurfaceMeshesToPlanes.Instance.GetActivePlanes().
// Assign the result to the 'vertical' list.
vertical = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Wall);
// Check to see if we have enough horizontal planes (minimumFloors)
// and vertical planes (minimumWalls), to set holograms on in the world.
if (horizontal.Count >= minimumFloors && vertical.Count >= minimumWalls)
{
// We have enough floors and walls to place our holograms on...
// 3.a: Let's reduce our triangle count by removing triangles
// from SpatialMapping meshes that intersect with our active planes.
// Call RemoveVertices().
// Pass in all activePlanes found by SurfaceMeshesToPlanes.Instance.
RemoveVertices(SurfaceMeshesToPlanes.Instance.ActivePlanes);
// 3.a: We can indicate to the user that scanning is over by
// changing the material applied to the Spatial Mapping meshes.
// Call SpatialMappingManager.Instance.SetSurfaceMaterial().
// Pass in the secondaryMaterial.
SpatialMappingManager.Instance.SetSurfaceMaterial(secondaryMaterial);
// 3.a: We are all done processing the mesh, so we can now
// initialize a collection of Placeable holograms in the world
// and use horizontal/vertical planes to set their starting positions.
// Call SpaceCollectionManager.Instance.GenerateItemsInWorld().
// Pass in the lists of horizontal and vertical planes that we found earlier.
SpaceCollectionManager.Instance.GenerateItemsInWorld(horizontal, vertical);
}
else
{
// We do not have enough floors/walls to place our holograms on...
// 3.a: Re-enter scanning mode so the user can find more surfaces by
// calling StartObserver() on the SpatialMappingManager.Instance.
SpatialMappingManager.Instance.StartObserver();
// 3.a: Re-process spatial data after scanning completes by
// re-setting meshesProcessed to false.
meshesProcessed = false;
}
}
/// <summary>
/// Creates planes from the spatial mapping surfaces.
/// </summary>
private void CreatePlanes()
{
// Generate planes based on the spatial map.
SurfaceMeshesToPlanes surfaceToPlanes = SurfaceMeshesToPlanes.Instance;
if (surfaceToPlanes != null && surfaceToPlanes.enabled)
{
surfaceToPlanes.MakePlanes();
}
}
/// <summary>
/// Removes triangles from the spatial mapping surfaces.
/// </summary>
/// <param name="boundingObjects"></param>
private void RemoveVertices(IEnumerable<GameObject> boundingObjects)
{
RemoveSurfaceVertices removeVerts = RemoveSurfaceVertices.Instance;
if (removeVerts != null && removeVerts.enabled)
{
removeVerts.RemoveSurfaceVerticesWithinBounds(boundingObjects);
}
}
/// <summary>
/// Called when the GameObject is unloaded.
/// </summary>
private void OnDestroy()
{
if (SurfaceMeshesToPlanes.Instance != null)
{
SurfaceMeshesToPlanes.Instance.MakePlanesComplete -= SurfaceMeshesToPlanes_MakePlanesComplete;
}
}
}
Buildelés és üzembe helyezés
- Mielőtt üzembe helyeznénk a HoloLensben, nyomja le a Lejátszás gombot a Unityben a lejátszási módba való belépéshez.
- Miután betöltötte a helyiséghálót a fájlból, várjon 10 másodpercet, mielőtt a feldolgozás elindul a térbeli leképezési hálón.
- Ha a feldolgozás befejeződött, a síkok a padlót, falakat, mennyezetet stb. képviselik.
- Miután az összes síkot megtalálták, látnia kell egy naprendszert a kamera közelében lévő padlón.
- Két plakátnak kell megjelennie a kamerák közelében lévő falakon is. Váltson a Jelenet lapra, ha nem látja őket Játék módban.
- Nyomja le ismét a Lejátszás gombot a lejátszási módból való kilépéshez.
- A szokásos módon hozza létre és helyezze üzembe a HoloLensben.
- Várjon, amíg befejeződik a térbeli leképezési adatok vizsgálata és feldolgozása.
- Ha látta repülők, próbálja megtalálni a naprendszer és plakátok a világon.
4. fejezet – Elhelyezés
Célkitűzések
- Állapítsa meg, hogy a hologram elfér-e a felületen.
- Visszajelzés küldése a felhasználónak, ha egy hologram elfér egy felületen.
Utasítások
- A Unity Hierarchy paneljén válassza ki a SpatialProcessing objektumot.
- Az Inspector panelen keresse meg a Surface Meshes To Planes (Szkript) összetevőt.
- A Kijelölés törléséhez módosítsa a Síkok rajzolása tulajdonságot Semmire .
- Módosítsa a Rajzsíkok tulajdonságot Falra, hogy csak a falsíkok jelenjenek meg.
- A Project panel Scripts mappájában kattintson duplán a Placeable.cs fájlra a Visual Studióban való megnyitásához.
A Placeable szkript már hozzá van csatolva a síktalálat-keresés befejezése után létrehozott plakátokhoz és kivetítőmezőkhöz. Mindössze annyit kell tennünk, hogy feloldunk néhány kódot, és ez a szkript a következőket fogja elérni:
- Állapítsa meg, hogy a hologram elfér-e egy felületen a határoló kocka középpontjából és négy sarkából származó sugárszórással.
- Ellenőrizze a normál felületet annak megállapításához, hogy elég sima-e ahhoz, hogy a hologram leüljön.
- Határoló kockát jeleníthet meg a hologram körül, hogy megjelenítse a tényleges méretét az elhelyezés során.
- Árnyékot vetítsen a hologram alá/mögé, hogy megmutassa, hol lesz elhelyezve a padlóra/falra.
- Az árnyék megjelenítése piros színnel, ha a hologram nem helyezhető a felszínre, vagy zöld színnel, ha lehet.
- Irányítsa újra a hologramot, hogy igazodjon ahhoz a felülettípushoz (függőleges vagy vízszintes), amelyhez affinitás van.
- Zökkenőmentesen helyezze a hologramot a kijelölt felületre, hogy elkerülje az ugrási vagy dokkolási viselkedést.
Az alábbi kódolási gyakorlatban törölje az összes kód megjegyzését, vagy használja ezt a befejezett megoldást a Placeable.cs fájlban:
using System.Collections.Generic;
using UnityEngine;
using Academy.HoloToolkit.Unity;
/// <summary>
/// Enumeration containing the surfaces on which a GameObject
/// can be placed. For simplicity of this sample, only one
/// surface type is allowed to be selected.
/// </summary>
public enum PlacementSurfaces
{
// Horizontal surface with an upward pointing normal.
Horizontal = 1,
// Vertical surface with a normal facing the user.
Vertical = 2,
}
/// <summary>
/// The Placeable class implements the logic used to determine if a GameObject
/// can be placed on a target surface. Constraints for placement include:
/// * No part of the GameObject's box collider impacts with another object in the scene
/// * The object lays flat (within specified tolerances) against the surface
/// * The object would not fall off of the surface if gravity were enabled.
/// This class also provides the following visualizations.
/// * A transparent cube representing the object's box collider.
/// * Shadow on the target surface indicating whether or not placement is valid.
/// </summary>
public class Placeable : MonoBehaviour
{
[Tooltip("The base material used to render the bounds asset when placement is allowed.")]
public Material PlaceableBoundsMaterial = null;
[Tooltip("The base material used to render the bounds asset when placement is not allowed.")]
public Material NotPlaceableBoundsMaterial = null;
[Tooltip("The material used to render the placement shadow when placement it allowed.")]
public Material PlaceableShadowMaterial = null;
[Tooltip("The material used to render the placement shadow when placement it not allowed.")]
public Material NotPlaceableShadowMaterial = null;
[Tooltip("The type of surface on which the object can be placed.")]
public PlacementSurfaces PlacementSurface = PlacementSurfaces.Horizontal;
[Tooltip("The child object(s) to hide during placement.")]
public List<GameObject> ChildrenToHide = new List<GameObject>();
/// <summary>
/// Indicates if the object is in the process of being placed.
/// </summary>
public bool IsPlacing { get; private set; }
// The most recent distance to the surface. This is used to
// locate the object when the user's gaze does not intersect
// with the Spatial Mapping mesh.
private float lastDistance = 2.0f;
// The distance away from the target surface that the object should hover prior while being placed.
private float hoverDistance = 0.15f;
// Threshold (the closer to 0, the stricter the standard) used to determine if a surface is flat.
private float distanceThreshold = 0.02f;
// Threshold (the closer to 1, the stricter the standard) used to determine if a surface is vertical.
private float upNormalThreshold = 0.9f;
// Maximum distance, from the object, that placement is allowed.
// This is used when raycasting to see if the object is near a placeable surface.
private float maximumPlacementDistance = 5.0f;
// Speed (1.0 being fastest) at which the object settles to the surface upon placement.
private float placementVelocity = 0.06f;
// Indicates whether or not this script manages the object's box collider.
private bool managingBoxCollider = false;
// The box collider used to determine of the object will fit in the desired location.
// It is also used to size the bounding cube.
private BoxCollider boxCollider = null;
// Visible asset used to show the dimensions of the object. This asset is sized
// using the box collider's bounds.
private GameObject boundsAsset = null;
// Visible asset used to show the where the object is attempting to be placed.
// This asset is sized using the box collider's bounds.
private GameObject shadowAsset = null;
// The location at which the object will be placed.
private Vector3 targetPosition;
/// <summary>
/// Called when the GameObject is created.
/// </summary>
private void Awake()
{
targetPosition = gameObject.transform.position;
// Get the object's collider.
boxCollider = gameObject.GetComponent<BoxCollider>();
if (boxCollider == null)
{
// The object does not have a collider, create one and remember that
// we are managing it.
managingBoxCollider = true;
boxCollider = gameObject.AddComponent<BoxCollider>();
boxCollider.enabled = false;
}
// Create the object that will be used to indicate the bounds of the GameObject.
boundsAsset = GameObject.CreatePrimitive(PrimitiveType.Cube);
boundsAsset.transform.parent = gameObject.transform;
boundsAsset.SetActive(false);
// Create a object that will be used as a shadow.
shadowAsset = GameObject.CreatePrimitive(PrimitiveType.Quad);
shadowAsset.transform.parent = gameObject.transform;
shadowAsset.SetActive(false);
}
/// <summary>
/// Called when our object is selected. Generally called by
/// a gesture management component.
/// </summary>
public void OnSelect()
{
/* TODO: 4.a CODE ALONG 4.a */
if (!IsPlacing)
{
OnPlacementStart();
}
else
{
OnPlacementStop();
}
}
/// <summary>
/// Called once per frame.
/// </summary>
private void Update()
{
/* TODO: 4.a CODE ALONG 4.a */
if (IsPlacing)
{
// Move the object.
Move();
// Set the visual elements.
Vector3 targetPosition;
Vector3 surfaceNormal;
bool canBePlaced = ValidatePlacement(out targetPosition, out surfaceNormal);
DisplayBounds(canBePlaced);
DisplayShadow(targetPosition, surfaceNormal, canBePlaced);
}
else
{
// Disable the visual elements.
boundsAsset.SetActive(false);
shadowAsset.SetActive(false);
// Gracefully place the object on the target surface.
float dist = (gameObject.transform.position - targetPosition).magnitude;
if (dist > 0)
{
gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, targetPosition, placementVelocity / dist);
}
else
{
// Unhide the child object(s) to make placement easier.
for (int i = 0; i < ChildrenToHide.Count; i++)
{
ChildrenToHide[i].SetActive(true);
}
}
}
}
/// <summary>
/// Verify whether or not the object can be placed.
/// </summary>
/// <param name="position">
/// The target position on the surface.
/// </param>
/// <param name="surfaceNormal">
/// The normal of the surface on which the object is to be placed.
/// </param>
/// <returns>
/// True if the target position is valid for placing the object, otherwise false.
/// </returns>
private bool ValidatePlacement(out Vector3 position, out Vector3 surfaceNormal)
{
Vector3 raycastDirection = gameObject.transform.forward;
if (PlacementSurface == PlacementSurfaces.Horizontal)
{
// Placing on horizontal surfaces.
// Raycast from the bottom face of the box collider.
raycastDirection = -(Vector3.up);
}
// Initialize out parameters.
position = Vector3.zero;
surfaceNormal = Vector3.zero;
Vector3[] facePoints = GetColliderFacePoints();
// The origin points we receive are in local space and we
// need to raycast in world space.
for (int i = 0; i < facePoints.Length; i++)
{
facePoints[i] = gameObject.transform.TransformVector(facePoints[i]) + gameObject.transform.position;
}
// Cast a ray from the center of the box collider face to the surface.
RaycastHit centerHit;
if (!Physics.Raycast(facePoints[0],
raycastDirection,
out centerHit,
maximumPlacementDistance,
SpatialMappingManager.Instance.LayerMask))
{
// If the ray failed to hit the surface, we are done.
return false;
}
// We have found a surface. Set position and surfaceNormal.
position = centerHit.point;
surfaceNormal = centerHit.normal;
// Cast a ray from the corners of the box collider face to the surface.
for (int i = 1; i < facePoints.Length; i++)
{
RaycastHit hitInfo;
if (Physics.Raycast(facePoints[i],
raycastDirection,
out hitInfo,
maximumPlacementDistance,
SpatialMappingManager.Instance.LayerMask))
{
// To be a valid placement location, each of the corners must have a similar
// enough distance to the surface as the center point
if (!IsEquivalentDistance(centerHit.distance, hitInfo.distance))
{
return false;
}
}
else
{
// The raycast failed to intersect with the target layer.
return false;
}
}
return true;
}
/// <summary>
/// Determine the coordinates, in local space, of the box collider face that
/// will be placed against the target surface.
/// </summary>
/// <returns>
/// Vector3 array with the center point of the face at index 0.
/// </returns>
private Vector3[] GetColliderFacePoints()
{
// Get the collider extents.
// The size values are twice the extents.
Vector3 extents = boxCollider.size / 2;
// Calculate the min and max values for each coordinate.
float minX = boxCollider.center.x - extents.x;
float maxX = boxCollider.center.x + extents.x;
float minY = boxCollider.center.y - extents.y;
float maxY = boxCollider.center.y + extents.y;
float minZ = boxCollider.center.z - extents.z;
float maxZ = boxCollider.center.z + extents.z;
Vector3 center;
Vector3 corner0;
Vector3 corner1;
Vector3 corner2;
Vector3 corner3;
if (PlacementSurface == PlacementSurfaces.Horizontal)
{
// Placing on horizontal surfaces.
center = new Vector3(boxCollider.center.x, minY, boxCollider.center.z);
corner0 = new Vector3(minX, minY, minZ);
corner1 = new Vector3(minX, minY, maxZ);
corner2 = new Vector3(maxX, minY, minZ);
corner3 = new Vector3(maxX, minY, maxZ);
}
else
{
// Placing on vertical surfaces.
center = new Vector3(boxCollider.center.x, boxCollider.center.y, maxZ);
corner0 = new Vector3(minX, minY, maxZ);
corner1 = new Vector3(minX, maxY, maxZ);
corner2 = new Vector3(maxX, minY, maxZ);
corner3 = new Vector3(maxX, maxY, maxZ);
}
return new Vector3[] { center, corner0, corner1, corner2, corner3 };
}
/// <summary>
/// Put the object into placement mode.
/// </summary>
public void OnPlacementStart()
{
// If we are managing the collider, enable it.
if (managingBoxCollider)
{
boxCollider.enabled = true;
}
// Hide the child object(s) to make placement easier.
for (int i = 0; i < ChildrenToHide.Count; i++)
{
ChildrenToHide[i].SetActive(false);
}
// Tell the gesture manager that it is to assume
// all input is to be given to this object.
GestureManager.Instance.OverrideFocusedObject = gameObject;
// Enter placement mode.
IsPlacing = true;
}
/// <summary>
/// Take the object out of placement mode.
/// </summary>
/// <remarks>
/// This method will leave the object in placement mode if called while
/// the object is in an invalid location. To determine whether or not
/// the object has been placed, check the value of the IsPlacing property.
/// </remarks>
public void OnPlacementStop()
{
// ValidatePlacement requires a normal as an out parameter.
Vector3 position;
Vector3 surfaceNormal;
// Check to see if we can exit placement mode.
if (!ValidatePlacement(out position, out surfaceNormal))
{
return;
}
// The object is allowed to be placed.
// We are placing at a small buffer away from the surface.
targetPosition = position + (0.01f * surfaceNormal);
OrientObject(true, surfaceNormal);
// If we are managing the collider, disable it.
if (managingBoxCollider)
{
boxCollider.enabled = false;
}
// Tell the gesture manager that it is to resume
// its normal behavior.
GestureManager.Instance.OverrideFocusedObject = null;
// Exit placement mode.
IsPlacing = false;
}
/// <summary>
/// Positions the object along the surface toward which the user is gazing.
/// </summary>
/// <remarks>
/// If the user's gaze does not intersect with a surface, the object
/// will remain at the most recently calculated distance.
/// </remarks>
private void Move()
{
Vector3 moveTo = gameObject.transform.position;
Vector3 surfaceNormal = Vector3.zero;
RaycastHit hitInfo;
bool hit = Physics.Raycast(Camera.main.transform.position,
Camera.main.transform.forward,
out hitInfo,
20f,
SpatialMappingManager.Instance.LayerMask);
if (hit)
{
float offsetDistance = hoverDistance;
// Place the object a small distance away from the surface while keeping
// the object from going behind the user.
if (hitInfo.distance <= hoverDistance)
{
offsetDistance = 0f;
}
moveTo = hitInfo.point + (offsetDistance * hitInfo.normal);
lastDistance = hitInfo.distance;
surfaceNormal = hitInfo.normal;
}
else
{
// The raycast failed to hit a surface. In this case, keep the object at the distance of the last
// intersected surface.
moveTo = Camera.main.transform.position + (Camera.main.transform.forward * lastDistance);
}
// Follow the user's gaze.
float dist = Mathf.Abs((gameObject.transform.position - moveTo).magnitude);
gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, moveTo, placementVelocity / dist);
// Orient the object.
// We are using the return value from Physics.Raycast to instruct
// the OrientObject function to align to the vertical surface if appropriate.
OrientObject(hit, surfaceNormal);
}
/// <summary>
/// Orients the object so that it faces the user.
/// </summary>
/// <param name="alignToVerticalSurface">
/// If true and the object is to be placed on a vertical surface,
/// orient parallel to the target surface. If false, orient the object
/// to face the user.
/// </param>
/// <param name="surfaceNormal">
/// The target surface's normal vector.
/// </param>
/// <remarks>
/// The alignToVerticalSurface parameter is ignored if the object
/// is to be placed on a horizontalSurface
/// </remarks>
private void OrientObject(bool alignToVerticalSurface, Vector3 surfaceNormal)
{
Quaternion rotation = Camera.main.transform.localRotation;
// If the user's gaze does not intersect with the Spatial Mapping mesh,
// orient the object towards the user.
if (alignToVerticalSurface && (PlacementSurface == PlacementSurfaces.Vertical))
{
// We are placing on a vertical surface.
// If the normal of the Spatial Mapping mesh indicates that the
// surface is vertical, orient parallel to the surface.
if (Mathf.Abs(surfaceNormal.y) <= (1 - upNormalThreshold))
{
rotation = Quaternion.LookRotation(-surfaceNormal, Vector3.up);
}
}
else
{
rotation.x = 0f;
rotation.z = 0f;
}
gameObject.transform.rotation = rotation;
}
/// <summary>
/// Displays the bounds asset.
/// </summary>
/// <param name="canBePlaced">
/// Specifies if the object is in a valid placement location.
/// </param>
private void DisplayBounds(bool canBePlaced)
{
// Ensure the bounds asset is sized and positioned correctly.
boundsAsset.transform.localPosition = boxCollider.center;
boundsAsset.transform.localScale = boxCollider.size;
boundsAsset.transform.rotation = gameObject.transform.rotation;
// Apply the appropriate material.
if (canBePlaced)
{
boundsAsset.GetComponent<Renderer>().sharedMaterial = PlaceableBoundsMaterial;
}
else
{
boundsAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableBoundsMaterial;
}
// Show the bounds asset.
boundsAsset.SetActive(true);
}
/// <summary>
/// Displays the placement shadow asset.
/// </summary>
/// <param name="position">
/// The position at which to place the shadow asset.
/// </param>
/// <param name="surfaceNormal">
/// The normal of the surface on which the asset will be placed
/// </param>
/// <param name="canBePlaced">
/// Specifies if the object is in a valid placement location.
/// </param>
private void DisplayShadow(Vector3 position,
Vector3 surfaceNormal,
bool canBePlaced)
{
// Rotate and scale the shadow so that it is displayed on the correct surface and matches the object.
float rotationX = 0.0f;
if (PlacementSurface == PlacementSurfaces.Horizontal)
{
rotationX = 90.0f;
shadowAsset.transform.localScale = new Vector3(boxCollider.size.x, boxCollider.size.z, 1);
}
else
{
shadowAsset.transform.localScale = boxCollider.size;
}
Quaternion rotation = Quaternion.Euler(rotationX, gameObject.transform.rotation.eulerAngles.y, 0);
shadowAsset.transform.rotation = rotation;
// Apply the appropriate material.
if (canBePlaced)
{
shadowAsset.GetComponent<Renderer>().sharedMaterial = PlaceableShadowMaterial;
}
else
{
shadowAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableShadowMaterial;
}
// Show the shadow asset as appropriate.
if (position != Vector3.zero)
{
// Position the shadow a small distance from the target surface, along the normal.
shadowAsset.transform.position = position + (0.01f * surfaceNormal);
shadowAsset.SetActive(true);
}
else
{
shadowAsset.SetActive(false);
}
}
/// <summary>
/// Determines if two distance values should be considered equivalent.
/// </summary>
/// <param name="d1">
/// Distance to compare.
/// </param>
/// <param name="d2">
/// Distance to compare.
/// </param>
/// <returns>
/// True if the distances are within the desired tolerance, otherwise false.
/// </returns>
private bool IsEquivalentDistance(float d1, float d2)
{
float dist = Mathf.Abs(d1 - d2);
return (dist <= distanceThreshold);
}
/// <summary>
/// Called when the GameObject is unloaded.
/// </summary>
private void OnDestroy()
{
// Unload objects we have created.
Destroy(boundsAsset);
boundsAsset = null;
Destroy(shadowAsset);
shadowAsset = null;
}
}
Buildelés és üzembe helyezés
- A korábbiakhoz hasonlóan hozza létre a projektet, és helyezze üzembe a HoloLensben.
- Várjon, amíg befejeződik a térbeli leképezési adatok vizsgálata és feldolgozása.
- Amikor látja a naprendszert, nézze meg az alábbi vetítési mezőt, és válasszon egy kézmozdulatot a mozgáshoz. Amíg a vetítési mező ki van jelölve, egy határoló kocka jelenik meg a vetítési mező körül.
- Vigye a fejét, hogy nézzen egy másik helyen a szobában. A vetítési mezőnek követnie kell a tekintetét. Ha a vetítési mező alatti árnyék vörösre változik, a hologram nem helyezhető el ezen a felületen. Ha a vetítési mező alatti árnyék zöld színűre változik, a hologramot egy másik kijelölési kézmozdulat végrehajtásával helyezheti el.
- Keresse meg és válassza ki az egyik holografikus plakátokat a falon, hogy áthelyezi azt egy új helyre. Figyelje meg, hogy a plakátot nem helyezheti el a padlóra vagy a mennyezetre, és hogy megfelelően igazodik az egyes falakhoz, miközben mozog.
5. fejezet – Elzáródás
Célkitűzések
- Állapítsa meg, hogy a térbeli leképezési háló elzárja-e a hologramot.
- Alkalmazzon különböző elzáródási technikákat a szórakoztató hatás eléréséhez.
Utasítások
Először is engedélyezni fogjuk, hogy a térbeli leképezési háló elzárjon más hologramokat anélkül, hogy elzárja a valós világot:
- A Hierarchia panelen válassza ki a SpatialProcessing objektumot.
- Az Inspector panelen keresse meg a Play Space Manager (Szkript) összetevőt.
- Kattintson a Másodlagos anyag tulajdonságtól jobbra lévő körre.
- Keresse meg és válassza ki az Occlusion anyagot, és zárja be az ablakot.
Ezután egy speciális viselkedést adunk hozzá a Földhöz, hogy kék kiemeléssel rendelkezzen, amikor egy másik hologram (például a nap) vagy a térbeli leképezési háló elzárja:
- A Projekt panel Hologramok mappájában bontsa ki a SolarSystem objektumot.
- Kattintson a Földre.
- Az Inspector panelen keresse meg a Föld anyagát (alsó összetevő).
- A Shader legördülő menüben módosítsa az árnyékolót Custom OcclusionRim (Egyéni > occlusionRim) beállításra. Ez kék kiemelést jelenít meg a Föld körül, amikor egy másik objektum elzárja.
Végül, lehetővé fogjuk tenni a naprendszerünk bolygói számára a röntgen-látást. A következők eléréséhez szerkeszteni kell a PlanetOcclusion.cs fájlt (amely a Scripts\SolarSystem mappában található):
- Állapítsa meg, hogy egy bolygót elzárt-e a SpatialMapping réteg (szobahálók és síkok).
- Megjelenítheti egy bolygó drótvázképét, amikor azt a SpatialMapping réteg elzárja.
- Rejtse el egy bolygó drótvázképét, ha azt nem blokkolja a SpatialMapping réteg.
Kövesse a PlanetOcclusion.cs fájl kódolási gyakorlatát, vagy használja a következő megoldást:
using UnityEngine;
using Academy.HoloToolkit.Unity;
/// <summary>
/// Determines when the occluded version of the planet should be visible.
/// This script allows us to do selective occlusion, so the occlusionObject
/// will only be rendered when a Spatial Mapping surface is occluding the planet,
/// not when another hologram is responsible for the occlusion.
/// </summary>
public class PlanetOcclusion : MonoBehaviour
{
[Tooltip("Object to display when the planet is occluded.")]
public GameObject occlusionObject;
/// <summary>
/// Points to raycast to when checking for occlusion.
/// </summary>
private Vector3[] checkPoints;
// Use this for initialization
void Start()
{
occlusionObject.SetActive(false);
// Set the check points to use when testing for occlusion.
MeshFilter filter = gameObject.GetComponent<MeshFilter>();
Vector3 extents = filter.mesh.bounds.extents;
Vector3 center = filter.mesh.bounds.center;
Vector3 top = new Vector3(center.x, center.y + extents.y, center.z);
Vector3 left = new Vector3(center.x - extents.x, center.y, center.z);
Vector3 right = new Vector3(center.x + extents.x, center.y, center.z);
Vector3 bottom = new Vector3(center.x, center.y - extents.y, center.z);
checkPoints = new Vector3[] { center, top, left, right, bottom };
}
// Update is called once per frame
void Update()
{
/* TODO: 5.a DEVELOPER CODING EXERCISE 5.a */
// Check to see if any of the planet's boundary points are occluded.
for (int i = 0; i < checkPoints.Length; i++)
{
// 5.a: Convert the current checkPoint to world coordinates.
// Call gameObject.transform.TransformPoint(checkPoints[i]).
// Assign the result to a new Vector3 variable called 'checkPt'.
Vector3 checkPt = gameObject.transform.TransformPoint(checkPoints[i]);
// 5.a: Call Vector3.Distance() to calculate the distance
// between the Main Camera's position and 'checkPt'.
// Assign the result to a new float variable called 'distance'.
float distance = Vector3.Distance(Camera.main.transform.position, checkPt);
// 5.a: Take 'checkPt' and subtract the Main Camera's position from it.
// Assign the result to a new Vector3 variable called 'direction'.
Vector3 direction = checkPt - Camera.main.transform.position;
// Used to indicate if the call to Physics.Raycast() was successful.
bool raycastHit = false;
// 5.a: Check if the planet is occluded by a spatial mapping surface.
// Call Physics.Raycast() with the following arguments:
// - Pass in the Main Camera's position as the origin.
// - Pass in 'direction' for the direction.
// - Pass in 'distance' for the maxDistance.
// - Pass in SpatialMappingManager.Instance.LayerMask as layerMask.
// Assign the result to 'raycastHit'.
raycastHit = Physics.Raycast(Camera.main.transform.position, direction, distance, SpatialMappingManager.Instance.LayerMask);
if (raycastHit)
{
// 5.a: Our raycast hit a surface, so the planet is occluded.
// Set the occlusionObject to active.
occlusionObject.SetActive(true);
// At least one point is occluded, so break from the loop.
break;
}
else
{
// 5.a: The Raycast did not hit, so the planet is not occluded.
// Deactivate the occlusionObject.
occlusionObject.SetActive(false);
}
}
}
}
Buildelés és üzembe helyezés
- A szokásos módon hozza létre és helyezze üzembe az alkalmazást a HoloLensben.
- Várjon, amíg a térbeli leképezési adatok vizsgálata és feldolgozása befejeződik (a falakon kék vonalaknak kell megjelennie).
- Keresse meg és válassza ki a naprendszer kivetítő dobozát, majd állítsa be a dobozt egy fal vagy egy számláló mögött.
- Az alapszintű eltömődést úgy tekintheti meg, ha a plakáton vagy a vetítőkereten lévő felületek mögé bújik.
- Keresse meg a Földet, kék kiemelési hatásnak kell lennie, amikor egy másik hologram vagy felület mögé kerül.
- Figyelje meg, ahogy a bolygók a fal mögött vagy a szoba más felületei mögött mozognak. Most már röntgenlátása van, és láthatja a drótvázváz vázát!
Vége
Gratulálunk! Ezzel befejezte az MR Spatial 230: Spatial mapping (Térbeli leképezés) című szakaszt.
- Tudja, hogyan vizsgálhatja a környezetet, és tölthet be térbeli leképezési adatokat a Unitybe.
- Megismerheti az árnyékolók alapjait, és hogy az anyagok hogyan használhatók fel a világ újramegjelenítésére.
- Megismerkedett a síkok keresésének és a háromszögek hálóból való eltávolításának új feldolgozási technikáival.
- A hologramok mozgatása és elhelyezése értelmes felületeken történt.
- Különböző elzáródási technikákat tapasztalt, és kihasználta a röntgenlátás erejét!