HoloLens (1. generációs) Spatial 230: Spatial Mapping

Fontos

Az Mixed Reality Academy oktatóanyagai az HoloLens (1. generációs), a Unity 2017 és Mixed Reality Modern headsetek szem előtt tartva voltak megtervezve. Ezért fontos, hogy ezeket az oktatóanyagokat a helyére helyezzük azoknak a fejlesztőknek, akik továbbra is útmutatásra van szüksége az eszközök fejlesztéséhez. Ezek az oktatóanyagok nem frissülnek a HoloLens 2-hez használt legújabb eszközkészletekkel vagy interakciókkal, és nem biztos, hogy kompatibilisek a Unity újabb verzióival. A rendszer fenntartja őket, hogy továbbra is a támogatott eszközökön dolgoznak. A 2. HoloLens új oktatóanyag-sorozat lett közzétéve.

A térbeli leképezés a való világot és a virtuális világot egyesíti azáltal, hogy a környezetről tanít hologramokat. Az MR Spatial 230 (Project Planetaemet) a következővel ismerkedünk meg:

  • Vizsgálja meg a környezetet, és továbbítsa az HoloLens adatokat a fejlesztői gépre.
  • Ismerkedés az árnyékolókkal, és megtudhatja, hogyan használhatja őket a tér megjelenítésére.
  • A helyiség hálóját hálófeldolgozással, egyszerű síkra kell lebontani.
  • Az MR Basics 101 (Alapvető eljárások) 101-benelsajátított elhelyezési technikákon túl adjon visszajelzést arról, hogy hol helyezhető el a környezetben egy hologram.
  • Ismerje meg az eltakaródás hatásait, így ha a hologram egy valós objektum mögött van, akkor is látható lesz a x-ray visionben!

Eszköztámogatás

Tanfolyam HoloLens Modern headsetek
MR Spatial 230: Térbeli leképezés ✔️

Előkészületek

Előfeltételek

Project fájlok

  • Töltse le a projekthez szükséges fájlokat. Unity 2017.2-es vagy újabb szükséges.
    • Ha továbbra is unity 5.6-támogatásra van szüksége, 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.
  • Archiválja a fájlokat az asztalra vagy más könnyen elérhető helyre.

Megjegyzés

Ha a letöltés előtt át szeretné nézni a forráskódot, az a következő GitHub.

Jegyzetek

  • Az "Saját kód engedélyezése" Visual Studio le kell tiltani(nincsbejelölve) az Eszközök beállításai hibakeresése alatt, hogy töréspontokat ássunk a > kódban.

Unity beállítása

  • Indítsa el a Unityt.
  • Új projekt létrehozásához válassza az Új lehetőséget.
  • A projektnek a Planetaemet nevezze el.
  • Ellenőrizze, hogy a 3D beállítás van-e kiválasztva.
  • Kattintson a Create Project (Projekt létrehozása) elemre.
  • Miután a Unity elindul, kattintson az Edit Project Gépház Player > (Lejátszó szerkesztése) elemre.
  • Az Inspector panelen keresse meg és válassza ki a Windows ikont.
  • Bontsa ki az Egyéb Gépház bontsa ki.
  • A Renderelés szakaszban jelölje be a Virtual Reality támogatott beállítását.
  • Ellenőrizze, Windows holographic rendszer megjelenik-e a Virtual Reality-adattitkok listájában. Ha nem, kattintson a lista alján található gombra, és válassza a ++
  • Bontsa ki a Közzététel Gépház.
  • A Képességek szakaszban ellenőrizze a következő beállításokat:
    • InternetClientServer
    • PrivateNetworkClientServer
    • Mikrofon
    • SpatialPerception (Térbeli pont)
  • Ugrás az Edit Project Gépház Quality (Project Gépház > szerkesztésére)
  • Az Inspector panel áruház ikonjának Windows alatt válassza az "Alapértelmezett" sor alatti fekete legördülő nyilat, és módosítsa az alapértelmezett beállítást Nagyon alacsonyra.
  • Az Assets Import Package Custom Package (Eszközök importálása) > Custom Package (Egyéni csomag importálása) gombra.
  • Lépjen a ...\HolographicAcademy-Hologramok-230-SpatialMapping\Starting mappába.
  • Kattintson a Planetapack.unitypackage elemre.
  • Kattintson a Megnyitás gombra.
  • Meg kell jelenni egy Import Unity Package (Import Unity-csomag) ablaknak, és kattintson az Import (Importálás) gombra.
  • Várjon, amíg a Unity importálja a projekt befejezéséhez szükséges összes eszközt.
  • A Hierarchy (Hierarchia) panelen törölje a Main Camera (Fő kamera) adatokat.
  • A ProjectHoloToolkit-SpatialMapping-230\Utilities\Prefabs mappában keresse meg a Main Camera objektumot.
  • Húzza a Main Camera (Fő kamera) előlapját a Hierarchy (Hierarchia) panelre.
  • A Hierarchy (Hierarchia) panelen törölje a Directional Light objektumot.
  • A Projectpanelen Hologramok a Kurzor objektumot.
  • Húzza & a & előfedőt a hierarchiába.
  • A Hierarchia panelen válassza a Kurzor objektumot.
  • Az Inspector panelen kattintson a Réteg legördülő gombra, és válassza a Rétegek szerkesztése... lehetőséget.
  • A 31. felhasználói rétegnek aSpatialMapping nevet kell adni.
  • Mentse az új jelenet: File Save Scene As...
  • Kattintson az Új mappa elemre, és nevezze el a mappát Scenes (Jelenetek) néven.
  • A fájlnak nevezze el a"Planetaemet",és mentse a Scenes mappába.

