HoloLens (1ère génération) Spatial 230 : Mappage spatial

Important

Les tutoriels Mixed Reality Academy ont été conçus avec HoloLens (1ère génération), Unity 2017 et Mixed Reality casques immersifs à l’esprit. Nous estimons qu’il est important de laisser ces tutoriels à la disposition des développeurs qui recherchent encore des conseils pour développer des applications sur ces appareils. Ces didacticiels ne seront pas mis à jour avec les derniers ensembles d’outils ou interactions utilisés pour HoloLens 2 et peuvent ne pas être compatibles avec les versions plus récentes d’Unity. Ils sont fournis dans le but de fonctionner sur les appareils pris en charge. Une nouvelle série de tutoriels a été publiée pour HoloLens 2.

Le mappage spatial combine le monde réel et le monde virtuel en enseignant des hologrammes sur l’environnement. Dans MR Spatial 230 (Project Planetarium), nous allons apprendre à :

  • Analysez l’environnement et transférez des données de HoloLens vers votre ordinateur de développement.
  • Explorez les nuanceurs et découvrez comment les utiliser pour visualiser votre espace.
  • Décomposez le maillage de la pièce en plans simples à l’aide du traitement de maillage.
  • Allez au-delà des techniques de placement que nous avons apprises dans MR Basics 101 et fournissez des commentaires sur l’emplacement où un hologramme peut être placé dans l’environnement.
  • Explorez les effets d’occlusion, donc quand votre hologramme se trouve derrière un objet du monde réel, vous pouvez toujours le voir avec la vision aux rayons X !

Prise en charge des appareils

Cours HoloLens Casques immersifs
Réalité mixte - Fonctionnalités spatiales - Cours 230 : Mappage spatial ✔️

Avant de commencer

Prérequis

Fichiers projet

  • Téléchargez les fichiers requis par le projet. Nécessite Unity 2017.2 ou version ultérieure.
    • Si vous avez toujours besoin de la prise en charge d’Unity 5.6, utilisez cette version.
    • Si vous avez toujours besoin de la prise en charge d’Unity 5.5, utilisez cette version.
    • Si vous avez toujours besoin de la prise en charge d’Unity 5.4, utilisez cette version.
  • Annulez l’archivage des fichiers sur votre bureau ou dans un autre emplacement facile d’accès.

Notes

Si vous souhaitez parcourir le code source avant de le télécharger, il est disponible sur GitHub.

Notes

  • « Activer uniquement mon code » dans Visual Studio doit être désactivé (décoché) sous Options > d’outils > Débogage pour atteindre les points d’arrêt de votre code.

