Aracılığıyla paylaş


HoloLens (1. nesil) Uzamsal 230: Uzamsal eşleme

Önemli

Karma Gerçeklik Academy öğreticileri HoloLens (1. nesil), Unity 2017 ve Karma Gerçeklik Immersive Headsets düşünülerek tasarlanmıştır. Bu nedenle, bu cihazlar için geliştirme konusunda hala rehberlik arayan geliştiriciler için bu öğreticileri yerinde bırakmanın önemli olduğunu hissediyoruz. Bu öğreticiler, HoloLens 2 için kullanılan en son araç kümeleri veya etkileşimlerle güncelleştirilmez ve Unity'nin daha yeni sürümleriyle uyumlu olmayabilir. Desteklenen cihazlarda çalışmaya devam etmek için bakımları yapılır. HoloLens 2 için yeni bir öğretici serisi yayınlanmıştır.

Uzamsal eşleme , ortam hakkında hologramlar öğreterek gerçek dünyayı ve sanal dünyayı birleştirir. MR Spatial 230 'da (Project Planetarium) nasıl yapılacağını öğreneceğiz:

  • Ortamı tarayın ve HoloLens'ten geliştirme makinenize veri aktarın.
  • Gölgelendiricileri keşfedin ve alanınızı görselleştirmek için bunları kullanmayı öğrenin.
  • Örgü işlemeyi kullanarak oda mesh'ini basit düzlemlere bölün.
  • MR Basics 101'de öğrendiğimiz yerleştirme tekniklerinin ötesine geçin ve bir hologramın ortama yerleştirilebileceği yer hakkında geri bildirim sağlayın.
  • Hologramınız gerçek bir nesnenin arkasındayken x-ray görüntü ile görmeye devam edebilmeniz için okluzyon efektlerini keşfedin!

Cihaz desteği

Kurs HoloLens Çevreleyici kulaklıklar
MR Spatial 230: Uzamsal haritalama ✔️

Başlamadan önce

Önkoşullar

Proje dosyaları

  • Projenin gerektirdiği dosyaları indirin. Unity 2017.2 veya sonraki bir sürümü gerektirir.
    • Unity 5.6 desteğine hala ihtiyacınız varsa lütfen bu sürümü kullanın.
    • Unity 5.5 desteğine hala ihtiyacınız varsa lütfen bu sürümü kullanın.
    • Unity 5.4 desteğine hala ihtiyacınız varsa lütfen bu sürümü kullanın.
  • Dosyaları masaüstünüzde veya ulaşılmaya kolay başka bir konumda arşivden kaldırın.

Not

İndirmeden önce kaynak kodu incelemek isterseniz GitHub'da kullanılabilir.

Notlar

  • Visual Studio'da "Yalnızca Kodumu Etkinleştir" seçeneğinin, kodunuzda kesme noktalarına ulaşmasını sağlamak için Araçlar > Seçenekler > Hata Ayıklama altında devre dışı bırakılması (işaretlenmemiş) olması gerekir.

Unity kurulumu

  • Unity'i başlatın.
  • Yeni proje oluşturmak için Yeni'yi seçin.
  • Projeye Planetaryum adını verin.
  • 3B ayarının seçili olduğunu doğrulayın.
  • Proje Oluştur'a tıklayın.
  • Unity başlatıldıktan sonra Proje Ayarları > Oynatıcısını Düzenle'ye >gidin.
  • Denetçi panelinde yeşil Windows Mağazası simgesini bulun ve seçin.
  • Diğer Ayarlar'ı genişletin.
  • İşleme bölümünde Sanal Gerçeklik Desteği seçeneğini işaretleyin.
  • Windows Holographic'inSanal Gerçeklik SDK'ları listesinde göründüğünü doğrulayın. Aksi takdirde, listenin + en altındaki düğmeyi seçin ve Windows Holographic'i seçin.
  • Yayımlama Ayarları'nı genişletin.
  • Özellikler bölümünde aşağıdaki ayarları denetleyin:
    • InternetClientServer
    • PrivateNetworkClientServer
    • Mikrofon
    • SpatialPerception
  • Proje Ayarları > Kalitesini Düzenle'ye > gidin
  • Denetçi panelinde, Windows Mağazası simgesinin altında , 'Varsayılan' satırının altındaki siyah açılan oku seçin ve varsayılan ayarı Çok Düşük olarak değiştirin.
  • Varlıklar > İçeri Aktarma Paketi > Özel Paketi'ne gidin.
  • ...\HolographicAcademy-Holograms-230-SpatialMapping\Starting klasörüne gidin.
  • Planetarium.unitypackage'a tıklayın.
  • ’a tıklayın.
  • Unity Paketini İçeri Aktar penceresi görünmelidir ve İçeri Aktar düğmesine tıklayın.
  • Unity'nin bu projeyi tamamlamak için ihtiyaç duyacağımız tüm varlıkları içeri aktarmasını bekleyin.
  • Hiyerarşi panelinde Ana Kamera'yı silin.
  • Proje panelinde HoloToolkit-SpatialMapping-230\Utilities\Prefabs klasöründe Ana Kamera nesnesini bulun.
  • Ana Kamera ön menüsünü Sürükleyip Hiyerarşi paneline bırakın.
  • Hiyerarşi panelinde Directional Light nesnesini silin.
  • Proje panelinde, Hologramlar klasöründe İmleç nesnesini bulun.
  • sürükleyin & İmleç ön düzenini Hiyerarşi'ye bırakın.
  • Hiyerarşi panelinde İmleç nesnesini seçin.
  • Denetçi panelinde Katman açılan menüsüne tıklayın ve Katmanları Düzenle... öğesini seçin.
  • Kullanıcı Katmanı 31'i "SpatialMapping" olarak adlandırabilirsiniz.
  • Yeni sahneyi kaydet: Dosya > Sahneyi Farklı Kaydet...
  • Yeni Klasör'e tıklayın ve klasörü Sahneler olarak adlandırın.
  • Dosyayı "Planetarium" olarak adlandırın ve Sahneler klasörüne kaydedin.

1. Bölüm - Tarama

Hedefler

  • SurfaceObserver hakkında bilgi edinin ve ayarlarının deneyimi ve performansı nasıl etkilediğini öğrenin.
  • Odanızın küllerini toplamak için bir oda tarama deneyimi oluşturun.

Yönergeler

  • HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabsproje panelinde SpatialMapping önfabını bulun.
  • Sürükleyin & SpatialMapping prefab'ını Hiyerarşi paneline bırakın.

Derleme ve Dağıtma (bölüm 1)

  • Unity'de Dosya > Derleme Ayarları'nı seçin.
  • Planetaryum sahnesini derlemeye eklemek için Açık Sahne Ekle'ye tıklayın.
  • Platformlistesinden Evrensel Windows Platformu seçin ve Platform Değiştir'e tıklayın.
  • SDK'yı Evrensel 10 ve UWP Derleme Türü'nüD3D olarak ayarlayın.
  • Unity C# Projelerini denetleyin.
  • Oluştur'a tıklayın.
  • "Uygulama" adlı yeni bir Klasör oluşturun.
  • Uygulama klasörüne tek tek tıklayın.
  • Klasör Seç düğmesine basın.
  • Unity'nin oluşturulması bittiğinde bir Dosya Gezgini penceresi görüntülenir.
  • Açmak için Uygulama klasörüne çift tıklayın.
  • Projeyi Visual Studio'da yüklemek için Planetarium.sln'ye çift tıklayın.
  • Visual Studio'da üst araç çubuğunu kullanarak Yapılandırma'yı Yayın olarak değiştirin.
  • Platformu x86 olarak değiştirin.
  • 'Yerel Makine'nin sağındaki açılan oka tıklayın ve Uzak Makine'yi seçin.
  • Cihazınızın IP adresini Adres alanına girin ve Kimlik Doğrulama Modu'nu Evrensel (Şifrelenmemiş Protokol) olarak değiştirin.
  • Hata Ayıkla -> Hata ayıklama olmadan başlat'a tıklayın veya Ctrl + F5 tuşlarına basın.
  • Derleme ve dağıtma durumu için Visual Studio'daki Çıkış panelini izleyin.
  • Uygulamanız dağıtıldıktan sonra odada gezinin. Çevresindeki yüzeylerin siyah beyaz tel çerçeve kafesleri ile kaplanmış olduğunu göreceksiniz.
  • Çevrenizi tarayın. Duvarlara, tavanlara ve zeminlere bakmayı unutmayın.

Derleme ve Dağıtma (bölüm 2)