1. fejezet – Vizsgálat

Célkitűzések

  • Ismerje meg a SurfaceObservert, és hogy a beállításai milyen hatással vannak a felhasználói élményre és a teljesítményre.
  • Hozzon létre egy helyiségvizsgálati élményt a helyiség hálóinak gyűjtéséhez.

Utasítások

  • A ProjectHoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs mappában keresse meg a SpatialMapping előfab adatokat.
  • Húzza & a & előfást a Hierarchia panelre.

Build és üzembe helyezés (1. rész)

  • A Unityben válassza a File Build Gépház.
  • Kattintson az Add Open Scenes (Nyitott jelenetek hozzáadása) elemre a Planeta scene (Planetaáriumjelenet) buildhez való hozzáadásához.
  • A Platform listában válassza Windows Universal Windows Platform lehetőséget, majd kattintson a Switch Platform (Platformváltás) elemre.
  • Állítsa az SDK-t Univerzális 10-re,az UWP buildtípusátpedig D3D-re.
  • Tekintse meg a Unity C#-projekteket.
  • Kattintson a Build (Build) gombra.
  • Hozzon létre egy "App" nevű új mappát.
  • Kattintson az Alkalmazás mappára.
  • Kattintson a Mappa kiválasztása gombra.
  • Amikor a Unity felépítése kész, megjelenik Fájlkezelő ablak.
  • Kattintson duplán az Alkalmazás mappára annak megnyitásához.
  • Kattintson duplán a Planetanéz.sln fájlra a projekt betöltéséhez a Visual Studio.
  • A Visual Studio 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" elemtől jobbra található legördülő nyílra, és válassza a Távoli gép lehetőséget.
  • Adja meg az eszköz IP-címét a Cím mezőben, és módosítsa a Hitelesítési módot Univerzális (Titkosítatlan protokoll) beállításra.
  • Kattintson a Hibakeresés – Indítás hibakeresés nélkül lehetőségre, vagy nyomja le a Ctrl + F5 billentyűkombinációt.
  • A build és az üzembe helyezés állapotát a Visual Studio panelen figyelheti meg.
  • Az alkalmazás üzembe helyezése után járja körbe a helyiséget. Látni fogja a fekete és fehér vonalkeretes hálók által körülfedő felületeket.
  • Beolvassa a környezetet. Mindenképpen figyeljen a falra, a felső határra és az emeletre.

Build é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 Window Profiler lehetőséget.
  • Kattintson az Add Profiler GPU (Profilkészítő GPU hozzáadása) elemre.
  • Kattintson az Active Profiler elemre.
  • Adja meg a HoloLens.
  • Kattintson a Csatlakozás gombra.
  • Figyelje meg, hogy hány ezredmásodperc szükséges 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 Studio, és nyissa meg a SpatialMappingObserver.cs fájlját. Ezt a Assembly-CSharp (Universal Windows) projekt HoloToolkit\SpatialMapping mappájában találja.
  • Keresse meg az Új() 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 képkocka renderelésének ezredmásodpercben történt 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 helyiség hálóját, és töltsünk be a Unitybe.

  • Térjen vissza Visual Studio, és távolítsa el a TrianglesPerCubicMeter sort, amit az előző szakaszban az Ű() függvényben adott hozzá.
  • A projektet újra üzembe kell ásni az eszközön. Most már 500 háromszöget kell futtatnunk minden métermérőn.
  • Nyisson meg egy böngészőt, és írja be HoloLens IP-címbe a következőhöz való Windows Eszközportál.
  • Válassza a 3D View (3D nézet) lehetőséget a bal oldali panelen.
  • A Surface rekonstrukciója alatt válassza a Frissítés gombot.
  • Figyelje meg, hogy a beolvasott területek a HoloLens megjelennek a megjelenítési ablakban.
  • A helyiség vizsgálatának mentéshez kattintson a Mentés gombra.
  • Nyissa meg a Letöltések mappát, és keresse meg a mentett helyiség SRMesh.obj modelljét.
  • Másolja az 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 található körre.
  • Keresse meg és jelölje ki az SRMesh objektumot, majd zárja be az ablakot.
  • Ellenőrizze, hogy az Inspectorpanel Room Model (Helyiségmodell) tulajdonsága SRMesh (SRMesh) beállításra van-e állítva.
  • Nyomja le a Play gombot a Unity előnézeti módjának beállításhoz.
  • A SpatialMapping összetevő betölti a hálókat a mentett helyiség modelljéből, így használhatja őket a Unityben.
  • Váltson Jelenet nézetre, hogy az összes helyiségmodellt a wireframe árnyékolóval jelenítse meg.
  • Az előnézeti módból való kilépéshez nyomja le újra a Lejátszás gombot.

MEGJEGYZÉS: Amikor legközelebb előnézeti módba lép a Unityben, az alapértelmezés szerint betölti a mentett helyiség hálóját.

2. fejezet – Vizualizáció

Célkitűzések

  • Megismeri az árnyékolók alapjait.
  • A környezet vizualizációja.

Utasítások

  • A Unity Hierarchy panelen válassza ki a SpatialMapping objektumot.
  • Az Inspector panelen keresse meg a Spatial Mapping Manager (Szkript) összetevőt.
  • Kattintson a Surface Material tulajdonságtól jobbra található körre.
  • Keresse meg és válassza ki a BlueLinesOnWalls anyagot, és zárja be az ablakot.
  • A ProjectShaders mappában kattintson duplán a BlueLinesOnWalls mappára az árnyékoló megnyitásához a Visual Studio.
  • Ez egy egyszerű képpont (csúcstól töredékre) árnyékoló, amely a következő feladatokat képes elvégezni:
    1. Világtérgé alakítja a csúcsok helyét.
    2. Ellenőrzi a csúcspont normálértékét annak megállapításához, hogy a képpont függőleges-e.
    3. Beállítja a képpont színét a rendereléshez.

Build é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ég háló összes függőleges felületére megjelennek (amelyek automatikusan betöltődnek a mentett beolvasási adatokból).
  • Váltson a Scene (Jelenet) lapra, és állítsa be a helyiség nézetét, és nézze meg, hogyan jelenik meg a teljes helyiségháló a Unityben.
  • Az Project panelen keresse meg az Anyagok mappát, és válassza ki a BlueLinesOnWalls anyagot.
  • Módosíthat néhány tulajdonságot, és láthatja, 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 vastagabbnak vagy vékonyabbnak jelenjenek meg.
    • Az Inspector panelen módosítsa a LinesPerMeter értékét, hogy az egyes falakon hány sor jelenjen meg.
  • Az előnézeti módból való kilépéshez kattintson ismét a Lejátszás gombra.
  • Buildelje és telepítse a HoloLens, és figyelje meg, hogyan jelenik meg az árnyékoló renderelése a valódi felületeken.

A Unity remekül előnézeti anyagokat használ, de mindig jó ötlet a renderelés megtekintése az eszközön.

3. fejezet – Feldolgozás

Célkitűzések

  • Megismeri a térbeli leképezési adatok feldolgozásának módszereit az alkalmazásban való használatra.
  • Térbeli leképezési adatok elemzése síkok megkereséséhez és háromszögek eltávolításához.
  • Használjon síkokat a hologramok elhelyezéséhez.

Utasítások

  • A Unity Project panelenHologramok a SpatialProcessing objektumot.
  • Húzza & a & objektumot a Hierarchia panelre.

A SpatialProcessing prefab 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íkokkal fogjuk ábrázolni a falat, az emeletet és a felső határokat. Ez az előregyártott fájl tartalmazza a RemoveSurfaceVertices.cs-et is, amely eltávolíthatja a csúcspontokat a térbeli leképezési hálóból. Ezzel réseket hozhat létre a hálóban, vagy eltávolíthatja a felesleges háromszögeket, amelyekre már nincs szükség (mert a síkok használhatók helyette).

  • A Unity Project panelenHologramok a SpaceCollection objektumot.
  • Húzza a SpaceCollection objektumot a Hierarchy panelre.
  • A Hierarchia panelen válassza ki a SpatialProcessing objektumot.
  • Az Inspector panelen keresse meg a Play Space Manager (Script) összetevőt.
  • Kattintson duplán a PlaySpaceManager.cs fájlra annak megnyitásához a Visual Studio.