Configuration d’Unity

  • Démarrez Unity.
  • Sélectionnez Nouveau pour créer un projet.
  • Nommez le projet Planétarium.
  • Vérifiez que le paramètre 3D est sélectionné.
  • Cliquez sur Create Project (Créer le projet).
  • Une fois Unity lancé, accédez à Modifier le > Lecteur paramètres > du projet.
  • Dans le panneau Inspecteur , recherchez et sélectionnez l’icône verte du Windows Store .
  • Développez Autres paramètres.
  • Dans la section Rendu, case activée l’option Prise en charge de la réalité virtuelle.
  • Vérifiez que Windows Holographic apparaît dans la liste des SDK de réalité virtuelle. Si ce n’est pas le cas, sélectionnez le + bouton en bas de la liste et choisissez Windows Holographic.
  • Développez Paramètres de publication.
  • Dans la section Fonctionnalités, case activée les paramètres suivants :
    • InternetClientServer
    • PrivateNetworkClientServer
    • Microphone
    • SpatialPerception
  • Accédez à Modifier la > qualité des paramètres > du projet
  • Dans le panneau Inspecteur , sous l’icône du Windows Store, sélectionnez la flèche déroulante noire sous la ligne « Par défaut » et remplacez le paramètre par défaut sur Très faible.
  • Accédez à Ressources > Importer le package > personnalisé.
  • Accédez au dossier ...\HolographicAcademy-Holograms-230-SpatialMapping\Starting .
  • Cliquez sur Planetarium.unitypackage.
  • Cliquez sur Ouvrir.
  • Une fenêtre Importer un package Unity doit apparaître. Cliquez sur le bouton Importer .
  • Attendez qu’Unity importe toutes les ressources dont nous aurons besoin pour terminer ce projet.
  • Dans le panneau Hiérarchie , supprimez la caméra principale.
  • Dans le panneau Projet , dossier HoloToolkit-SpatialMapping-230\Utilities\Prefabs , recherchez l’objet Main Camera .
  • Faites glisser et déposez le préfabriqué de la caméra principale dans le panneau Hiérarchie .
  • Dans le panneau Hiérarchie , supprimez l’objet Lumière directionnelle .
  • Dans le panneau Projet , dossier Holograms , recherchez l’objet Cursor .
  • Faites glisser & déposez le préfabriqué curseur dans la hiérarchie.
  • Dans le panneau Hiérarchie , sélectionnez l’objet Cursor .
  • Dans le panneau Inspecteur , cliquez sur la liste déroulante Calque et sélectionnez Modifier les couches....
  • Nommez la couche utilisateur 31 « SpatialMapping ».
  • Enregistrer la nouvelle scène : Fichier > Enregistrer la scène sous...
  • Cliquez sur Nouveau dossier et nommez le dossier Scenes.
  • Nommez le fichier « Planétarium » et enregistrez-le dans le dossier Scenes .

Chapitre 1 - Analyse

Objectifs

  • Découvrez le SurfaceObserver et l’impact de ses paramètres sur l’expérience et les performances.
  • Créez une expérience d’analyse de salle pour collecter les maillages de votre salle.

Instructions

  • Dans le dossier Volet projetHoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs , recherchez le préfabriqué SpatialMapping .
  • Faites glisser & déposez le préfabriqué SpatialMapping dans le panneau Hiérarchie .

Générer et déployer (partie 1)

  • Dans Unity, sélectionnez Paramètres de build de fichier>.
  • Cliquez sur Ajouter des scènes ouvertes pour ajouter la scène Planetarium à la build.
  • Sélectionnez plateforme Windows universelle dans la liste Plateforme, puis cliquez sur Changer de plateforme.
  • Définissez sdksur Universal 10 et type de build UWP sur D3D.
  • Vérifiez les projets Unity C#.
  • Cliquez sur Générer.
  • Créez un dossier nommé « App ».
  • Cliquez sur le dossier Application .
  • Appuyez sur le bouton Sélectionner un dossier .
  • Lorsque Unity a terminé la génération, une fenêtre Explorateur de fichiers s’affiche.
  • Double-cliquez sur le dossier Application pour l’ouvrir.
  • Double-cliquez sur Planetarium.sln pour charger le projet dans Visual Studio.
  • Dans Visual Studio, utilisez la barre d’outils supérieure pour remplacer la configuration par Mise en production.
  • Remplacez la plateforme par x86.
  • Cliquez sur la flèche déroulante à droite de « Ordinateur local », puis sélectionnez Ordinateur distant.
  • Entrez l’adresse IP de votre appareil dans le champ Adresse et remplacez Mode d’authentification par Universel (protocole non chiffré).
  • Cliquez sur Déboguer -> Démarrer sans débogage ou appuyez sur Ctrl + F5.
  • Regardez le panneau Sortie dans Visual Studio pour obtenir des status de génération et de déploiement.
  • Une fois votre application déployée, parcourez la salle. Vous verrez les surfaces environnantes couvertes de maillages filaires noirs et blancs.
  • Analysez votre environnement. Veillez à regarder les murs, les plafonds et les planchers.

Générer et déployer (partie 2)

Voyons maintenant comment le mappage spatial peut affecter les performances.

  • Dans Unity, sélectionnez Window > Profiler.
  • Cliquez sur Ajouter un GPU profileur>.
  • Cliquez sur Active Profiler ><Enter IP(Entrée IP).>
  • Entrez l’adresse IP de votre HoloLens.
  • Cliquez sur Connecter.
  • Observez le nombre de millisecondes nécessaires pour que le GPU affiche un frame.
  • Arrêtez l’exécution de l’application sur l’appareil.
  • Revenez à Visual Studio et ouvrez SpatialMappingObserver.cs. Vous le trouverez dans le dossier HoloToolkit\SpatialMapping du projet Assembly-CSharp (Windows universel).
  • Recherchez la fonction Awake() et ajoutez la ligne de code suivante : TrianglesPerCubicMeter = 1200 ;
  • Redéployez le projet sur votre appareil, puis reconnectez le profileur. Observez la modification du nombre de millisecondes pour afficher un cadre.
  • Arrêtez l’exécution de l’application sur l’appareil.

Enregistrer et charger dans Unity

Enfin, nous allons enregistrer notre maillage de salle et le charger dans Unity.

  • Revenez à Visual Studio et supprimez la ligne TrianglesPerCubicMeter que vous avez ajoutée à la fonction Awake() au cours de la section précédente.
  • Redéployez le projet sur votre appareil. Nous devrions maintenant fonctionner avec 500 triangles par mètre cube.
  • Ouvrez un navigateur et entrez votre adresse IP HoloLens pour accéder au portail d’appareil Windows.
  • Sélectionnez l’option Affichage 3D dans le volet gauche.
  • Sous Reconstruction de Surface , sélectionnez le bouton Mettre à jour .
  • Observez que les zones que vous avez analysées sur votre HoloLens apparaissent dans la fenêtre d’affichage.
  • Pour enregistrer l’analyse de votre salle, appuyez sur le bouton Enregistrer.
  • Ouvrez votre dossier Téléchargements pour rechercher le modèle de salle enregistrée SRMesh.obj.
  • Copiez SRMesh.obj dans le dossier Assets de votre projet Unity.
  • Dans Unity, sélectionnez l’objet SpatialMapping dans le panneau Hierarchy .
  • Recherchez le composant Object Surface Observer (Script).
  • Cliquez sur le cercle à droite de la propriété Modèle de salle.
  • Recherchez et sélectionnez l’objet SRMesh , puis fermez la fenêtre.
  • Vérifiez que la propriété Modèle de salle dans le panneau Inspector est désormais définie sur SRMesh.
  • Appuyez sur le bouton Lecture pour accéder au mode d’aperçu d’Unity.
  • Le composant SpatialMapping charge les maillages à partir du modèle de salle enregistrée afin que vous puissiez les utiliser dans Unity.
  • Basculez en mode Scène pour voir tout votre modèle de salle affiché avec le nuanceur filaire.
  • Appuyez à nouveau sur le bouton Lire pour quitter le mode d’aperçu.

NOTE: La prochaine fois que vous entrez en mode d’aperçu dans Unity, il chargera le maillage de salle enregistré par défaut.

Chapitre 2 - Visualisation

Objectifs

  • Découvrez les principes de base des nuanceurs.
  • Visualisez votre environnement.

Instructions

  • Dans le panneau Hiérarchie d’Unity, sélectionnez l’objet SpatialMapping .
  • Dans le panneau Inspecteur, recherchez le composant Gestionnaire de mappage spatial (script).
  • Cliquez sur le cercle à droite de la propriété Surface Material .
  • Recherchez et sélectionnez le matériau BlueLinesOnWalls et fermez la fenêtre.
  • Dans le dossier Nuanceurs du volet Projet, double-cliquez sur BlueLinesOnWalls pour ouvrir le nuanceur dans Visual Studio.
  • Il s’agit d’un nuanceur de pixels simple (vertex à fragment) qui effectue les tâches suivantes :
    1. Convertit l’emplacement d’un sommet en espace mondial.
    2. Vérifie la valeur normale du vertex pour déterminer si un pixel est vertical.
    3. Définit la couleur du pixel pour le rendu.

Génération et déploiement d’applications WPF

  • Revenez à Unity et appuyez sur Lecture pour passer en mode préversion.
  • Les lignes bleues seront affichées sur toutes les surfaces verticales du maillage de salle (qui est automatiquement chargé à partir de nos données d’analyse enregistrées).
  • Basculez vers l’onglet Scène pour ajuster votre vue de la salle et voir comment le maillage de salle entier apparaît dans Unity.
  • Dans le panneau Projet , recherchez le dossier Matériaux et sélectionnez le matériau BlueLinesOnWalls .
  • Modifiez certaines propriétés et voyez comment les modifications apparaissent dans l’éditeur Unity.
    • Dans le panneau Inspecteur , ajustez la valeur LineScale pour que les lignes apparaissent plus épaisses ou plus minces.
    • Dans le panneau Inspecteur , ajustez la valeur LinesPerMeter pour modifier le nombre de lignes qui apparaissent sur chaque mur.
  • Cliquez à nouveau sur Lire pour quitter le mode d’aperçu.
  • Générez et déployez sur HoloLens et observez comment le rendu du nuanceur apparaît sur des surfaces réelles.

Unity fait un excellent travail d’aperçu des matériaux, mais il est toujours judicieux d’case activée rendu dans l’appareil.

Chapitre 3 - Traitement

Objectifs

  • Découvrez des techniques pour traiter les données de mappage spatial à utiliser dans votre application.
  • Analysez les données de mappage spatial pour rechercher des plans et supprimer des triangles.
  • Utilisez des plans pour le placement de l’hologramme.

Instructions

  • Dans le panneau Projet d’Unity, dossier Holograms , recherchez l’objet SpatialProcessing .
  • Faites glisser & déposez l’objet SpatialProcessing dans le panneau Hierarchy .

Le préfabriqué SpatialProcessing comprend des composants pour le traitement des données de mappage spatial. SurfaceMeshesToPlanes.cs recherche et génère des plans basés sur les données de mappage spatial. Nous utiliserons des plans dans notre application pour représenter les murs, les planchers et les plafonds. Ce préfabriqué inclut également RemoveSurfaceVertices.cs qui peut supprimer des sommets du maillage de mappage spatial. Cela peut être utilisé pour créer des trous dans le maillage ou pour supprimer les triangles excédentaires qui ne sont plus nécessaires (car les plans peuvent être utilisés à la place).

  • Dans le volet Projet d’Unity, dossier Holograms , recherchez l’objet SpaceCollection .
  • Faites glisser et déposez l’objet SpaceCollection dans le panneau Hierarchy .
  • Dans le panneau Hiérarchie , sélectionnez l’objet SpatialProcessing .
  • Dans le panneau Inspecteur, recherchez le composant Play Space Manager (Script).
  • Double-cliquez sur PlaySpaceManager.cs pour l’ouvrir dans Visual Studio.

PlaySpaceManager.cs contient du code spécifique à l’application. Nous allons ajouter des fonctionnalités à ce script pour activer le comportement suivant :

  1. Arrêtez la collecte des données de mappage spatial une fois que nous avons dépassé la limite de temps d’analyse (10 secondes).
  2. Traiter les données de mappage spatial :
    1. Utilisez SurfaceMeshesToPlanes pour créer une représentation plus simple du monde sous forme de plans (murs, planchers, plafonds, etc.).
    2. Utilisez RemoveSurfaceVertices pour supprimer les triangles de surface qui se trouvent dans les limites du plan.
  3. Générez une collection d’hologrammes dans le monde et placez-les sur des plans mur et plancher près de l’utilisateur.

Effectuez les exercices de codage marqués dans PlaySpaceManager.cs, ou remplacez le script par la solution terminée ci-dessous :

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

Génération et déploiement d’applications WPF

  • Avant de déployer sur HoloLens, appuyez sur le bouton Lecture dans Unity pour passer en mode lecture.
  • Une fois le maillage de salle chargé à partir du fichier, attendez 10 secondes avant que le traitement ne commence sur le maillage de mappage spatial.
  • Une fois le traitement terminé, les plans apparaissent pour représenter le plancher, les murs, le plafond, etc.
  • Une fois que tous les avions ont été trouvés, vous devriez voir un système solaire apparaître sur une table de plancher près de la caméra.
  • Deux affiches devraient apparaître sur les murs près de la caméra aussi. Basculez vers l’onglet Scène si vous ne pouvez pas les voir en mode Jeu .
  • Appuyez à nouveau sur le bouton Lecture pour quitter le mode lecture.
  • Générez et déployez sur HoloLens, comme d’habitude.
  • Attendez que l’analyse et le traitement des données de mappage spatial se terminent.
  • Une fois que vous voyez des avions, essayez de trouver le système solaire et les affiches dans votre monde.

Chapitre 4 - Placement

Objectifs

  • Déterminez si un hologramme sera ajusté sur une surface.
  • Fournissez des commentaires à l’utilisateur lorsqu’un hologramme peut ou ne peut pas tenir sur une surface.

Instructions

  • Dans le panneau Hiérarchie d’Unity, sélectionnez l’objet SpatialProcessing .
  • Dans le panneau Inspecteur, recherchez le composant Surface Meshes To Planes (Script).
  • Remplacez la propriété Draw Planes par Nothing pour effacer la sélection.
  • Remplacez la propriété Draw Planes par Wall afin que seuls les plans de mur soient rendus.
  • Dans le volet Projet , dossier Scripts , double-cliquez sur Placeable.cs pour l’ouvrir dans Visual Studio.

Le script Placeable est déjà attaché aux affiches et à la zone de projection qui sont créées une fois la recherche de plan terminée. Tout ce que nous avons à faire est de supprimer les marques de commentaire d’un code, et ce script permet d’obtenir les éléments suivants :

  1. Déterminez si un hologramme s’adaptera à une surface en effectuant un raycasting à partir du centre et des quatre coins du cube englobant.
  2. Vérifiez la surface normale pour déterminer si elle est suffisamment lisse pour que l’hologramme se repose.
  3. Affichez un cube englobant autour de l’hologramme pour afficher sa taille réelle lors de la mise en place.
  4. Cassez une ombre sous/derrière l’hologramme pour indiquer où elle sera placée sur le sol/le mur.
  5. Affichez l’ombre en rouge, si l’hologramme ne peut pas être placé sur la surface, ou vert, si possible.
  6. Réorientez l’hologramme pour qu’il s’aligne sur le type de surface (vertical ou horizontal) auquel il a une affinité.
  7. Placez l’hologramme en douceur sur la surface sélectionnée pour éviter un comportement de saut ou d’accrochage.

Supprimez les marques de commentaire de tout le code de l’exercice de codage ci-dessous, ou utilisez cette solution terminée dans Placeable.cs :

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

Génération et déploiement d’applications WPF

  • Comme précédemment, générez le projet et déployez-le sur HoloLens.
  • Attendez que l’analyse et le traitement des données de mappage spatial se terminent.
  • Lorsque vous voyez le système solaire, regardez la zone de projection ci-dessous et effectuez un mouvement de sélection pour le déplacer. Lorsque la zone de projection est sélectionnée, un cube englobant est visible autour de la zone de projection.
  • Déplacez-vous la tête pour regarder un autre emplacement dans la pièce. La zone de projection doit suivre votre regard. Lorsque l’ombre située sous la zone de projection devient rouge, vous ne pouvez pas placer l’hologramme sur cette surface. Lorsque l’ombre située sous la zone de projection devient verte, vous pouvez placer l’hologramme en effectuant un autre mouvement de sélection.
  • Recherchez et sélectionnez l’une des affiches holographiques sur un mur pour la déplacer vers un nouvel emplacement. Notez que vous ne pouvez pas placer l’affiche sur le plancher ou le plafond, et qu’elle reste correctement orientée sur chaque mur au fur et à mesure que vous vous déplacez.

Chapitre 5 - Occlusion

Objectifs

  • Déterminez si un hologramme est obstrué par le maillage de mappage spatial.
  • Appliquez différentes techniques d’occlusion pour obtenir un effet amusant.

Instructions

Tout d’abord, nous allons permettre au maillage de mappage spatial d’obstruer d’autres hologrammes sans obluder le monde réel :

  • Dans le panneau Hiérarchie , sélectionnez l’objet SpatialProcessing .
  • Dans le panneau Inspecteur, recherchez le composant Play Space Manager (Script).
  • Cliquez sur le cercle à droite de la propriété Matériau secondaire .
  • Recherchez et sélectionnez le matériau Occlusion , puis fermez la fenêtre.

Ensuite, nous allons ajouter un comportement spécial à la Terre, afin qu’elle ait un surbrillance bleu chaque fois qu’elle devient obclée par un autre hologramme (comme le soleil), ou par le maillage de mappage spatial :

  • Dans le panneau Projet , dans le dossier Holograms , développez l’objet SolarSystem .
  • Cliquez sur Terre.
  • Dans le panneau Inspecteur , recherchez le matériau de la Terre (composant inférieur).
  • Dans la liste déroulante Nuanceur, remplacez le nuanceur par Custom > OcclusionRim. Cela rend une mise en évidence bleue autour de la Terre chaque fois qu’elle est obstruée par un autre objet.

Enfin, nous allons permettre un effet de vision de rayons X pour les planètes de notre système solaire. Nous devrons modifier PlanetOcclusion.cs (qui se trouve dans le dossier Scripts\SolarSystem) afin d’effectuer les opérations suivantes :

  1. Déterminez si une planète est obstruée par la couche SpatialMapping (maillages et plans de salle).
  2. Afficher la représentation filaire d’une planète chaque fois qu’elle est obstruée par la couche SpatialMapping.
  3. Masquez la représentation filaire d’une planète lorsqu’elle n’est pas bloquée par la couche SpatialMapping.

Suivez l’exercice de codage dans PlanetOcclusion.cs ou utilisez la solution suivante :

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

Génération et déploiement d’applications WPF

  • Générez et déployez l’application sur HoloLens, comme d’habitude.
  • Attendez que l’analyse et le traitement des données de mappage spatial soient terminés (vous devriez voir des lignes bleues apparaître sur les murs).
  • Recherchez et sélectionnez la zone de projection du système solaire, puis définissez la case à côté d’un mur ou derrière un compteur.
  • Vous pouvez afficher l’occlusion de base en se cachant derrière des surfaces à appairer dans la zone d’affiche ou de projection.
  • Recherchez la Terre, il devrait y avoir un effet de surbrillance bleu chaque fois qu’il passe derrière un autre hologramme ou surface.
  • Regardez les planètes se déplacer derrière le mur ou d’autres surfaces dans la pièce. Vous avez maintenant une vision en rayons X et vous pouvez voir leurs squelettes filaires !

La fin

Félicitations ! Vous avez maintenant terminé MR Spatial 230 : Mappage spatial.

  • Vous savez comment analyser votre environnement et charger des données de mappage spatial dans Unity.
  • Vous comprenez les principes de base des nuanceurs et la façon dont les matériaux peuvent être utilisés pour visualiser le monde.
  • Vous avez découvert de nouvelles techniques de traitement pour trouver des plans et supprimer des triangles d’un maillage.
  • Vous avez pu vous déplacer et placer des hologrammes sur des surfaces qui étaient logiques.
  • Vous avez fait l’expérience de différentes techniques d’occlusion et vous avez exploité la puissance de la vision en rayons X !