Hololens 作爲一款混合現實設備,其與傳統 VR/AR 設備最大的區別是,能夠和現實世界進行交互。
以一個立方體爲例,當我們沒有使用 Spatial Mapping 時,我們只能在空間中移動它,而不能把它放置在現實世界的物體上,例如放置在一個椅子上。當我們使用了 Spatial Mapping 後,Hololens 會先掃描出所在房間的三維信息,掃描完畢後你就可以將物體放置在掃描後的空間物體上。
一 , 用 unity2018.4.9 vs2017 創建一個新的 Unity 項目 VoiceDemo,初始化項目:
1.導入 MRTK 包 (版本 HoloToolkit-Unity-2017.4.2.0)
2.應用項目設置爲 MR 項目 (一鍵設置成爲可以部署的環境)
3.使用 HoloLensCamera 替代默認相機
4.添加 CursorWithFeedback (識別並反饋手勢的光標控件)
5.添加 InputManager (作爲輸入源管理器,管理 gaze,gesture,speech等)
6.設置 InputManager 的 SimpleSinglePointerSelector 腳本的 Cursor 屬性爲添加的 CursorWithFeedback (添加手勢源到inputmanger)
7.創建一個空 GameObject,名爲 Manager
,爲其添加子 gameObject: InputManager
8.添加一個 Cube
最終 Hierarchy 結構如下:
二、實現空間映射Spatial Mapping
1)添加 MRTK 工具包下的 SpatialMapping 預製體到 Manager 對象下。 //用於掃描當前的空間環境
修改 Spatial Mapping Manager 的 Surface Material 屬性值爲 MRTK 包中的 SpatialUnderstandingSurface(空間理解時的表面網格材質),其他參數使用默認值即可,該屬性爲空間掃描時所使用的材質。
(2)在 Manager 下新建一個 GameObject(Create Empty ),名爲 SpatialProcessing
。
(3)爲 SpatialProcessing 添加以下兩個 MRTK 包中的腳本:
SurfaceMeshesToPlanes.cs
RemoveSurfaceVertices.cs
(4)新建腳本 SpatialProcessing.cs
,並將其添加到 SpatialProcessing 上。
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using System.Collections.Generic;
using UnityEngine;
namespace HoloToolkit.Unity.SpatialMapping.Tests
{
public class SpatialProcessing : Singleton<SpatialProcessing>
{
[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("結束處理所需要的最小floor數量")]
public uint minimumFloors = 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 yet.
if (!meshesProcessed)
{
// Check to see if enough scanning time has passed
// since starting the observer.
if ((Time.unscaledTime - 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...
if (SpatialMappingManager.Instance.IsObserverRunning())
{
// Stop the observer.
SpatialMappingManager.Instance.StopObserver();
}
// Call CreatePlanes() to generate planes.
CreatePlanes();
// 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)
{
// Collection of floor planes that we can use to set horizontal items on.
List<GameObject> floors = new List<GameObject>();
floors = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Floor);
// Check to see if we have enough floors (minimumFloors) to start processing.
if (floors.Count >= minimumFloors)
{
// Reduce our triangle count by removing any triangles
// from SpatialMapping meshes that intersect with active planes.
RemoveVertices(SurfaceMeshesToPlanes.Instance.ActivePlanes);
// After scanning is over, switch to the secondary (occlusion) material.
SpatialMappingManager.Instance.SetSurfaceMaterial(secondaryMaterial);
}
else
{
// Re-enter scanning mode so the user can find more surfaces before processing.
SpatialMappingManager.Instance.StartObserver();
// Re-process spatial data after scanning completes.
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>
protected override void OnDestroy()
{
if (SurfaceMeshesToPlanes.Instance != null)
{
SurfaceMeshesToPlanes.Instance.MakePlanesComplete -= SurfaceMeshesToPlanes_MakePlanesComplete;
}
base.OnDestroy();
}
}
}
(5)設置 SpatialProcessing 預設體屬性如下:
Surface Meshes To Planes 腳本能夠將掃描的網格轉換爲實體。
Draw Planes 爲需要轉換的類型。
Destory Planes 爲需要丟棄的類型。
這裏這兩個參數都使用了默認值,即保留了 Wall、Floor、Ceiling、Table 類型的網格數據。
Remove Surface Vertices 腳本能夠把與實體重合的網格刪除。
SpatialProcessing 腳本用於處理網格數據。
Scan Time : 掃描過多少秒開始轉換
Default Material: 掃描時使用的材質,這裏使用 MRTK 包中的 WireframeBlue。
secondaryMaterial: 停止掃描時使用的材質,這裏使用 MRTK 包中的 Occlusion,注意路徑是 HoloToolKit/SpatialMapping/Materials/Occlusion.mat。
minimumFloors: 結束處理所需要的最小 floor 數量。
(6)爲 Cube 添加 MRTK 包下的 TapToPlace.cs
腳本。
(7)使用真機運行程序,不要忘記添加 SpatialPerception
權限:
實驗效果:
程序啓動後,會先掃描空間信息。當掃描結束後,我們就可以把 Cube 放在實際的物體上,比如牆壁上。
三、Spatial UnderStanding 空間理解
不知道你在運行上面的程序時,有沒有嘗試過,在掃描結束後你走動到之前沒有掃描到的地方,這時候就無法將Cube放置在實際的物體上了。
這也很好理解,程序在啓動的一段時間內掃描空間數據,掃描結束後將其轉換爲(房屋)模型,你實際上放到的是在(房屋)模型上(不信你先掃描一個椅子,掃描結束後將椅子移走,Cube 只能放在椅子原來的位置上)。而我們之前沒有掃描到的地方,自然沒有(房屋)模型,因此無法放置。
解決辦法:Hololens 爲我們提供了 Spatial UnderStanding 的功能,能夠讓 Hololens 實時掃描空間數據,實時更新(房屋)模型。當然這樣會佔用較大的 CPU 資源。
MRTK 工具包爲我們提供了 SpatialUnderstanding,直接將其拖入 Manager 下即可。
重新運行程序,我們發現是在實時掃描的,掃描到的部分被藍色網格所覆蓋。
查看下開啓 SpatialUnderstanding 的 CPU 使用情況: