本文我們將通過Unity創建一個包括:凝視(gaze)、手勢(gesture)、語音輸入(voice)、空間聲音(spatial sound)和空間映射(spatial mapping)的完整的 Hololens 項目,並在 Hololens Emulator 模擬器上運行。
譯者注:本文與 Holograms 101 類似,區別在於部署部分,本文 101E 是使用模擬器運行,101 是直接使用 HoloLens。
目錄:
Chapter0 - 預先準備
開發環境
一個正確配置Hololens 開發環境的 Win10 PC機。
項目文件
Chapter1 - “Holo” World
在這個章節中,我們將新建一個Unity項目,並且走一遍build和deploy的流程。
- 準備
- 打開 Unity。
- 點擊 Open。
- 找到你之前解壓的 Origami 文件夾。
- 選中 Origami,然後點擊 Select Folder。
- 保存當前 Scene : File > Save Scene As。
- 把 Scene 命名爲 Origami ,然後單擊 Save。
- 設置 Main Camera
- 選中 Main Camera。
- 設置其 Transform 組件中 Position 屬性爲 (0, 0, 0)。
- 找到 Clear Flags 屬性, 把其值從 Skybox 改爲 Solid color。
- 修改 Background 屬性,其顏色 RGBA 爲(0, 0, 0, 0)。
- 修改 Clipping Planes 屬性的 Near 值爲 0.85。(譯者注:參考Holograms 100)
- 設置場景
- 在 Hierarchy 中, 單擊 Create > Create Empty。
- 右鍵單擊新的 GameObject 選擇 Rename。 將 GameObject 重命名爲 OrigamiCollection。
- 從 Project 面板中的 Holograms 文件夾裏:
- 拖拽 Stage 進入 Hierarchy ,並作爲 OrigamiCollection 的子物體。
- 拖拽 Sphere1 進入 Hierarchy, 並作爲 OrigamiCollection 的子物體。
- 拖拽 Sphere2 進入 Hierarchy, 並作爲 OrigamiCollection 的子物體。
- 右鍵單擊 Directional Light 物體,選擇 Delete 刪除。
- 從 Holograms 文件夾中, 拖拽 Lights 到 Hierarchy 面板的根部。
- 選中 OrigamiCollection。
- 修改其 Transform 組件中的 Position 屬性爲 (0, -0.5, 2.0)。
- 單擊 Play 按鈕,看看現在效果如何。
- 再次單擊 Play 按鈕,退出預覽模式。
- 導出項目到Visual Studio 2015
- 選擇 File > Build Settings。
- 把 運行平臺 即 Platform 修改爲 Windows Store 並且單擊 Switch Platform。.
- 設置 SDK 爲 Universal 10 ,Build Type 爲 D3D。
- UWP SDK 可以選 Latest installed。(譯者注:最好與你裝VS2015時安裝的那個版本一致,否則VS會提示項目需要更新)
- 勾上 Unity C# Projects。
- 單擊 Add Open Scenes ,添加當前場景.
- 單擊 Player Settings….
- 在 Inspector 面板裏, 選中 Windows Store logo。接着展開 Publishing Settings。
- 在 Capabilities 部分, 選中 Microphone 和 SpatialPerception。
- 回到 Build Settings 窗口, 單擊 Build。
- 新建一個文件夾,命名爲 APP。
- 單擊選擇 App 文件夾。
- 單擊 Select Folder 按鈕。
- 當Unity 完成 Building 的時候,會自動打開一個資源管理器。
- 打開 APP 文件夾。
- 打開生成的 Visual Studio Solution。
- 在VS工具欄中,把 target 從 Debug 改爲 Release,從 ARM 改爲 x86。
- 單擊 本地計算機(Local Machine) 旁邊的小箭頭,將部署目標變爲 Hololens Emulator。
- 點擊 調試(Debug) > 開始執行不調試(Debug without debugging)
- 等待一會後,Emulator 將會打開 Origami 項目。當第一次運行Emulator時,它可能需要加載15分鐘左右。當開始後,注意不要關閉它。
Hololens Emulator 效果圖
Chapter 2 - Gaze(凝視)
在這個章節中,我們將介紹Hololens中的三種交互方式之一,Gaze(凝視)。
- 從 Holograms 文件夾中拖拽 Cursor 物體進入 Hierarchy。
- 在 Scripts 文件夾中新建一個 Script,命名爲 WorldCursor。
- 把 WorldCursor 添加給 Cursor。
- 雙擊 WorldCursor ,在VS中編輯腳本。
- 複製並粘貼以下腳本,並保存。
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 thecursor 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;
}
}
}
- 再次build該項目,並在 VS 中部署到模擬器運行查看效果(模擬器可以不用關閉~以便於下一次調試)。
Gaze 效果圖 注意圖中箭頭所指
Chapter3 - Gestures(手勢)
在本章節中,我們將添加手勢控制的功能。當用戶選擇到一個場景中的紙球時,我們將使紙球基於Unity中的物理引擎下落。
- 在 Scripts 文件夾下,新創建一個腳本 GazeGestureManager。
- 把腳本 GazeGestureManager 添加給 OrigamiCollection 物體。
- 編輯 GazeGestureManager 腳本,添加以下代碼:
using UnityEngine;
using UnityEngine.VR.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 Start()
{
Instance = this;
// Set up a GestureRecognizer to detect Select gestures.
recognizer = new GestureRecognizer();
recognizer.TappedEvent += (source, tapCount, ray) =>
{
// Send an OnSelect message to the focused object and its ancestors.
if (FocusedObject != null)
{
FocusedObject.SendMessageUpwards("OnSelect");
}
};
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();
}
}
}
- 再新建一個腳本,命名爲 SphereCommands。
- 將腳本 SphereCommands 添加給 Sphere1 和 Sphere2 物體。
- 編輯 SPhereCommands 腳本,添加以下代碼:
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 Emulator上進行測試。
- 凝視一個紙球。
- 按下 Spacebar 鍵,用來模擬選擇的手勢。觀察效果,小球落下。
Chapter4 - Voice 聲音控制
在本章節中,我們將添加兩種聲音控制命令:
- “Reset World”:讓已經降落的小球回到一開始的位置。
- “Drop Sphere”:讓小球降落
- 首先,新建一個腳本 SpeechManager。
- 把腳本 SpeechManager 添加給 OrigamiCollection 物體。
- 打開腳本 SpeechManager 並添加如下代碼:
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");
}
});
// 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();
}
}
}
- 打開腳本 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)
{
DestroyImmediate(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 Emulator上進行測試。
- 調整視點,凝視一個小球。然後說:”Drop Sphere!”
- 說 “Reset World” 讓小球回到原來的位置。
Chapter5 - Spatial sound 空間聲音
在本章節中,我們將爲應用添加音樂,並給關鍵動作添加音效。我們將使用 Spatial sound 來給聲音一個三維空間中具體的方位。
- 在 Unity Editor 中,選擇 Edit > Project Settings > Audio。
- 找到 Spatializer Plugin 選項並選擇 MS HRTF Spatializer。
- 從 Holograms 文件夾拖拽 Ambience 物體到 Hierarchy 面板中的 OrigamiCollection 物體上。
- 選中 OrigamiCollection 物體,找到 Audio Source 組件,修改以下屬性:
- 勾選 Spatialize
- 勾選 Play on Awake
- 把 Spatial Blend 的滑塊拖到最右,使其值爲 3D。
- 勾選 Loop。
- 展開 3D Sound Settings,把 Doppler Level 的值修改爲 0.1。
- 把 Volume Rolloff 設置爲 Logarithmic Rolloff。
- 把 Max Distance 設置爲 20。
- 新建一個腳本命名爲 SphereSounds。
- 把腳本 SphereSounds 添加給 Sphere1 和Sphere2。
- 打開腳本添加如下代碼:
using UnityEngine;
public class SphereSounds : MonoBehaviour
{
AudioSource audioSource = null;
AudioClip impactClip = null;
AudioClip rollingClip = null;
bool rolling = false;
void Start()
{
// Add an AudioSource component and set up some defaults
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.playOnAwake = false;
audioSource.spatialize = true;
audioSource.spatialBlend = 1.0f;
audioSource.dopplerLevel = 0.0f;
audioSource.rolloffMode = AudioRolloffMode.Logarithmic;
audioSource.maxDistance = 20f;
// Load the Sphere sounds from the Resources folder
impactClip = Resources.Load<AudioClip>("Impact");
rollingClip = 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)
{
audioSource.clip = impactClip;
audioSource.Play();
}
}
// Occurs each frame that this object continues to collide with another object
void OnCollisionStay(Collision collision)
{
Rigidbody rigid = this.gameObject.GetComponent<Rigidbody>();
// Play a rolling sound if the sphere is rolling fast enough.
if (!rolling && rigid.velocity.magnitude >= 0.01f)
{
rolling = true;
audioSource.clip = rollingClip;
audioSource.Play();
}
// Stop the rolling sound if rolling slows down.
else if (rolling && rigid.velocity.magnitude < 0.01f)
{
rolling = false;
audioSource.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;
audioSource.Stop();
}
}
}
- 導出項目,部署到 Hololens Emulator上進行測試。
- 戴上耳機體驗3D效果,靠近遠離感受音量的變化。
Chapter6 - Spatial mapping 空間映射
在本章節,我們將通過 Spatial mapping 去實現把場景中的物體放置在真實世界的真實物體之上。
- 把 Holograms 文件夾中的 Spatial Mapping 拖入 Hierarchy。
- 選中 Spatial Mapping,修改其以下屬性:
- 勾選 Draw Visual Meshes
- 爲 Draw Material 指定 wireframe 材質。
- 導出項目,部署到 Hololens Emulator上進行測試。
- 當應用運行時,可以觀察到一個提前掃描過的房間的網格以 wireframe 材質的樣式出現了。
- 觀察小球是如何掉出桌子,掉到地上的。
現在我將教給你如何讓 ORIgamiCollections 移動到一個新的位置。
- 新建一個腳本,命名爲 TapToPlaceParent。
- 把腳本拖給 Stage 物體。
- 打開腳本進行編輯,添加如下代碼:
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;
}
}
}
}
- 導出項目,部署到 Hololens Emulator上進行測試。
- 現在,你可以移動物體到一個新的位置:首先凝視物體,然後使用選擇手勢(Spacebar),把視點移動到新的位置後,再次使用選擇手勢即可。
The end
到此結束啦~
你已經學會了:
- 如何在Unity裏創建一個Hololens應用。
- 如何使用gaze, gesture, voice, sounds, 以及 spatial mapping。
- 如何導出項目、部署應用到模擬器上。
相信你現在已經準備好開發自己的 Hololens 應用啦~