Holograms 101
該教程將帶領你走完 Hologram 創建 的全過程。整個開發分成如下幾個部分: 聚焦輸入 gaze, 手勢輸入gesture , 聲音輸入voice input, 映射聲音spatial sound and 映射地圖spatial mapping.
整個教程大概耗時1個小時.
開始前的要求:
工程文件
- 下載該工程所需要的 開發文件
- 解壓下載的 開發文件,將該文件夾命名為 Origami
Chapter 1 - "Holo" world
在這一章節,我們將要配置 我們的 第一個 Unity 工程,並走過 整個Build (編譯)和 deploy(部署)過程
目標
- 設置Unity環境,以適應Hologram開發
- 創建一個Hologram
- 看到創建出來的Hologram工程效果
步驟
- 打開Unity
- 點擊 Open.
- 找到之前解壓並重命名為 Origami 文件夾
- 選擇 Origami 並點擊 Select Folder.
- 因為新工程 Origami project 並沒有包含任何 scene, 所以需要保存當前的默認 scene (default scene)為一個新的scene: File / Save Scene As.
- 將新的scene命名為 Origami 並點擊 Save 按鈕.
配置主虛擬鏡頭(main virtual camera)
- 在 Hierarchy Panel 中, 選中 Main Camera.
- 在右側的 Inspector 選項欄中,將 position 配置為 0,0,0.
- 在當前的 Clear Flags 屬性中,將下拉框中的設置從Skybox 改為 Solid color
- 將 Background 屬性點開
- 將 R, G, B, 和 A 設置為0

設置場景scene
- 在 Hierarchy Panel 中, 點擊 Create 並 Create Empty
- 新創建的文件夾名字是 GameObject,重命名該文件夾為 OrigamiCollection
- 從 Project Panel 的 Holograms 文件夾中:
- 拖拽 Stage 到 Hierarchy Panel 中,作為 OrigamiCollection 的子項
- 拖拽 Sphere1 到 Hierarchy Panel 中,作為 OrigamiCollection 的子項
- 拖拽 Sphere2 到 Hierarchy Panel 中,作為 OrigamiCollection 的子項
- 刪除 Hierarchy Panel 中的 Directional Light 項
- 從 Holograms 文件夾中,拖拽 Lights 項到 Hierarchy Panel 的根目錄
- 選中 Hierarchy Panel 的 OrigamiCollection 目錄
- 在右側的 Inspector 欄,設置 tranform的position值為0, -0.5, 2.0.
- 點擊 項目 正上方的 “播放”
按鈕,可以預覽效果 
- 再次點擊 “播放”按鈕,關閉預覽
從Unity導出工程到Visual Studio
- 選擇 File > Build Settings.
- 選擇 Windows Store
- SDK 選擇 Universal 10 並選擇 Build Type 為 D3D.
- 選中 Unity C# Projects.
- 點擊 Add Open Scenes 按鈕,添加當前的視圖到Scenes In Build 欄中
- 點擊 Build.
-
- 接下來會彈出一個windows窗口,在該窗口創建文件夾 App
- 單擊 App 文件夾
- 然后點擊 選擇文件夾. 就會開始編譯

- 當編譯結束,就會自動彈出編譯好的文件目錄
- 打開 App 文件夾
- 雙擊 Origami.sln.
- 在VS頂部工具欄中,修改Debug 為 Release ,並修改 ARM 為 X86 架構
- 點擊設備旁的 三角形按鈕,選擇遠程計算機( Remote Device)
- 將地址(Address) 設置為Hololens的 IP 或者 Hololens的名稱
- 設置身份驗證模式(Authentication Mode)為 通用(Universal)
- 點擊選擇( Select)
- 如果是 使用Hololens模擬器,則直接選擇HoloLens Emulator 即可。
- 緊接着開始調試
- Origami 項目將會被部署在你的Hololens上(或者Hololens 模擬器上),並運行
- 帶上你的Hololens開始體驗吧!(譯者表示完全體驗不了,因為沒設備啊(┬_┬),只能仿真玩玩)


Chapter 2 - Gaze
在本節中,將會描述Hololens三種交互方式之一的 -- 凝視輸入(gaze).
目標
- 讓我們的凝視輸入可視化(視線所指會出現一個圓圈).
介紹
- 返回 Unity 工程
- 選擇 Holograms 文件夾
- 將 Cursor 組建拖入 Hierarchy panel 的根目錄中

- 右擊 Scripts 文件夾,進入Create 目錄,並選擇C# Script.

- 將新創建的腳本命名為 WorldCursor
- 選中 Cursor 組件
- 拖拽 WorldCursor 腳本到Inspector panel 中的 Cursor 組件上

- 這時候,再雙擊 WorldCursor 腳本文件,會自動打開 Visual Studio
- 復制下面的代碼到 WorldCursor.cs 文件中,保存
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
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
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;
}
}
}
|
- 進入目錄 File > Build Settings 重新生成工程
- 返回Visual Studio 解決方案中
- 這時會提示 是否需要重新加載 ,選擇是。
- 然后繼續點擊調試

- 現在可以看到視線聚焦之處,有一個紅色圓環。

Chapter 3 - Gestures
在這一章節中,我們將學習使用 手勢輸入gestures。通過使能 Unity 的物理引擎,打開重力模擬, 當用戶選擇了一個紙球,就會讓該紙球下落。
目標
- 使用選擇手勢控制Hologram
步驟
接下來創建一個腳本,使得程序能夠檢測到 選擇手勢
- 在 Scripts 目錄中,創建一個名為 GazeGestureManager 的腳本
- 將 GazeGestureManager 腳本拖入 OrigamiCollection 目錄中