Şimdi Uzamsal Eşleme'nin performansı nasıl etkileyebileceğini keşfedelim.

  • Unity'de Pencere Profili Oluşturucu'ya >tıklayın.
  • Profil Oluşturucu > GPU'sunu Ekle'ye tıklayın.
  • Etkin Profil Oluşturucu ><IP> Girin'e tıklayın.
  • HoloLens'inizin IP adresini girin.
  • Bağlan'a tıklayın.
  • GPU'nun bir çerçeveyi işlemesi için gereken milisaniye sayısını gözlemleyin.
  • Uygulamanın cihazda çalışmasını durdurun.
  • Visual Studio'ya dönün ve SpatialMappingObserver.cs dosyasını açın. Bunu Assembly-CSharp (Evrensel Windows) projesinin HoloToolkit\SpatialMapping klasöründe bulabilirsiniz.
  • Awake() işlevini bulun ve şu kod satırını ekleyin: TrianglesPerCubicMeter = 1200;
  • Projeyi cihazınıza yeniden dağıtın ve profil oluşturucuyu yeniden bağlayın. Bir çerçeveyi işlemek için milisaniye sayısındaki değişikliği gözlemleyin.
  • Uygulamanın cihazda çalışmasını durdurun.

Unity'de kaydetme ve yükleme

Son olarak oda ağımızı kaydedip Unity'ye yükleyelim.

  • Visual Studio'ya dönün ve önceki bölümde Awake() işlevine eklediğiniz TrianglesPerCubicMeter satırını kaldırın.
  • Projeyi cihazınıza yeniden dağıtın. Şimdi metreküp başına 500 üçgenle çalışıyor olmalıyız.
  • Bir tarayıcı açın ve HoloLens IPAddress'inize girerek Windows Cihaz Portalı'na gidin.
  • Sol panelde 3B Görünüm seçeneğini belirleyin.
  • Surface yeniden yapılandırması'nın altında Güncelleştir düğmesini seçin.
  • HoloLens'inizde taradığınız alanların ekran penceresinde görünmesini izleyin.
  • Oda taramanızı kaydetmek için Kaydet düğmesine basın.
  • Kaydedilen oda modeli SRMesh.obj'yi bulmak için İndirilenler klasörünüzü açın.
  • SRMesh.obj dosyasını Unity projenizin Assets klasörüne kopyalayın.
  • Unity'de Hiyerarşi panelindeSpatialMapping nesnesini seçin.
  • Object Surface Observer (Betik) bileşenini bulun.
  • Oda Modeli özelliğinin sağındaki daireye tıklayın.
  • SRMesh nesnesini bulup seçin ve ardından pencereyi kapatın.
  • Denetçipanelindeki Oda Modeli özelliğinin artık SRMesh olarak ayarlandığını doğrulayın.
  • Unity'nin önizleme moduna girmek için Yürüt düğmesine basın.
  • SpatialMapping bileşeni, kaydedilmiş oda modelindeki çizgileri unity'de kullanabilmeniz için yükler.
  • Tüm oda modelinizi tel çerçeve gölgelendiricisiyle görüntülemek için Sahne görünümüne geçin.
  • Önizleme modundan çıkmak için Oynat düğmesine yeniden basın.

NOT: Unity'de önizleme moduna bir sonraki girişte, kaydedilen oda ağı varsayılan olarak yüklenir.

2. Bölüm - Görselleştirme

Hedefler

  • Gölgelendiricilerin temellerini öğrenin.
  • Çevrenizi görselleştirin.

Yönergeler

  • Unity'nin Hiyerarşi panelinde SpatialMapping nesnesini seçin.
  • Denetçi panelinde Uzamsal Eşleme Yöneticisi (Betik) bileşenini bulun.
  • Surface Material özelliğinin sağındaki daireye tıklayın.
  • BlueLinesOnWalls malzemesini bulup seçin ve pencereyi kapatın.
  • Proje paneli Gölgelendiricileri klasöründe BlueLinesOnWalls'a çift tıklayarak gölgelendiriciyi Visual Studio'da açın.
  • Bu, aşağıdaki görevleri gerçekleştiren basit bir piksel (parçaya köşe) gölgelendiricidir:
    1. Bir köşenin konumunu dünya uzaya dönüştürür.
    2. Bir pikselin dikey olup olmadığını belirlemek için köşenin normal olup olmadığını denetler.
    3. İşleme için pikselin rengini ayarlar.

Yapılandırma ve Dağıtma

  • Unity'ye dönün ve önizleme moduna geçmek için Yürüt'e basın.
  • Oda örgüsünün tüm dikey yüzeylerinde (kaydedilen tarama verilerimizden otomatik olarak yüklenen) mavi çizgiler işlenir.
  • Oda görünümünüzü ayarlamak ve Unity'de tüm oda örgüsünün nasıl göründüğüne bakmak için Sahne sekmesine geçin.
  • Proje panelinde Materials klasörünü bulun ve BlueLinesOnWalls malzemesini seçin.
  • Bazı özellikleri değiştirin ve değişikliklerin Unity düzenleyicisinde nasıl göründüğüne bakın.
    • Denetçi panelinde LineScale değerini ayarlayarak çizgilerin daha kalın veya daha ince görünmesini sağlayın.
    • Denetçi panelinde LinesPerMeter değerini ayarlayarak her bir duvarda kaç çizginin görüneceğini değiştirin.
  • Önizleme modundan çıkmak için oynat'a yeniden tıklayın.
  • HoloLens'i derleyip dağıtın ve gölgelendirici işlemenin gerçek yüzeylerde nasıl göründüğünü gözlemleyin.

Unity, malzemelerin önizlemesini görüntülemek için harika bir iş çıkarır, ancak cihazda işlemeyi kullanıma almak her zaman iyi bir fikirdir.

Bölüm 3 - İşleme

Hedefler

  • Uygulamanızda kullanmak üzere uzamsal eşleme verilerini işleme tekniklerini öğrenin.
  • Düzlemleri bulmak ve üçgenleri kaldırmak için uzamsal haritalama verilerini analiz edin.
  • Hologram yerleştirme için uçakları kullanın.

Yönergeler

  • Unity'nin Proje panelinde, Hologramlar klasöründe SpatialProcessing nesnesini bulun.
  • Sürükleyin & SpatialProcessing nesnesini Hiyerarşi paneline bırakın.

SpatialProcessing prefab, uzamsal eşleme verilerini işlemeye yönelik bileşenler içerir. SurfaceMeshesToPlanes.cs uzamsal eşleme verilerini temel alan düzlemleri bulur ve oluşturur. Uygulamamızda duvarları, zeminleri ve tavanları temsil eden uçakları kullanacağız. Bu prefab, uzamsal eşleme ağındaki köşeleri kaldırabilen RemoveSurfaceVertices.cs dosyasını da içerir. Bu, örgüde delikler oluşturmak veya artık gerekli olmayan fazla üçgenleri kaldırmak için kullanılabilir (çünkü bunun yerine düzlemler kullanılabilir).

  • Unity'nin Proje panelinde, Hologramlar klasöründe SpaceCollection nesnesini bulun.
  • SpaceCollection nesnesini Sürükleyip Hiyerarşi paneline bırakın.
  • Hiyerarşi panelinde SpatialProcessing nesnesini seçin.
  • Denetçi panelinde Play Space Manager (Betik) bileşenini bulun.
  • PlaySpaceManager.cs dosyasına çift tıklayarak Visual Studio'da açın.

PlaySpaceManager.cs uygulamaya özgü kod içerir. Aşağıdaki davranışı etkinleştirmek için bu betiğin işlevselliğini ekleyeceğiz:

  1. Tarama süresi sınırını (10 saniye) aştıktan sonra uzamsal eşleme verilerini toplamayı durdurun.
  2. Uzamsal eşleme verilerini işleme:
    1. Dünyanın uçaklar (duvarlar, zeminler, tavanlar vb.) olarak daha basit bir gösterimini oluşturmak için SurfaceMeshesToPlanes'ı kullanın.
    2. Düzlem sınırları içinde kalan yüzey üçgenlerini kaldırmak için RemoveSurfaceVertices kullanın.
  3. Dünyada hologramlardan oluşan bir koleksiyon oluşturun ve bunları kullanıcının yakınındaki duvar ve zemin düzlemlerine yerleştirin.

PlaySpaceManager.cs içinde işaretlenmiş kodlama alıştırmalarını tamamlayın veya betiği aşağıdaki tamamlanmış çözümle değiştirin:

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

Yapılandırma ve Dağıtma

  • HoloLens'e dağıtmadan önce Unity'de Yürüt düğmesine basarak yürütme moduna geçin.
  • Oda ağı dosyadan yüklendikten sonra, uzamsal haritalama ağı üzerinde işleme başlamadan önce 10 saniye bekleyin.
  • İşlem tamamlandığında, düzlemler zemini, duvarları, tavanı vb. temsil edecek şekilde görünür.
  • Tüm uçaklar bulunduktan sonra, kameranın yakınındaki bir kat masasında bir güneş sistemi görünmelidir.
  • Kameranın yakınındaki duvarlarda da iki poster görünmelidir. Oyun modunda göremiyorsanız Sahne sekmesine geçin.
  • Yürütme modundan çıkmak için Oynat düğmesine yeniden basın.
  • Her zamanki gibi HoloLens'i derleyin ve dağıtın.
  • Uzamsal eşleme verilerinin taranmasını ve işlenmesinin tamamlanmasını bekleyin.
  • Uçakları gördüğünüzde, dünyanızdaki güneş sistemini ve posterleri bulmaya çalışın.

Bölüm 4 - Yerleştirme

Hedefler

  • Hologramların bir yüzeye sığar mı saptayın.
  • Hologram bir yüzeye sığabildiğinde/sığamadığında kullanıcıya geri bildirim sağlayın.

Yönergeler

  • Unity'nin Hiyerarşi panelinde SpatialProcessing nesnesini seçin.
  • Denetçi panelinde Surface Meshes To Planes (Betik) bileşenini bulun.
  • Seçimi temizlemek için Çizim Düzlemleri özelliğini Hiçbir şey olarak değiştirin.
  • Çizim Düzlemleri özelliğini Duvar olarak değiştirin; böylece yalnızca duvar düzlemleri işlenir.
  • Proje panelindeki Betikler klasöründe Yerleştirilebilir.cs dosyasına çift tıklayarak Visual Studio'da açın.

Yerleştirilebilir betik, uçak bulma tamamlandıktan sonra oluşturulan posterlere ve projeksiyon kutusuna zaten eklenmiştir. Tek yapmamız gereken bazı kodların açıklamalarını açmaktır ve bu betik aşağıdakileri başaracaktır:

  1. Hologramın yüzeye sığıp sığmadığını belirlemek için merkezden ve sınırlayıcı küpünün dört köşesinden raycasting yapın.
  2. Hologramın üzerine hizalı olarak oturması için yeterince düzgün olup olmadığını belirlemek için yüzeyi normal kontrol edin.
  3. Yerleştirildiği sırada gerçek boyutunu göstermek için hologramın çevresinde sınırlayıcı bir küp oluşturun.
  4. Hologramın zemine/duvara yerleştirileceği yeri göstermek için hologramın altına/arkasına gölge at.
  5. Hologram yüzeye yerleştirilemiyorsa gölgeyi kırmızı veya mümkünse yeşil olarak işleyebilirsiniz.
  6. Hologramı, benzine sahip olduğu yüzey türüyle (dikey veya yatay) hizalamak için yeniden yönlendirin.
  7. Atlama veya tutturma davranışını önlemek için hologramı seçili yüzeye sorunsuz bir şekilde yerleştirin.

Aşağıdaki kodlama alıştırmasında tüm kodun açıklamasını kaldırın veya Bu tamamlanmış çözümü Placeable.cs dosyasında kullanın:

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

Yapılandırma ve Dağıtma

  • Daha önce olduğu gibi projeyi derleyin ve HoloLens'e dağıtın.
  • Uzamsal eşleme verilerinin taranmasını ve işlenmesinin tamamlanmasını bekleyin.
  • Güneş sistemini gördüğünüzde aşağıdaki projeksiyon kutusuna bakın ve hareket ettirmek için bir seçim hareketi gerçekleştirin. Projeksiyon kutusu seçiliyken, projeksiyon kutusunun çevresinde bir sınırlayıcı küp görünür.
  • Sizi odanın farklı bir yerine bakmak için hareket ettirin. Projeksiyon kutusu bakışınızı izlemelidir. Projeksiyon kutusunun altındaki gölge kırmızıya döndüğünde hologramı o yüzeye yerleştiremezsiniz. Projeksiyon kutusunun altındaki gölge yeşile döndüğünde, başka bir seçim hareketi gerçekleştirerek hologramı yerleştirebilirsiniz.
  • Duvardaki holografik posterlerden birini bulup seçerek yeni bir konuma taşıyın. Posteri zemine veya tavana yerleştiremediğinize ve gezindikçe her duvara doğru şekilde yönlendirildiğini fark edin.

