空間映射提供了HoloLens周圍環(huán)境中真實(shí)世界表面的詳細(xì)表示分井,允許開發(fā)人員創(chuàng)建令人信服的混合現(xiàn)實(shí)體驗(yàn)吻贿。通過將真實(shí)世界與虛擬世界合并撤奸,應(yīng)用可以使全息圖看起來是真實(shí)的啸驯。通過提供熟悉的現(xiàn)實(shí)世界行為和交互,應(yīng)用程序也可以更自然地與用戶期望一致英上。
Unity內(nèi)置了對空間映射功能的支持炭序,通過以下兩種方式提供給開發(fā)者:
- HoloToolkit項(xiàng)目中你可以找到空間映射組件,這可以讓你便捷快速地開始使用空間映射特性苍日。
- Unity還提供更多底層的空間映射API惭聂,以便開發(fā)者能夠完全控制空間映射特性,滿足定制復(fù)雜的應(yīng)用需求
本文主要介紹HoloToolKit提供的空間映射(SpatialMapping)組件相恃,利用此組件可以快速的應(yīng)用中集成使用HoloLens的空間映射特性辜纲。
空間映射(SpatialMapping)主要有下面的使用用途:
- Occlusion
- Visualization
- Placement
- Physics
- Navigation
設(shè)置 SpatialMapping 功能開啟
為了使應(yīng)用能夠使用空間映射數(shù)據(jù),SpatialPerception能力必須被啟用。
使用以下步驟啟用此能力:
- 在Unity編輯器中耕腾,進(jìn)入Player Settings選項(xiàng)(Edit > Project Settings > Player)
- 點(diǎn)擊Window Store選項(xiàng)卡
- 展開Publish Settings選項(xiàng)见剩,并在Capabilities列表勾選SpatialPerception選項(xiàng)
use the API 使用底層API步驟
SurfaceObserver是主要使用到的API對象,下面是應(yīng)用使用空間映射特性推薦的大致流程:
- 設(shè)定SurfaceObserver對象
要為每一個(gè)需要空間映射數(shù)據(jù)的空間區(qū)域在應(yīng)用中聲明并初始化一個(gè)SurfaceObserver對象:
SurfaceObserver surfaceObserver;
void Start () {
surfaceObserver = new SurfaceObserver();
}
- 通過調(diào)用SetVolumeAsSphere扫俺、SetVolumeAsAxisAlignedBox苍苞、 SetVolumeAsOrientedBox、 或 SetVolumeAsFrustum方法可以為每個(gè)SurfaceObserver對象指定它們需要獲取數(shù)據(jù)的空間范圍狼纬。以后你還可以通過再次調(diào)用它們來重新設(shè)定檢測的空間范圍羹呵。
void Start () {
...
surfaceObserver.SetVolumeAsAxisAlignedBox(Vector3.zero, new Vector3(3, 3, 3));
}
處理OnDataReady事件
OnDataReady事件方法會(huì)接收到一個(gè)SurfaceData對象,它包含了WorldAnchor疗琉、MeshFilter和MeshCollider對象數(shù)據(jù)担巩,表示了當(dāng)前關(guān)聯(lián)的空間表面最新狀態(tài)。通過訪問Mesh Filter對象的Mesh數(shù)據(jù)可以進(jìn)行性能分析或者處理網(wǎng)格没炒。使用最新的Mesh數(shù)據(jù)來渲染空間表面并將它用于物理碰撞或者射線擊中對象。確認(rèn)SurfaceData內(nèi)容不為空很重要犯戏。-
處理空間表面變化送火,即處理OnSurfaceChanged事件
關(guān)于空間表面變化,有幾個(gè)典型情形需要處理先匪。Added狀態(tài)和Updated狀態(tài)可以使用相同的代碼處理种吸,Removed狀態(tài)則使用另一種代碼來處理。
- 在Added和Updated情形下呀非,我們從字典中添加或者獲取代碼當(dāng)前網(wǎng)格的對象坚俗,使用必要的組件來創(chuàng)建一個(gè)SurfaceData結(jié)構(gòu)體,然后調(diào)用RequestMeshDataAsync方法在場景中使用網(wǎng)格數(shù)據(jù)和位置來填充對象岸裙。
- 在Removed情形下猖败,我們從字典中移除當(dāng)前網(wǎng)格代表的對象并銷毀它。
System.Collections.Generic.Dictionary<SurfaceId, GameObject> spatialMeshObjects = new System.Collections.Generic.Dictionary<SurfaceId, GameObject>();
private void OnSurfaceChanged(SurfaceId surfaceId, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime)
{
switch (changeType)
{
case SurfaceChange.Added:
case SurfaceChange.Updated:
if (!spatialMeshObjects.ContainsKey(surfaceId))
{
spatialMeshObjects[surfaceId] = new GameObject("spatial-mapping-" + surfaceId);
spatialMeshObjects[surfaceId].transform.parent = this.transform;
spatialMeshObjects[surfaceId].AddComponent<MeshRenderer>();
}
GameObject target = spatialMeshObjects[surfaceId];
SurfaceData sd = new SurfaceData(
//系統(tǒng)返回的surface id,
//當(dāng)前對象的MeshFilter組件
target.GetComponent<MeshFilter>() ?? target.AddComponent<MeshFilter>(),
//用于在空間中定位對象的空間錨
target.GetComponent<WorldAnchor>() ?? target.AddComponent<WorldAnchor>(),
//當(dāng)前網(wǎng)格對象的MeshCollider組件
target.GetComponent<MeshCollider>() ?? target.AddComponent<MeshCollider>(),
//每立方米網(wǎng)格三角形的數(shù)量
1000,
//bakeMeshes -如果是true降允,MeshCollider會(huì)被數(shù)據(jù)填充恩闻,反之MeshCollider為空
true
);
SurfaceObserver.RequestMeshAsync(sd, OnDataReady);
break;
case SurfaceChange.Removed:
var obj = spatialMeshObjects[surfaceId];
spatialMeshObjects.Remove(surfaceId);
if (obj != null)
{
GameObject.Destroy(obj);
}
break;
default:
break;
}
}
SpatialMapping Components 空間映射組件
下面通過一個(gè)例子來說明空間映射的使用
1.在HoloToolkit->SpatialMapping->Prefabs 中找到并添加SpatialMapping Prefabs
在SpatialMapping中能夠看到已經(jīng)存在了三個(gè)腳本組件:
- SpatialMappingObserver.cs
- SpatialMappingManager.cs
- ObjectSurfaceObserver.cs
SpatialMappingObserver.cs 主要是用于定期進(jìn)行對周圍環(huán)境進(jìn)行掃描并更新Surface數(shù)據(jù),具體可參考代碼:
// 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;
using UnityEngine.VR.WSA;
namespace HoloToolkit.Unity
{
/// <summary>
/// Spatial Mapping Observer states.
/// </summary>
public enum ObserverStates
{
/// <summary>
/// The SurfaceObserver is currently running.
/// </summary>
Running = 0,
/// <summary>
/// The SurfaceObserver is currently idle.
/// </summary>
Stopped = 1
}
/// <summary>
/// The SpatialMappingObserver class encapsulates the SurfaceObserver into an easy to use
/// object that handles managing the observed surfaces and the rendering of surface geometry.
/// </summary>
public class SpatialMappingObserver : SpatialMappingSource
{
[Tooltip("The number of triangles to calculate per cubic meter.")]
//每立方米的三角形網(wǎng)格的數(shù)量
public float TrianglesPerCubicMeter = 500f;
[Tooltip("The extents of the observation volume.")]
//設(shè)置檢測的空間體積范圍
public Vector3 Extents = Vector3.one * 10.0f;
[Tooltip("How long to wait (in sec) between Spatial Mapping updates.")]
//空間映射更新的等待時(shí)間
public float TimeBetweenUpdates = 3.5f;
[Tooltip("Recalculates normals whenever a mesh is updated.")]
public bool RecalculateNormals = false;
/// <summary>
/// Our Surface Observer object for generating/updating Spatial Mapping data.
/// </summary>
/// SurfaceObserver對象用于生成或更新空間映射數(shù)據(jù)
private SurfaceObserver observer;
/// <summary>
/// A dictionary of surfaces that our Surface Observer knows about.
/// Key: surface id
/// Value: GameObject containing a Mesh, a MeshRenderer and a Material
/// </summary>
private Dictionary<int, GameObject> surfaces = new Dictionary<int, GameObject>();
/// <summary>
/// A queue of SurfaceData objects. SurfaceData objects are sent to the
/// SurfaceObserver to generate meshes of the environment.
/// </summary>
private Queue<SurfaceData> surfaceWorkQueue = new Queue<SurfaceData>();
/// <summary>
/// To prevent too many meshes from being generated at the same time, we will
/// only request one mesh to be created at a time. This variable will track
/// if a mesh creation request is in flight.
/// 防止不同surface的網(wǎng)格同時(shí)生成剧董,置標(biāo)記位幢尚,只允許一次性生成一個(gè)surface的網(wǎng)格。
/// </summary>
private bool surfaceWorkOutstanding = false;
/// <summary>
/// Used to track when the Observer was last updated.
/// 用于跟蹤觀察者的上次更新時(shí)間
/// </summary>
private float updateTime;
/// <summary>
/// Indicates the current state of the Surface Observer.
/// </summary>
public ObserverStates ObserverState { get; private set; }
private void Awake()
{
//為每一個(gè)需要空間映射數(shù)據(jù)的空間區(qū)域在應(yīng)用中初始化一個(gè)SurfaceObserver對象
observer = new SurfaceObserver();
ObserverState = ObserverStates.Stopped;
}
/// <summary>
/// Called when the GaemObject is initialized.
/// </summary>
private void Start()
{
//通過調(diào)用SetVolumeAsSphere翅楼、SetVolumeAsAxisAlignedBox尉剩、
//SetVolumeAsOrientedBox、 或 SetVolumeAsFrustum方法可以為每個(gè)SurfaceObserver對象
//指定它們需要獲取數(shù)據(jù)的空間范圍毅臊±砭ィ可再次調(diào)用來重新設(shè)定檢測的空間范圍。
observer.SetVolumeAsAxisAlignedBox(Vector3.zero, Extents);
}
/// <summary>
/// Called once per frame.
/// </summary>
private void Update()
{
//只有在SurfaceObserver處于運(yùn)行狀態(tài)時(shí)進(jìn)行處理
if (ObserverState == ObserverStates.Running)
{
// If we don't have mesh creation in flight, but we could schedule mesh creation, do so.
if (surfaceWorkOutstanding == false && surfaceWorkQueue.Count > 0)
{
// Pop the SurfaceData off the queue. A more sophisticated algorithm could prioritize
// the queue based on distance to the user or some other metric.
//將SurfaceData從隊(duì)列中取出
SurfaceData surfaceData = surfaceWorkQueue.Dequeue();
// If RequestMeshAsync succeeds, then we have successfully scheduled mesh creation.
//當(dāng)RequestMeshAsync調(diào)用成功,則就成功調(diào)度了網(wǎng)格創(chuàng)建,置surfaceWorkOutstanding為true
//等待回調(diào)函數(shù)SurfaceObserver_OnDataReady處理完成功蜓,再將surfaceWorkOutstanding置為false
surfaceWorkOutstanding = observer.RequestMeshAsync(surfaceData, SurfaceObserver_OnDataReady);
}
// If we don't have any other work to do, and enough time has passed since the previous
// update request, request updates for the spatial mapping data.
//每隔一段時(shí)間進(jìn)行刷新园爷,查看是否發(fā)生變化
else if (surfaceWorkOutstanding == false && (Time.time - updateTime) >= TimeBetweenUpdates)
{
observer.Update(SurfaceObserver_OnSurfaceChanged);
updateTime = Time.time;
}
}
}
/// <summary>
/// Starts the Surface Observer.
/// </summary>
public void StartObserving()
{
if (ObserverState != ObserverStates.Running)
{
Debug.Log("Starting the observer.");
ObserverState = ObserverStates.Running;
// We want the first update immediately.
updateTime = 0;
}
}
/// <summary>
/// Stops the Surface Observer.
/// </summary>
/// <remarks>Sets the Surface Observer state to ObserverStates.Stopped.</remarks>
public void StopObserving()
{
if (ObserverState == ObserverStates.Running)
{
Debug.Log("Stopping the observer.");
ObserverState = ObserverStates.Stopped;
}
}
/// <summary>
/// Handles the SurfaceObserver's OnDataReady event.
/// </summary>
/// <param name="cookedData">Struct containing output data.</param>
/// <param name="outputWritten">Set to true if output has been written.</param>
/// <param name="elapsedCookTimeSeconds">Seconds between mesh cook request and propagation of this event.</param>
private void SurfaceObserver_OnDataReady(SurfaceData cookedData, bool outputWritten, float elapsedCookTimeSeconds)
{
GameObject surface;
if (surfaces.TryGetValue(cookedData.id.handle, out surface))
{
// Set the draw material for the renderer.
//設(shè)置材質(zhì)
MeshRenderer renderer = surface.GetComponent<MeshRenderer>();
renderer.sharedMaterial = SpatialMappingManager.Instance.SurfaceMaterial;
renderer.enabled = SpatialMappingManager.Instance.DrawVisualMeshes;
if (RecalculateNormals)
{
MeshFilter filter = surface.GetComponent<MeshFilter>();
if (filter != null && filter.sharedMesh != null)
{
filter.sharedMesh.RecalculateNormals();
}
}
if (SpatialMappingManager.Instance.CastShadows == false)
{
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
}
}
surfaceWorkOutstanding = false;
}
/// <summary>
/// Handles the SurfaceObserver's OnSurfaceChanged event.
///處理空間表面變化事件
/// </summary>
/// <param name="id">The identifier assigned to the surface which has changed.</param>
/// <param name="changeType">The type of change that occurred on the surface.</param>
/// <param name="bounds">The bounds of the surface.</param>
/// <param name="updateTime">The date and time at which the change occurred.</param>
private void SurfaceObserver_OnSurfaceChanged(SurfaceId id, SurfaceChange changeType, Bounds bounds, System.DateTime updateTime)
{
// Verify that the client of the Surface Observer is expecting updates.
if (ObserverState != ObserverStates.Running)
{
return;
}
GameObject surface;
//關(guān)于空間表面變化,有幾個(gè)典型情形需要處理式撼。Added狀態(tài)和Updated狀態(tài)可以使用相同的代碼處理童社,Removed狀態(tài)則使用另一種代碼來處理。
switch (changeType)
{
// Adding and updating are nearly identical. The only difference is if a new GameObject to contain
// the surface needs to be created.
//在Added和Updated情形下著隆,我們從字典中添加或者獲取代碼當(dāng)前網(wǎng)格的對象扰楼,使用必要
//的組件來創(chuàng)建一個(gè)SurfaceData結(jié)構(gòu)體,然后調(diào)用RequestMeshDataAsync方法在場景中
//使用網(wǎng)格數(shù)據(jù)和位置來填充對象美浦。
case SurfaceChange.Added:
case SurfaceChange.Updated:
// Check to see if the surface is known to the observer.
if (!surfaces.TryGetValue(id.handle, out surface))
{
// If we are adding a new surface, construct a GameObject
// to represent its state and attach some Mesh-related
// components to it.
surface = AddSurfaceObject(null, string.Format("Surface-{0}", id.handle), transform);
surface.AddComponent<WorldAnchor>();
// Add the surface to our dictionary of known surfaces so
// we can interact with it later.
surfaces.Add(id.handle, surface);
}
// Add the request to create the mesh for this surface to our work queue.
//將surface添加到隊(duì)列中弦赖,等待處理
QueueSurfaceDataRequest(id, surface);
break;
//在Removed情形下,我們從字典中移除當(dāng)前網(wǎng)格代表的對象并銷毀它浦辨。
case SurfaceChange.Removed:
// Always process surface removal events.
if (surfaces.TryGetValue(id.handle, out surface))
{
RemoveSurfaceObject(surface);
surfaces.Remove(id.handle);
}
break;
}
}
/// <summary>
/// Calls GetMeshAsync to update the SurfaceData and re-activate the surface object when ready.
/// </summary>
/// <param name="id">Identifier of the SurfaceData object to update.</param>
/// <param name="surface">The SurfaceData object to update.</param>
private void QueueSurfaceDataRequest(SurfaceId id, GameObject surface)
{
SurfaceData surfaceData = new SurfaceData(id,
surface.GetComponent<MeshFilter>(), //當(dāng)前對象的MeshFilter組件
surface.GetComponent<WorldAnchor>(), //用于在空間中定位對象的空間錨
surface.GetComponent<MeshCollider>(), //當(dāng)前網(wǎng)格對象的MeshCollider組件
TrianglesPerCubicMeter, //每立方米網(wǎng)格三角形的數(shù)量
true);
surfaceWorkQueue.Enqueue(surfaceData);
}
/// <summary>
/// Called when the GameObject is unloaded.
/// </summary>
private void OnDestroy()
{
// Stop the observer.
StopObserving();
observer.Dispose();
observer = null;
// Clear our surface mesh collection.
surfaces.Clear();
}
}
}
SpatialMappingManager.cs 主要是對Surface的材質(zhì)蹬竖,是否顯示網(wǎng)格等一些參數(shù)進(jìn)行配置獲取管理:
// 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
{
/// <summary>
/// The SpatialMappingManager class allows applications to use a SurfaceObserver or a stored
/// Spatial Mapping mesh (loaded from a file).
/// When an application loads a mesh file, the SurfaceObserver is stopped.
/// Calling StartObserver() clears the stored mesh and enables real-time SpatialMapping updates.
/// </summary>
[RequireComponent(typeof(SpatialMappingObserver))]
public partial class SpatialMappingManager : Singleton<SpatialMappingManager>
{
[Tooltip("The physics layer for spatial mapping objects to be set to.")]
public int PhysicsLayer = 31;
[Tooltip("The material to use for rendering spatial mapping data.")]
public Material surfaceMaterial;
[Tooltip("Determines if spatial mapping data will be rendered.")]
public bool drawVisualMeshes = false;
[Tooltip("Determines if spatial mapping data will cast shadows.")]
public bool castShadows = false;
/// <summary>
/// Used for gathering real-time Spatial Mapping data on the HoloLens.
/// </summary>
private SpatialMappingObserver surfaceObserver;
/// <summary>
/// Used for loading spatial mapping data from a room model.
/// </summary>
private ObjectSurfaceObserver objectSurfaceObserver;
/// <summary>
/// Time when StartObserver() was called.
/// </summary>
[HideInInspector]
public float StartTime { get; private set; }
/// <summary>
/// The current source of spatial mapping data.
/// </summary>
public SpatialMappingSource Source { get; private set; }
// Called when the GameObject is first created.
private void Awake()
{
surfaceObserver = gameObject.GetComponent<SpatialMappingObserver>();
Source = surfaceObserver;
}
// Use for initialization.
private void Start()
{
#if !UNITY_EDITOR
StartObserver();
#endif
#if UNITY_EDITOR
objectSurfaceObserver = GetComponent<ObjectSurfaceObserver>();
if (objectSurfaceObserver != null)
{
// In the Unity editor, try loading saved meshes from a model.
objectSurfaceObserver.Load(objectSurfaceObserver.roomModel);
if (objectSurfaceObserver.GetMeshFilters().Count > 0)
{
SetSpatialMappingSource(objectSurfaceObserver);
}
}
#endif
}
/// <summary>
/// Returns the layer as a bit mask.
/// </summary>
public int LayerMask
{
get { return (1 << PhysicsLayer); }
}
/// <summary>
/// The material to use when rendering surfaces.
/// </summary>
public Material SurfaceMaterial
{
get
{
return surfaceMaterial;
}
set
{
if (value != surfaceMaterial)
{
surfaceMaterial = value;
SetSurfaceMaterial(surfaceMaterial);
}
}
}
/// <summary>
/// Specifies whether or not the SpatialMapping meshes are to be rendered.
/// </summary>
public bool DrawVisualMeshes
{
get
{
return drawVisualMeshes;
}
set
{
if (value != drawVisualMeshes)
{
drawVisualMeshes = value;
UpdateRendering(drawVisualMeshes);
}
}
}
/// <summary>
/// Specifies whether or not the SpatialMapping meshes can cast shadows.
/// </summary>
public bool CastShadows
{
get
{
return castShadows;
}
set
{
if (value != castShadows)
{
castShadows = value;
SetShadowCasting(castShadows);
}
}
}
/// <summary>
/// Sets the source of surface information.
/// </summary>
/// <param name="mappingSource">The source to switch to. Null means return to the live stream if possible.</param>
public void SetSpatialMappingSource(SpatialMappingSource mappingSource)
{
UpdateRendering(false);
if (mappingSource == null)
{
Source = surfaceObserver;
}
else
{
Source = mappingSource;
}
UpdateRendering(DrawVisualMeshes);
}
/// <summary>
/// Sets the material used by all Spatial Mapping meshes.
/// </summary>
/// <param name="surfaceMaterial">New material to apply.</param>
public void SetSurfaceMaterial(Material surfaceMaterial)
{
SurfaceMaterial = surfaceMaterial;
if (DrawVisualMeshes)
{
foreach (Renderer renderer in Source.GetMeshRenderers())
{
if (renderer != null)
{
renderer.sharedMaterial = surfaceMaterial;
}
}
}
}
/// <summary>
/// Checks to see if the SurfaceObserver is currently running.
/// </summary>
/// <returns>True, if the observer state is running.</returns>
public bool IsObserverRunning()
{
return surfaceObserver.ObserverState == ObserverStates.Running;
}
/// <summary>
/// Instructs the SurfaceObserver to start updating the SpatialMapping mesh.
/// </summary>
public void StartObserver()
{
if (!IsObserverRunning())
{
surfaceObserver.StartObserving();
StartTime = Time.time;
}
}
/// <summary>
/// Instructs the SurfacesurfaceObserver to stop updating the SpatialMapping mesh.
/// </summary>
public void StopObserver()
{
if (IsObserverRunning())
{
surfaceObserver.StopObserving();
}
}
/// <summary>
/// Gets all meshes that are associated with the SpatialMapping mesh.
/// </summary>
/// <returns>
/// Collection of Mesh objects representing the SpatialMapping mesh.
/// </returns>
public List<Mesh> GetMeshes()
{
List<Mesh> meshes = new List<Mesh>();
List<MeshFilter> meshFilters = GetMeshFilters();
// Get all valid mesh filters for observed surfaces.
foreach (MeshFilter filter in meshFilters)
{
// GetMeshFilters ensures that both filter and filter.sharedMesh are not null.
meshes.Add(filter.sharedMesh);
}
return meshes;
}
/// <summary>
/// Gets all Mesh Filter objects associated with the Spatial Mapping mesh.
/// </summary>
/// <returns>Collection of Mesh Filter objects.</returns>
public List<MeshFilter> GetMeshFilters()
{
return Source.GetMeshFilters();
}
/// <summary>
/// Sets the Cast Shadows property for each Spatial Mapping mesh renderer.
/// </summary>
private void SetShadowCasting(bool castShadows)
{
CastShadows = castShadows;
foreach (Renderer renderer in Source.GetMeshRenderers())
{
if (renderer != null)
{
if (castShadows)
{
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
}
else
{
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
}
}
}
}
/// <summary>
/// Updates the rendering state on the currently enabled surfaces.
/// </summary>
/// <param name="Enable">True, if meshes should be rendered.</param>
private void UpdateRendering(bool Enable)
{
List<MeshRenderer> renderers = Source.GetMeshRenderers();
for (int index = 0; index < renderers.Count; index++)
{
if (renderers[index] != null)
{
renderers[index].enabled = Enable;
if (Enable)
{
renderers[index].sharedMaterial = SurfaceMaterial;
}
}
}
}
}
}
ObjectSurfaceObserver.cs主要用于當(dāng)處于Unity編輯環(huán)境下時(shí),加載房間模型數(shù)據(jù)流酬,來進(jìn)行測試:
-
使用device Protal可以在瀏覽器查看所掃描的房間币厕。點(diǎn)擊update可以在上面視窗中顯示查看,點(diǎn)擊save進(jìn)行將你所在的房間模型進(jìn)行保存:
-
將保存的房間模型直接加載進(jìn)unity項(xiàng)目Assets文件目錄下芽腾,再將其拖拽到ObjectSurfaceObserver.cs腳本組件的Room Model中即可在項(xiàng)目文件中保留房間模型旦装。當(dāng)在unity環(huán)境中運(yùn)行調(diào)試時(shí),可加載此房間模型數(shù)據(jù)進(jìn)行測試摊滔。
ObjectSurfaceObserver.cs詳細(xì)代碼如下:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using UnityEngine;
namespace HoloToolkit.Unity
{
public class ObjectSurfaceObserver : SpatialMappingSource
{
[Tooltip("The room model to use when loading meshes in Unity.")]
public GameObject roomModel;
// Use this for initialization.
private void Start()
{
#if UNITY_EDITOR
// When in the Unity editor, try loading saved meshes from a model.
Load(roomModel);
if (GetMeshFilters().Count > 0)
{
SpatialMappingManager.Instance.SetSpatialMappingSource(this);
}
#endif
}
/// <summary>
/// Loads the SpatialMapping mesh from the specified room object.
/// </summary>
/// <param name="roomModel">The room model to load meshes from.</param>
public void Load(GameObject roomModel)
{
if (roomModel == null)
{
Debug.Log("No room model specified.");
return;
}
GameObject roomObject = GameObject.Instantiate(roomModel);
Cleanup();
try
{
MeshFilter[] roomFilters = roomObject.GetComponentsInChildren<MeshFilter>();
foreach (MeshFilter filter in roomFilters)
{
GameObject surface = AddSurfaceObject(filter.sharedMesh, "roomMesh-" + surfaceObjects.Count, transform);
Renderer renderer = surface.GetComponent<MeshRenderer>();
if (SpatialMappingManager.Instance.DrawVisualMeshes == false)
{
renderer.enabled = false;
}
if (SpatialMappingManager.Instance.CastShadows == false)
{
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
}
// Reset the surface mesh collider to fit the updated mesh.
// Unity tribal knowledge indicates that to change the mesh assigned to a
// mesh collider, the mesh must first be set to null. Presumably there
// is a side effect in the setter when setting the shared mesh to null.
MeshCollider collider = surface.GetComponent<MeshCollider>();
collider.sharedMesh = null;
collider.sharedMesh = surface.GetComponent<MeshFilter>().sharedMesh;
}
}
catch
{
Debug.Log("Failed to load object " + roomModel.name);
}
finally
{
if (roomModel != null && roomObject != null)
{
GameObject.DestroyImmediate(roomObject);
}
}
}
}
}
2. 新增一個(gè)Cube Prefab阴绢,并且添加腳本組件CubeScript.cs
using UnityEngine;
using System.Collections;
public class CubeScript : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
//剛啟動(dòng)應(yīng)用時(shí),空間映射還沒準(zhǔn)備好艰躺,將創(chuàng)建的Cube釋放掉
void Update () {
if (transform.position.y < -3)
{
Destroy(gameObject);
}
}
}
3.在MainCamera上新增腳本組件CubeCreator.cs呻袭,用于每隔一秒鐘產(chǎn)生一個(gè)Cube,用于測試
using UnityEngine;
using System.Collections;
public class CubeCreator : MonoBehaviour {
public GameObject cubePrefab;
// Use this for initialization
void Start () {
StartCoroutine(CreateCube());
}
// Update is called once per frame
void Update () {
}
private IEnumerator CreateCube()
{
while (true)
{
float r = 1.5f;
var theta = transform.rotation.eulerAngles.y * Mathf.Deg2Rad;
var x = r * Mathf.Sin(theta);
var z = r * Mathf.Cos(theta);
Instantiate(cubePrefab,
new Vector3(x, 1, z),
Quaternion.Euler(0, transform.rotation.eulerAngles.y, z));
yield return new WaitForSeconds(1);
}
}
}
4. 在SpatialMapping上添加DrawMeshChanger.cs腳本組件,用于改變Surface的材質(zhì)描滔,一個(gè)是帶網(wǎng)格的棒妨,一個(gè)是沒有網(wǎng)格線的
using UnityEngine;
using System.Collections;
using HoloToolkit.Unity;
using UnityEngine.VR.WSA.Input;
using System;
public class DrawMeshChanger : MonoBehaviour {
GestureRecognizer recognizer;
public bool isWireframe = true;
public Material Wireframe;
public Material Occlusion;
// Use this for initialization
void Start () {
recognizer = new GestureRecognizer();
recognizer.TappedEvent += Recognizer_TappedEvent;
recognizer.StartCapturingGestures();
}
private void Recognizer_TappedEvent(InteractionSourceKind source, int tapCount, Ray headRay)
{
SpatialMappingManager.Instance.SetSurfaceMaterial(isWireframe ? Occlusion : Wireframe);
isWireframe = !isWireframe;
}
// Update is called once per frame
void Update () {
}
}
5. 運(yùn)行測試
能夠看到在現(xiàn)實(shí)世界的物體表面附著一層網(wǎng)格線,落下的Cube可以在桌子表面含长,或者落到了地面上券腔。當(dāng)在環(huán)境中進(jìn)行點(diǎn)擊,可以進(jìn)行切換Surface的材質(zhì)拘泞。當(dāng)點(diǎn)擊后可以看到網(wǎng)格消失了纷纫。
(簡書MD編輯模式下不能插入視頻,移步視頻鏈接:https://v.qq.com/x/page/e0350sn17nw.html)