A PlaySpaceManager.cs alkalmazásspecifikus kódot tartalmaz. Ehhez a szkripthez funkciókkal fogjuk engedélyezni a következő működést:

  1. Állítsa le a térbeli leképezési adatok gyűjtését, miután túlléptük a vizsgálat időkorlátját (10 másodperc).
  2. A térbeli leképezési adatok feldolgozása:
    1. A SurfaceMeshesToPlanes használatával egyszerűbben ábrázolja a világot síkként (fal, emelet, felső határ stb.).
    2. A RemoveSurfaceVertices használatával eltávolíthatja a síkhatárok között található felületi háromszögeket.
  3. Hologramok gyűjteményének létrehozása a világon, és azok a felhasználó közelében lévő fal- és padlósíkra helyezése.

Töltse ki 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;
        }
    }
}

Build és üzembe helyezés

  • Mielőtt üzembe helyez a HoloLens, nyomja le a Play gombot a Unityben a play 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 elkezdődne a térbeli leképezési hálón.
  • Ha a feldolgozás befejeződött, a síkok a padlónak, a falnak, a felső határnak stb. jelennek meg.
  • Miután megtalálta az összes síkot, egy naprendszernek kell megjelennie a kamera közelében egy padlótáblán.
  • Két poszternek kell megjelennie a kamera közelében a falon. Váltson a Scene (Jelenet) lapra, ha nem látja őket Game módban.
  • A play módból való kilépéshez nyomja le újra a Lejátszás gombot.
  • Build és üzembe helyezés a HoloLens szokásos módon.
  • Várjon, amíg a térbeli leképezési adatok vizsgálata és feldolgozása befejeződik.
  • Ha látja a síkokat, próbálja megtalálni a naprendszert és a posztereket a világában.

4. fejezet – Elhelyezés

Célkitűzések

  • Állapítsa meg, hogy egy hologram elfér-e a felületen.
  • Visszajelzés küldése a felhasználónak, ha egy hologram elfér vagy nem fér el a felületen.

Utasítások

  • A Unity Hierarchy panelen válassza ki a SpatialProcessing objektumot.
  • Az Inspector panelen keresse meg a Surface Meshes To Planes (Script) összetevőt.
  • Módosítsa a Draw Planes (Sík rajzolása)tulajdonságot Nothing (Nincs) beállításra, hogy törölje a kijelölést.
  • Módosítsa a Draw Planes (Sík rajzolás) tulajdonságot Wall(Fal) tulajdonságra, hogy csak a falsíkok jelennek meg.
  • A ProjectScripts (Parancsfájlok) mappában kattintson duplán a Placeable.cs fájlra a Visual Studio.

A Placeable szkript már csatolva van a síkkeresés befejezése után létrehozott poszterekhez és kivetítőmezőkhöz. Mindössze le kell írnunk néhány kódot, és ez a szkript a következőket éri el:

  1. Állapítsa meg, hogy egy hologram elfér-e a felületen úgy, hogy a határoló kocka közepéről és négy sarka közül négy sarkú betekenyezést ad a felületre.
  2. Ellenőrizze a normál felületet, és állapítsa meg, hogy elég zökkenőmentes-e ahhoz, hogy a hologram a felszínen legyen.
  3. Határolókeretet renderel a hologram körül, hogy megjeleníthesse annak tényleges méretét, miközben el van helyezve.
  4. A hologram alá vagy mögé árnyékolása megmutatja, hogy hol lesz elhelyezve a padlóra/falra.
  5. Az árnyékot piros színnel renderelheti, ha a hologram nem helyezhető a felszínre, vagy zöld színnel, ha igen.
  6. Irányítsa újra a hologramot úgy, hogy igazodni fog ahhoz a felülettípushoz (függőleges vagy vízszintes), amelyhez affinitással rendelkezik.
  7. Zökkenőmentesen helyezze a hologramot a kiválasztott felületre, hogy elkerülje az elugró vagy bekattintó viselkedést.

Az alábbi kódolási gyakorlat összes kódját helyezze vissza, vagy használja ezt a kész 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;
    }
}

Build és üzembe helyezés

  • Ahogy korábban, buildelje a projektet, és telepítse a HoloLens.
  • Várjon, amíg a térbeli leképezési adatok vizsgálata és feldolgozása befejeződik.
  • Ha látja a naprendszert, tekintse meg az alábbi leképezésmezőt, és hajtson végre egy kijelölési kézmozdulatot a mozgatáshoz. Amíg a leképezés mező ki van jelölve, egy határolókeret látható lesz a leképezés mezője körül.
  • Helyezze át a mozgást a helyiség egy másik helyére. A leképezés mezőnek követnie kell a tekintetét. Ha a leképezés mező alatti árnyék pirosra vált, a hologram nem kerül arra a felületre. Ha a leképezés mező alatti árnyék zöldre vált, egy másik kijelölési kézmozdulattal helyezze el a hologramot.
  • Keresse meg és válassza ki a fal egyik holografikus poszterét, és helyezze át egy új helyre. Figyelje meg, hogy a posztereket nem lehet a padlóra vagy a felső határra felhelyezni, és hogy az mindig megfelelően orientált marad az egyes fal felé, ahogy mozog.

5. fejezet – Eltakaródás

Célkitűzések

  • Állapítsa meg, hogy a térbeli leképezési háló eltolt-e egy hologramot.
  • Különböző eltakarási technikák alkalmazásával szórakoztató hatást érhet el.

Utasítások

Először is lehetővé fogjuk tenni, hogy a térleképezési háló más hologramokat is kitűntsen anélkül, hogy az a való világba tolódást hoz létre:

  • A Hierarchia panelen válassza ki a SpatialProcessing objektumot.
  • Az Inspector panelen keresse meg a Play Space Manager (Script) összetevőt.
  • Kattintson a Secondary Material (Másodlagos anyag) tulajdonságtól jobbra található körre.
  • Keresse meg és válassza ki az Occlusion anyagot, és zárja be az ablakot.

A következő lépés egy különleges viselkedés hozzáadása a Földhöz, hogy kék kiemelést adjon hozzá, amikor egy másik hologram (például a nap) vagy a térbeli leképezési háló elzárja:

  • A Project panel Hologramok aSolarSystem objektumot.
  • Kattintson a Földön elemre.
  • Az Inspector panelen keresse meg a Föld anyagát (alsó összetevő).
  • A Shader legördülő menübenmódosítsa a árnyékolót Custom OcclusionRimre. Ez kék kiemelést fog renderelni a Föld körül, amikor egy másik objektum el van zárva.

Végül engedélyezni fogjuk a naprendszerünk bolygóira gyakorolt x-ray vision hatást. A következők eléréséhez szerkesztenie kell a PlanetOcclusion.cs fájlt (amely a Scripts\SolarSystem mappában található):

  1. Állapítsa meg, hogy egy bolygót eltolt-e a SpatialMapping réteg (helyiséghálók és síkok).
  2. Egy bolygó wireframe ábrázolása, amikor a SpatialMapping réteg elzárja.
  3. Elrejtheti egy bolygó wireframe-ábrázolásai, ha nem blokkolja a SpatialMapping réteg.

Kövesse a PlanetOcclusion.cs 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);
            }
        }
    }
}

Build és üzembe helyezés

  • Az alkalmazás a szokásos módon HoloLens üzembe.
  • Várja meg, 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 jelölje ki a Naprendszer leképezésmezőt, majd állítsa be a dobozt egy fal vagy egy számláló mögött.
  • Az alapszintű eltakarítást úgy is megtekintheti, ha a poszterek vagy kivetítőmezők között társviszonyba állva elrejti a tárgyakat.
  • Keresse meg a Bolygót, ahol egy 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 vagy a helyiség egyéb felületei mögött mozognak. Most már van x-ray visione, és láthatja a vázukat!

Vége

Gratulálunk! Befejezte az MR Spatial 230: Spatial Mapping (TÉRbeli leképezés) adatokat.

  • Tudja, hogyan lehet beolvasni a környezetet, és térbeli leképezési adatokat betölteni a Unitybe.
  • Tisztában van az árnyékolók alapjaival, és hogy hogyan használhatók az anyagok a világ újra vizualizációihoz.
  • Megtanulta, hogyan lehet új feldolgozási technikákat találni a síkok keresésében és a háromszögek hálóból való eltávolításában.
  • A hologramokat olyan felületekre tudta áthelyezni és áthelyezni, amelyek értelmesek voltak.
  • Különböző eltakaródási technikákat tapasztalt, és kihasználta a x-ray vision erejét!