5. Bölüm - Tıkanıklık

Hedefler

  • Bir hologramın uzamsal eşleme ağı tarafından tıkanmış olup olmadığını belirleyin.
  • Eğlenceli bir etki elde etmek için farklı okluzyon teknikleri uygulayın.

Yönergeler

İlk olarak, uzamsal haritalama örgüsüne gerçek dünyayı tıkamadan diğer hologramları tıkamasına izin yeceğiz:

  • Hiyerarşi panelinde SpatialProcessing nesnesini seçin.
  • Denetçi panelinde Play Space Manager (Betik) bileşenini bulun.
  • İkincil Malzeme özelliğinin sağındaki daireye tıklayın.
  • Occlusion malzemesini bulup seçin ve pencereyi kapatın.

Ardından, Dünya'ya özel bir davranış ekleyeceğiz, böylece başka bir hologram (güneş gibi) veya uzamsal harita ağı tarafından tıkandığı her durumda mavi bir vurguya sahip olacak:

  • Proje panelinde, Hologramlar klasöründe SolarSystem nesnesini genişletin.
  • Dünya'ya tıklayın.
  • Denetçi panelinde Dünyanın malzemesini (alt bileşen) bulun.
  • Gölgelendirici açılan menüsünde gölgelendiriciyi Özel > OcclusionRim olarak değiştirin. Bu, başka bir nesne tarafından her tıkandığı zaman Dünya'nın etrafında mavi bir vurgu oluşturur.

Son olarak, güneş sistemimizdeki gezegenler için x-ışını görüntü efektini etkinleştireceğiz. Aşağıdakileri başarmak için PlanetOcclusion.cs dosyasını (Scripts\SolarSystem klasöründe bulunur) düzenlememiz gerekir:

  1. Bir gezegenin SpatialMapping katmanı (oda kafesleri ve düzlemler) tarafından tıkanıp tıkanmadığını belirleyin.
  2. SpatialMapping katmanı tarafından her tıkandığı zaman bir gezegenin tel çerçeve gösterimini gösterin.
  3. Uzamsal Eşleme katmanı tarafından engellenmeyen bir gezegenin tel çerçeve gösterimini gizleyin.

PlanetOcclusion.cs dosyasındaki kodlama alıştırmasını izleyin veya aşağıdaki çözümü kullanın:

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

Yapılandırma ve Dağıtma

  • Uygulamayı her zamanki gibi derleyin ve HoloLens'e dağıtın.
  • Uzamsal eşleme verilerinin taranmasını ve işlenmesinin tamamlanmasını bekleyin (duvarlarda mavi çizgiler görünmelidir).
  • Güneş sisteminin projeksiyon kutusunu bulup seçin ve ardından kutuyu bir duvarın yanına veya sayacın arkasına ayarlayın.
  • Poster veya projeksiyon kutusunda eşlemek için yüzeylerin arkasına saklanarak temel oklüzyonu görüntüleyebilirsiniz.
  • Dünya'yı arayın, başka bir hologramın veya yüzeyin arkasına her geçtiğinde mavi bir vurgu etkisi olmalıdır.
  • Gezegenlerin duvarın veya odadaki diğer yüzeylerin arkasında hareket etmesini izleyin. Artık x-ray görüntüye sahipsiniz ve tel çerçeve iskeletlerini görebiliyorsunuz!

Son

Tebrikler! Mr Spatial 230: Spatial mapping'i tamamladınız.

  • Ortamınızı taramayı ve uzamsal eşleme verilerini Unity'ye yüklemeyi biliyorsunuz.
  • Gölgelendiricilerin temellerini ve malzemeleri dünyayı yeniden görselleştirmek için nasıl kullanılabileceğini anlıyorsunuz.
  • Düzlemleri bulmak ve bir ağdan üçgenleri kaldırmak için yeni işleme tekniklerini öğrendiniz.
  • Hologramları anlamlı olan yüzeylere taşıyabiliyor ve yerleştirebiliyordunuz.
  • Farklı oklüzyon tekniklerini deneyimlediyseniz ve x-ray vision'ın gücünden yararlanmıştınız!