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 或更新版本。
    • 如果您仍然需要 Unity 5.6 支援,請使用 此版本
    • 如果您仍然需要 Unity 5.5 支援,請使用 此版本
    • 如果您仍然需要 Unity 5.4 支援,請使用 此版本
  • 將檔案解除封存到桌面或其他容易觸達的位置。 將資料夾名稱保留為 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 中的 Sphere1Sphere2 物件。
  • 在 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 建置和部署應用程式。

您現在已準備好開始建立自己的全像攝影體驗!

另請參閱