- 打開 GazeGestureManager 腳本,並復制如下code:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
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.
- 占看 OrigamiCollection 目錄
- 拖拽 SphereCommands 腳本到 Sphere1 模型上
- 拖拽 SphereCommands 腳本到 Sphere2 模型上

- 打開 visual studio 編輯,復制如下代碼到 SphereCommands 腳本中:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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;
}
}
}
|
- 重新生成Hologram
- 注視紙球
- 采用選擇手勢,查看紙球下落過程

Chapter 4 - Voice
這一章節,我們將要添加兩個語音輸入命令( voice commands ):
"Reset world": 將掉落的小球,初始化到原始位置
"Drop sphere":令小球掉落
目標
- 添加常駐后台的聲音識別命令.
- 創建一個能對聲音產生反應的應用
步驟
- 在 Scripts 目錄中,創建一個名為 SpeechManager 的腳本
- 拖拽 SpeechManager 腳本到 OrigamiCollection 目錄中
- 雙擊打開 SpeechManager 腳本
- 復制如下代碼到腳本 SpeechManager.cs 中:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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腳本
- 更新其代碼如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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();
}
}
|
- 重新build整個工程
- 注視某一個球體,說出命令 "Drop Sphere".
- 說出命令"Reset World",讓球體返回原來位置。
- (譯者表示Emulator中也是可以使用聲音來控制的,Follow me, say " Drop sphere~")
Chapter 5 - Spatial sound
在這一章節中,我們將要添加一段音樂到應用app中,然后在特定動作下,觸發音樂。我們將要使用 聲音映射spatial sound 來 將插入的音樂定位到指定的位置上。
目標
- 在我們的世界中,聽到Hologram
步驟
- 進入選項 Edit > Project Settings > Audio

- 在右邊的 Inspector Panel 中,, 找到 Spatializer Plugin 並選擇 MS HRTF Spatializer.

- 將 Holograms 文件夾中的 Ambience 模型,拖拽到 OrigamiCollection 目錄中
- 選中 OrigamiCollection 目錄,並在右邊Inspector panel找到 Audio Source ,修改如下屬性:
- 選中 Spatialize
- 選中 Play On Awake.
- 修改 Spatial Blend 為 3D
- 選中 Loop
- 展開 3D Sound Settings,並在Doppler Level 中輸入 0.1
- 設置 Volume Rolloff 為 Custom Rolloff.

- 在 Scripts 目錄中,創建一個 SphereSounds 腳本
- 將腳本 SphereSounds 拖拽到 Sphere1 和 Sphere2 模型上
- 打開 SphereSounds 腳本,並更新如下代碼:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
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.Custom;
// 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();
}
}
}
|
- 保存代碼,重新build工程
- 這時候兩個小球,相當於一個聲源,當移動視角,帶上耳機體驗的話,是能夠感覺到雙通道聲音的趕腳的!(棒)
Chapter 6 - Spatial mapping
現在我們要使用 spatial mapping ,將 我們的應用放置到物理世界中的實物上。
目標
- 將真實世界代入到虛擬世界中
- 隨意放置我們的Hologram
步驟
- 在Unity 中,選中Holograms 目錄
- 拖拽 Spatial Mapping 到 Hierarchy 的根目錄下
- 選中 Spatial Mapping
- 在右邊的 Inspector panel 中,修改如下屬性:
- 選中 Draw Visual Meshes 選項
- 將Draw Material 選項選為 "wireframe"
- 重新編譯build工程
- 當應用運行,可以看到 (網格模型)wireframe mesh 將在物理世界中顯示
- 觀察小球是怎么在當前場景下落的
下面將指導你如何將 OrigamiCollection 移動到一個新的位置:
- 在 Scripts 文件夾中,創建一個腳本名叫TapToPlaceParent.
- 在 Hierarchy 中,展開 OrigamiCollection 目錄,並選中Stage 模型
- 將腳本 TapToPlaceParent 拖拽到 Stage 模型上
- 更新代碼如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
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;
}
}
}
}
|
- 重新編譯build工程
- 現在我們應該可以通過凝視(gazing)將我們的目標重新定位。使用選擇手勢(Select gesture)就可以移動位置
Chapter 7 - Holographic fun
Objectives
- Reveal the entrance to a holographic underworld.
Instructions
Now we'll show you how to uncover the holographic underworld:
- From the Holograms folder in the Project Panel:
- Drag Underworld into the Hierarchy to be a child of OrigamiCollection.
- In the Scripts folder, create a script named HitTarget.
- In the Hierarchy, expand the OrigamiCollection.
- Expand the Stage object and select the Target object (blue fan).
- Drag the HitTarget script onto the Target object.
- Open the HitTarget script in Visual Studio, and update it to be the following:
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 objectvoid 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.SetMappingEnabled(false);}}
- In Unity, select the Target object.
- Two public properties are now visible on the Hit Target component and need to reference objects in our scene:
- Drag Underworld from the Hierarchy panel to the Underworld property on the Hit Target component.
- Drag Stage from the Hierarchy panel to the Object to Hide property on the Hit Target component.
- Export, build and deploy the app.
- Place the Origami Collection on the floor, and then use the Select gesture to make a sphere drop.
- When the sphere hits the target (blue fan), an explosion will occur. The collection will be hidden and a hole to the underworld will appear.
The end
And that's the end of this tutorial!
You learned:
- How to create a holographic app in Unity.
- How to make use of gaze, gesture, voice, sounds, and spatial mapping.
- How to build and deploy an app using Visual Studio.
You are now ready to start creating your own holographic apps!
