HoloLens (第 1 代) Basics 101:使用裝置完成專案
重要
Mixed Reality Academy 教學課程是使用 HoloLens (第 1 代) 、Unity 2017 和 Mixed Reality 沉浸式頭戴式裝置所設計。 因此,對於仍在尋找這些裝置開發指引的開發人員而言,我們覺得這些教學課程很重要。 這些教學課程不會使用用於 HoloLens 2 的最新工具組或互動進行更新,而且可能與較新版本的 Unity 不相容。 系統會保留這些資訊,以繼續在支援的裝置上運作。 已針對 HoloLens 2 公佈一系列新的教學課程。
本教學課程將逐步引導您完成內建 Unity 的完整專案,示範 HoloLens 上的核心 Windows Mixed Reality 功能,包括注視、手勢、語音輸入、空間音效和空間對應。
本教學課程大約需要 1 小時才能完成。
裝置支援
課程 | HoloLens | 沉浸式頭戴裝置 |
---|---|---|
MR Basics 101:使用裝置完成專案 | ✔️ |
在您開始使用 Intune 之前
必要條件
專案檔
- 下載專案所需的 檔案 。 需要 Unity 2017.2 或更新版本。
- 將檔案解除封存到桌面或其他容易觸達的位置。 將資料夾名稱保留為 Origami。
注意
如果您想要先查看原始碼再下載,可以在 GitHub 上取得。
第 1 章 - “Holo” 世界
在本章中,我們將設定我們的第一個 Unity 專案,並逐步執行建置和部署程式。
目標
- 設定 Unity 以進行全像攝影開發。
- 製作全像投影。
- 查看您建立的全像投影。
指示
- 啟動 Unity。
- 選取 [開啟]。
- 輸入位置作為您先前未封存的 Origami 資料夾。
- 選取 [Origami ],然後按兩下 [ 選取資料夾]。
- 因為 Origami 專案不包含場景,所以使用: 檔案 / 另存場景另存新檔,將空的默認場景儲存到新檔案。
- 將新場景命名為 Origami ,然後按 [ 儲存] 按鈕。
設定主要虛擬相機
- 在 [階層面板] 中,選取 [主要相機]。
- 在 偵測器 中,將其轉換位置設定為 0,0,0。
- 尋找 Clear Flags 屬性,並將下拉式清單從 Skybox 變更為 純色。
- 按一下 [背景] 欄位,以開啟色彩選擇器。
- 將 R、G、B 和 A 設為 0。
設定場景
- 在 [ 階層] 面板中,按兩下 [ 建立 ] 和 [ 建立空白]。
- 以滑鼠右鍵按下新的 GameObject ,然後選取 [重新命名]。 將 GameObject 重新命名為 OrigamiCollection。
- 從 [項目面板] 的 [全像投影 ] 資料夾中 (,展開 [資產],然後選取 [全像投影],或按兩下 [項目面板] 中的 [全像投影] 資料夾) :
- 將 [階段 ] 拖曳至 [階層] 以作為 OrigamiCollection 的子系。
- 將 Sphere1 拖曳到階層中,成為 OrigamiCollection的子系。
- 將 Sphere2 拖曳到階層中,成為 OrigamiCollection的子系。
- 以滑鼠右鍵按兩下 [階層] 面板中的 [方向光線] 物件,然後選取 [刪除]。
- 從 [全像投影] 資料夾,將 Lights 拖曳到 [ 階層面板] 的根目錄。
- 在 [階層] 中,選取 OrigamiCollection。
- 在 Inspector 中,將轉換位置設定為 0、-0.5、2.0。
- 按 Unity 中的 [播放 ] 按鈕以預覽您的全像投影。
- 您應該會在預覽視窗中看到 Origami 物件。
- 再次按 [播放 ] 以停止預覽模式。
將專案從 Unity 導出至 Visual Studio
在 Unity 中,選取 [ 檔案 > 組建設定]。
在 [平臺] 列表中選取 [通用 Windows 平台],然後按兩下 [切換平臺]。
將 SDK 設定為 通用 10 ,並將 [組建類型] 設定為 D3D。
檢查 Unity C# 專案。
按兩下 [新增開啟場景 ] 以新增場景。
按一下 [建置]。
在出現的檔案總管視窗中,建立名為 「App」 的新資料夾 。
按兩下 [ 應用程式資料夾]。
按 [選取資料夾]。
當 Unity 完成時,會出現 檔案總管 視窗。
開啟 [應用程式 ] 資料夾。
開啟 (按兩下) Origami.sln。
使用 Visual Studio 中的頂端工具列,將目標從 [偵錯] 變更為 [ 發行 ],並將 [ARM] 變更為 [X86]。
按兩下 [裝置] 按鈕旁的箭號,然後選取 [透過Wi-Fi部署的 遠端電腦 ]。
- 將 [位址 ] 設定為 HoloLens 的名稱或 IP 位址。 如果您不知道您的裝置 IP 位址,請查看 [ 設定 > 網络] & [因特網 > 進階選項 ],或詢問 Cortana「Hey Cortana,我的 IP 地址為何?」
- 如果 HoloLens 是透過 USB 連結,您可以改為選取 [ 裝置 ] 以透過 USB 部署。
- 將 [驗證模式 ] 設定為 [通用]。
- 按一下 [選取]
點選 「偵錯開始但不偵錯」,或按 Ctrl + F5。> 如果這是第一次部署到您的裝置,您必須 將其與 Visual Studio 配對。
Origami 項目現在會建置、部署至 HoloLens,然後執行。
放置 HoloLens 並查看新的全像投影。
第 2 章 - 注視
在本章中,我們將介紹三種與全像投影互動方式的前三種方式 -- 注視。
目標
- 使用世界鎖定游標將註視可視化。
指示
- 返回 Unity 專案,如果仍開啟,請關閉 [建置設定] 視窗。
- 選取 [專案] 面板中的 [全像投影] 資料夾。
- 將 Cursor 物件拖曳至根層級的 [ 階層] 面板 。
- 按兩下 Cursor 物件,以進一步查看它。
- 以滑鼠右鍵按兩下 [專案] 面板中的 [ 腳稿 ] 資料夾。
- 按兩下 [ 建立 ] 子功能表。
- 選取 [C# 腳本]。
- 將腳本命名為 WorldCursor。 注意:名稱區分大小寫。 您不需要新增 .cs 擴展名。
- 在 [階層] 面板中選取 Cursor 物件。
- 將 WorldCursor 腳本拖放到 [偵測器] 面板中。
- 按兩下 WorldCursor 腳本,在 Visual Studio 中開啟它。
- 將此程式代碼複製並貼到 WorldCursor.cs 和 [全部儲存]。
using UnityEngine;
public class WorldCursor : MonoBehaviour
{
private MeshRenderer meshRenderer;
// Use this for initialization
void Start()
{
// Grab the mesh renderer that's on the same object as this script.
meshRenderer = this.gameObject.GetComponentInChildren<MeshRenderer>();
}
// Update is called once per frame
void Update()
{
// Do a raycast into the world based on the user's
// head position and orientation.
var headPosition = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
RaycastHit hitInfo;
if (Physics.Raycast(headPosition, gazeDirection, out hitInfo))
{
// If the raycast hit a hologram...
// Display the cursor mesh.
meshRenderer.enabled = true;
// Move the cursor to the point where the raycast hit.
this.transform.position = hitInfo.point;
// Rotate the cursor to hug the surface of the hologram.
this.transform.rotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
}
else
{
// If the raycast did not hit a hologram, hide the cursor mesh.
meshRenderer.enabled = false;
}
}
}
- 從 [檔案 > 建置設定] 重建應用程式。
- 返回先前用來部署至 HoloLens 的 Visual Studio 解決方案。
- 出現提示時,選取 [全部重載]。
- 按兩下 [ 偵錯 -> 啟動但不偵錯 ],或按 Ctrl + F5。
- 現在查看場景,並注意游標與物件圖形的互動方式。
第 3 章 - 手勢
在本章中,我們將 新增手勢的支援。 當用戶選取紙張球體時,我們會使用 Unity 物理引擎開啟重力,讓球體落落。
目標
- 使用 [選取] 手勢來控制全像投影。
指示
我們會從建立腳本開始,然後偵測到 [選取] 手勢。
- 在 [ 腳本 ] 資料夾中,建立名為 GazeGestureManager 的腳本。
- 將 GazeGestureManager 腳本拖曳至 Hierarchy 中的 OrigamiCollection 物件。
- 在 Visual Studio 中開啟 GazeGestureManager 腳本,並新增下列程序代碼:
using UnityEngine;
using UnityEngine.XR.WSA.Input;
public class GazeGestureManager : MonoBehaviour
{
public static GazeGestureManager Instance { get; private set; }
// Represents the hologram that is currently being gazed at.
public GameObject FocusedObject { get; private set; }
GestureRecognizer recognizer;
// Use this for initialization
void Awake()
{
Instance = this;
// Set up a GestureRecognizer to detect Select gestures.
recognizer = new GestureRecognizer();
recognizer.Tapped += (args) =>
{
// Send an OnSelect message to the focused object and its ancestors.
if (FocusedObject != null)
{
FocusedObject.SendMessageUpwards("OnSelect", SendMessageOptions.DontRequireReceiver);
}
};
recognizer.StartCapturingGestures();
}
// Update is called once per frame
void Update()
{
// Figure out which hologram is focused this frame.
GameObject oldFocusObject = FocusedObject;
// Do a raycast into the world based on the user's
// head position and orientation.
var headPosition = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
RaycastHit hitInfo;
if (Physics.Raycast(headPosition, gazeDirection, out hitInfo))
{
// If the raycast hit a hologram, use that as the focused object.
FocusedObject = hitInfo.collider.gameObject;
}
else
{
// If the raycast did not hit a hologram, clear the focused object.
FocusedObject = null;
}
// If the focused object changed this frame,
// start detecting fresh gestures again.
if (FocusedObject != oldFocusObject)
{
recognizer.CancelGestures();
recognizer.StartCapturingGestures();
}
}
}
- 在 Scripts 資料夾中建立另一個腳本,這次名為 SphereCommands。
- 展開 [階層] 檢視中的 OrigamiCollection 物件。
- 將 SphereCommands 腳本拖曳至 [階層] 面板中的 Sphere1 物件。
- 將 SphereCommands 腳本拖曳到 [階層] 面板中的 Sphere2 物件。
- 在 Visual Studio 中開啟文稿以進行編輯,並以下列程式代碼取代預設程式代碼:
using UnityEngine;
public class SphereCommands : MonoBehaviour
{
// Called by GazeGestureManager when the user performs a Select gesture
void OnSelect()
{
// If the sphere has no Rigidbody component, add one to enable physics.
if (!this.GetComponent<Rigidbody>())
{
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
}
- 匯出、建置應用程式並將其部署至 HoloLens。
- 查看其中一個球體。
- 執行選取手勢,並 watch 球形落在下方的介面上。
第 4 章 - 語音
在本章中,我們將新增兩個 語音命令的支援:「重設世界」,以將捨棄的球體傳回其原始位置,以及「置放球體」讓球體落下。
目標
- 新增一律在背景中接聽的語音命令。
- 建立回應語音命令的全像投影。
指示
- 在 [ 腳本 ] 資料夾中,建立名為 SpeechManager的腳本。
- 將 SpeechManager 腳本拖曳至 Hierarchy 中的 OrigamiCollection 物件
- 在 Visual Studio 中開啟 SpeechManager 腳本。
- 將此程式代碼複製並貼到 SpeechManager.cs 並 全部儲存:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Windows.Speech;
public class SpeechManager : MonoBehaviour
{
KeywordRecognizer keywordRecognizer = null;
Dictionary<string, System.Action> keywords = new Dictionary<string, System.Action>();
// Use this for initialization
void Start()
{
keywords.Add("Reset world", () =>
{
// Call the OnReset method on every descendant object.
this.BroadcastMessage("OnReset");
});
keywords.Add("Drop Sphere", () =>
{
var focusObject = GazeGestureManager.Instance.FocusedObject;
if (focusObject != null)
{
// Call the OnDrop method on just the focused object.
focusObject.SendMessage("OnDrop", SendMessageOptions.DontRequireReceiver);
}
});
// Tell the KeywordRecognizer about our keywords.
keywordRecognizer = new KeywordRecognizer(keywords.Keys.ToArray());
// Register a callback for the KeywordRecognizer and start recognizing!
keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
keywordRecognizer.Start();
}
private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
{
System.Action keywordAction;
if (keywords.TryGetValue(args.text, out keywordAction))
{
keywordAction.Invoke();
}
}
}
- 在 Visual Studio 中開啟 SphereCommands 腳本。
- 更新文稿以讀取,如下所示:
using UnityEngine;
public class SphereCommands : MonoBehaviour
{
Vector3 originalPosition;
// Use this for initialization
void Start()
{
// Grab the original local position of the sphere when the app starts.
originalPosition = this.transform.localPosition;
}
// Called by GazeGestureManager when the user performs a Select gesture
void OnSelect()
{
// If the sphere has no Rigidbody component, add one to enable physics.
if (!this.GetComponent<Rigidbody>())
{
var rigidbody = this.gameObject.AddComponent<Rigidbody>();
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
}
}
// Called by SpeechManager when the user says the "Reset world" command
void OnReset()
{
// If the sphere has a Rigidbody component, remove it to disable physics.
var rigidbody = this.GetComponent<Rigidbody>();
if (rigidbody != null)
{
rigidbody.isKinematic = true;
Destroy(rigidbody);
}
// Put the sphere back into its original local position.
this.transform.localPosition = originalPosition;
}
// Called by SpeechManager when the user says the "Drop sphere" command
void OnDrop()
{
// Just do the same logic as a Select gesture.
OnSelect();
}
}
- 匯出、建置應用程式並將其部署至 HoloLens。
- 查看其中一個球體,並說出「卸除球體」。
- 說「重設世界」,讓他們回到其初始位置。
第 5 章 - 空間音效
在本章中,我們會將音樂新增至應用程式,然後在特定動作上觸發音效。 我們將使用 空間音效 來提供 3D 空間中的特定位置。
目標
- 聆聽您世界中的全像投影。
指示
- 在 Unity 中,從頂端功能表中選取 [編輯>項目設定音訊]>
- 在右側的 [偵測器面板] 中,尋找 Spatializer 外掛程式 設定,然後選取 [MS HRTF Spatializer]。
- 從 [專案] 面板中的 [Holograms ] 資料夾中,將 [元素 ] 物件拖曳至 [階層面板] 中的 OrigamiCollection 物件。
- 選取 [OrigamiCollection ],然後在 [偵測器] 面板中尋找 [音訊來源 ] 元件。 變更下列屬性:
- 檢查 Spatialize 屬性。
- 檢查 [在喚醒時播放]。
- 將滑桿拖曳到右邊,將 空間混合 變更為 3D 。 當您移動滑桿時,值應該從 0 變更為 1。
- 檢查 Loop 屬性。
- 展開 [3D 音效設定],然後針對 Doppler 層級輸入 0.1。
- 將 [磁碟區變換 ] 設定為 [對數輪詢]。
- 將 [最大距離] 設定為 20。
- 在 [ 腳本 ] 資料夾中,建立名為 SphereSounds 的腳本。
- 將 SphereSound 拖放 至 Hierarchy 中的 Sphere1 和 Sphere2 物件。
- 在 Visual Studio 中開啟 SphereSounds ,更新下列程式代碼並 全部儲存。
using UnityEngine;
public class SphereSounds : MonoBehaviour
{
AudioSource impactAudioSource = null;
AudioSource rollingAudioSource = null;
bool rolling = false;
void Start()
{
// Add an AudioSource component and set up some defaults
impactAudioSource = gameObject.AddComponent<AudioSource>();
impactAudioSource.playOnAwake = false;
impactAudioSource.spatialize = true;
impactAudioSource.spatialBlend = 1.0f;
impactAudioSource.dopplerLevel = 0.0f;
impactAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
impactAudioSource.maxDistance = 20f;
rollingAudioSource = gameObject.AddComponent<AudioSource>();
rollingAudioSource.playOnAwake = false;
rollingAudioSource.spatialize = true;
rollingAudioSource.spatialBlend = 1.0f;
rollingAudioSource.dopplerLevel = 0.0f;
rollingAudioSource.rolloffMode = AudioRolloffMode.Logarithmic;
rollingAudioSource.maxDistance = 20f;
rollingAudioSource.loop = true;
// Load the Sphere sounds from the Resources folder
impactAudioSource.clip = Resources.Load<AudioClip>("Impact");
rollingAudioSource.clip = Resources.Load<AudioClip>("Rolling");
}
// Occurs when this object starts colliding with another object
void OnCollisionEnter(Collision collision)
{
// Play an impact sound if the sphere impacts strongly enough.
if (collision.relativeVelocity.magnitude >= 0.1f)
{
impactAudioSource.Play();
}
}
// Occurs each frame that this object continues to collide with another object
void OnCollisionStay(Collision collision)
{
Rigidbody rigid = gameObject.GetComponent<Rigidbody>();
// Play a rolling sound if the sphere is rolling fast enough.
if (!rolling && rigid.velocity.magnitude >= 0.01f)
{
rolling = true;
rollingAudioSource.Play();
}
// Stop the rolling sound if rolling slows down.
else if (rolling && rigid.velocity.magnitude < 0.01f)
{
rolling = false;
rollingAudioSource.Stop();
}
}
// Occurs when this object stops colliding with another object
void OnCollisionExit(Collision collision)
{
// Stop the rolling sound if the object falls off and stops colliding.
if (rolling)
{
rolling = false;
impactAudioSource.Stop();
rollingAudioSource.Stop();
}
}
}
- 儲存腳本,並返回 Unity。
- 匯出、建置應用程式並將其部署至 HoloLens。
- 從 [階段] 和「端對端」進一步移動,以聽到音效變更。
第 6 章 - 空間對應
現在,我們將使用 空間對應 ,將遊戲板放在真實世界中的實際物件上。
目標
- 將您的真實世界帶入虛擬世界。
- 將您的全像投影放在您最重要的位置。
指示
- 在 Unity 中,按兩下 [專案] 面板中的 [全像投影 ] 資料夾。
- 將 空間對應 資產拖曳至 階層的根目錄。
- 按兩下 [階層] 中的 [空間對應 ] 物件。
- 在 [ 偵測器] 面板中,變更下列屬性:
- 核 取 [繪製視覺網格] 方塊 。
- 找出 [繪製材質 ],然後按兩下右邊的圓形。 在頂端的搜尋欄位中輸入 「wireframe」。 按兩下結果,然後關閉視窗。 當您這樣做時,Draw Material 的值應該會設定為Wireframe。
- 匯出、建置應用程式並將其部署至 HoloLens。
- 當應用程式執行時,線框網格會重疊您的真實世界。
- 觀看滾動球如何落下階段,並落在樓層!
現在我們將示範如何將 OrigamiCollection 移至新位置:
- 在 [ 腳本 ] 資料夾中,建立名為 TapToPlaceParent 的腳本。
- 在 [階層] 中,展開 OrigamiCollection ,然後選取 Stage 物件。
- 將 TapToPlaceParent 腳本拖曳至 Stage 物件。
- 在 Visual Studio 中開啟 TapToPlaceParent 腳本,並將其更新為下列內容:
using UnityEngine;
public class TapToPlaceParent : MonoBehaviour
{
bool placing = false;
// Called by GazeGestureManager when the user performs a Select gesture
void OnSelect()
{
// On each Select gesture, toggle whether the user is in placing mode.
placing = !placing;
// If the user is in placing mode, display the spatial mapping mesh.
if (placing)
{
SpatialMapping.Instance.DrawVisualMeshes = true;
}
// If the user is not in placing mode, hide the spatial mapping mesh.
else
{
SpatialMapping.Instance.DrawVisualMeshes = false;
}
}
// Update is called once per frame
void Update()
{
// If the user is in placing mode,
// update the placement to match the user's gaze.
if (placing)
{
// Do a raycast into the world that will only hit the Spatial Mapping mesh.
var headPosition = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
RaycastHit hitInfo;
if (Physics.Raycast(headPosition, gazeDirection, out hitInfo,
30.0f, SpatialMapping.PhysicsRaycastMask))
{
// Move this object's parent object to
// where the raycast hit the Spatial Mapping mesh.
this.transform.parent.position = hitInfo.point;
// Rotate this object's parent object to face the user.
Quaternion toQuat = Camera.main.transform.localRotation;
toQuat.x = 0;
toQuat.z = 0;
this.transform.parent.rotation = toQuat;
}
}
}
}
- 匯出、建置和部署應用程式。
- 現在,您應該能夠將遊戲放在特定位置,方法是使用 [選取] 手勢,然後移至新的位置,然後再次使用 [選取] 手勢。
第 7 章 - 全像攝影功能
目標
- 顯示全像攝影下世界入口。
指示
現在我們將示範如何找出全像攝影底下世界:
- 從 [項目面板] 中的 [全像投影] 資料夾:
- 將 下層 拖曳到階層中,以成為 OrigamiCollection 的子系。
- 在 [腳本] 資料夾中,建立名為 HitTarget 的腳本。
- 在 階層中,展開 OrigamiCollection。
- 展開 Stage 對象,然後選取 [ 目標 物件] (藍色風扇) 。
- 將 HitTarget 腳本拖曳至 Target 物件。
- 在 Visual Studio 中開啟 HitTarget 腳本,並將其更新為下列內容:
using UnityEngine;
public class HitTarget : MonoBehaviour
{
// These public fields become settable properties in the Unity editor.
public GameObject underworld;
public GameObject objectToHide;
// Occurs when this object starts colliding with another object
void OnCollisionEnter(Collision collision)
{
// Hide the stage and show the underworld.
objectToHide.SetActive(false);
underworld.SetActive(true);
// Disable Spatial Mapping to let the spheres enter the underworld.
SpatialMapping.Instance.MappingEnabled = false;
}
}
- 在 Unity 中,選取 [目標 ] 物件。
- 點擊 目標 元件上現在會顯示兩個公用屬性,而且需要參考場景中的物件:
- 將 Underworld 從 [階層] 面板拖曳至 [點擊目標] 元件上的 [下層] 屬性。
- 將 [階段] 從 [階層] 面板拖曳至 [物件] 以隱藏點擊目標元件上的屬性。
- 匯出、建置及部署應用程式。
- 將 Origami 集合放在樓層,然後使用 [選取] 手勢讓球體放下。
- 當球體到達目標 (藍色風扇) 時,就會發生暴增。 集合將會隱藏,而底下世界將會有一個空洞。
結束
這就是本教學課程的結尾!
您已了解︰
- 如何在 Unity 中建立全像攝影應用程式。
- 如何使用注視、手勢、語音、音效和空間對應。
- 如何使用 Visual Studio 建置和部署應用程式。
您現在已準備好開始建立自己的全像攝影體驗!