HoloLens(第一代)基础知识 101:使用设备完成项目
重要
混合现实学院教程在制作时考虑到了 HoloLens(第一代)、Unity 2017 和混合现实沉浸式头戴显示设备。 因此,对于仍在寻求这些设备的开发指导的开发人员而言,我们觉得很有必要保留这些教程。 我们不会在这些教程中更新 HoloLens 2 所用的最新工具集或交互相关的内容,因此这些教程可能与较新版本的 Unity 不相符。 我们将维护这些教程,使之持续适用于支持的设备。 已经为 HoloLens 2 发布了一系列新教程。
本教程引导你完成一个在 Unity 中生成的完整项目,该项目演示 HoloLens 上的核心 Windows Mixed Reality 功能,包括凝视、手势、语音输入、空间音效和空间映射。
完成本教程大约需要 1 小时。
设备支持
课程 | HoloLens | 沉浸式头戴显示设备 |
---|---|---|
MR 基础 101 - 使用设备完成项目 | ✔ |
开始之前
先决条件
项目文件
- 下载项目所需的文件。 需要 Unity 2017.2 或更高版本。
- 将文件解压缩到桌面或其他易于访问的位置。 将文件夹名称保留为 Origami。
注意
如果要在下载源代码之前查看它,可以在 GitHub 上查看。
第 1 章 -“全息”世界
在本章中,我们将设置第一个 Unity 项目,并逐步完成生成和部署过程。
目标
- 设置 Unity 进行全息开发。
- 制作全息影像。
- 查看制作的全息影像。
说明
- 启动 “Unity”。
- 选择打开。
- 输入先前解压缩的“Origami”文件夹所在的位置。
- 选择 “Origami”,然后单击“选择文件夹”。
- 由于 Origami 项目不包含场景,请使用以下命令将空的默认场景保存到新文件:“文件” / “场景另存为”。
- 将新场景命名为“Origami”,然后按“保存”按钮。
设置主虚拟相机
- 在“层次结构面板”中,选择“主摄像头” 。
- 在“检查器”中,将其转换位置设置为“0,0,0”。
- 找到“清除标志”属性,将下拉列表从“天空盒”更改为“纯色”。
- 单击“背景”字段以打开颜色选取器。
- 将“R、G、B 和 A”设置为“0” 。
设置场景
- 在“层次结构”面板中,单击“创建”和“创建空白项”。
- 右键单击新的“GameObject”并选择“重命名”。 将 GameObject 重命名为“OrigamiCollection”。
- 从“项目”面板中的“Holograms”文件夹(展开“资产”并选择“Holograms”,或双击“项目”面板中的“Holograms”文件夹):
- 将 “Stage” 拖放到“层次结构”中,使之成为 “OrigamiCollection” 的子项。
- 将“Sphere1”拖放到“层次结构”中,使之成为“OrigamiCollection”的子项。
- 将“Sphere2”拖放到“层次结构”中,使之成为“OrigamiCollection”的子项。
- 在“层次结构”面板中右键单击“定向光”对象,然后选择“删除”。
- 从“Holograms”文件夹中,将“Lights”拖放到“层次结构”面板的根目录中。
- 在“层次结构”中,选择“OrigamiCollection”。
- 在“检查器”中,将转换位置设置为“0, -0.5, 2.0”。
- 在 Unity 中按“播放”按钮预览全息影像。
- 预览窗口中应会显示 Origami 对象。
- 再次按“播放”以停止预览模式。
将项目从 Unity 导出到 Visual Studio
在 Unity 中,选择“文件”>“生成设置”。
在“平台”列表中选择“通用 Windows 平台”,然后单击“切换平台”。
将“SDK”设置为“通用 10”,并将“生成类型”设置为“D3D”。
选中“Unity C# 项目”。
单击“添加开放式场景”以添加场景。
单击“生成”。
在出现的文件资源管理器窗口中,创建名为“App”的新文件夹。
单击“App”文件夹。
按“选择文件夹”。
完成 Unity 设置后,将出现一个文件资源管理器窗口。
打开“App”文件夹。
打开(双击)“Origami.sln”。
在 Visual Studio 中使用顶部工具栏,将目标从“调试”更改为“发布”,并从“ARM”更改为“X86”。
单击“设备”按钮旁边的箭头,然后选择“远程计算机”以通过 Wi-Fi 进行部署。
- 将“地址”设置为 HoloLens 的名称或 IP 地址。 如果你不知道自己的设备 IP 地址,可以在“设置”>“网络和 Internet”>“高级选项”中找到,或询问 Cortana“你好小娜,我的 IP 地址是什么?”
- 如果 HoloLens 是通过 USB 连接的,你可以改为选择“设备”以通过 USB 进行部署。
- 将“身份验证模式”保留为“通用”。
- 单击“选择”
单击“调试”>“开始执行(不调试)”或按 Ctrl+F5。 如果这是你第一次部署到设备,需要将设备与 Visual Studio 配对。
Origami 项目现在将生成并部署到 HoloLens,然后运行。
戴上 HoloLens 并环顾四周,以观看新的全息影像。
第 2 章 - 凝视
本章介绍与全息影像交互的三种方式中的第一种 - 凝视。
目标
- 使用世界锁定光标来可视化凝视。
说明
- 返回到 Unity 项目,如果“生成设置”窗口仍然打开,请将其关闭。
- 在“项目面板”中选择“Holograms”文件夹。
- 将“Cursor”对象拖放到“层次结构面板”中的根级别。
- 双击“Cursor”对象以仔细查看。
- 在“项目”面板中右键单击“Scripts”文件夹。
- 单击“创建”子菜单。
- 选择“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 的物理引擎打开重力,使纸球掉落。
目标
- 使用“选择”手势控制全息影像。
说明
我们首先创建一个脚本,然后可以检测“选择”手势。
- 在 “Scripts” 文件夹中,创建名为 “GazeGestureManager” 的脚本。
- 将“GazeGestureManager”脚本拖放到“层次结构”中的“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。
- 注视某个球体。
- 执行选择手势,观察球体掉落到下方的表面上。
第 4 章 - 语音
在本章中,我们将添加对两个语音命令的支持:“重置世界”(将掉落的球体返回其原始位置)和“掉落球体”(使球体掉落)。
目标
- 添加始终在后台聆听的语音命令。
- 创建对语音命令做出反应的全息影像。
说明
- 在“Scripts”文件夹中,创建名为“SpeechManager”的脚本。
- 将“SpeechManager”脚本拖放到“层次结构”中的“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 中,从顶部菜单中选择“编辑”>“项目设置”>“音频”
- 在右侧的“检查器”面板中,找到“空间定位器插件”设置并选择“MS HRTF 空间定位器”。
- 从“项目”面板上的“Holograms”文件夹中,将“Ambience”对象拖放到“层次结构”面板中的“OrigamiCollection”对象上。
- 选择“OrigamiCollection”,并在“检查器”面板中找到“音频源”组件。 更改这些属性:
- 选中“空间化”属性。
- 选中“唤醒时播放”。
- 朝右侧拖动滑块,将“空间混合”更改为“3D”。 移动滑块时,值应从 0 更改为 1。
- 选中“循环”属性。
- 展开“3D 音效设置”,然后为“多普勒级别”输入“0.1”。
- 将“音量衰减”设置为“对数衰减”。
- 将“最大距离”设置为“20”。
- 在“Scripts”文件夹中,创建名为“SphereSounds”的脚本。
- 将“SphereSounds”拖放到“层次结构”中的“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 的“项目”面板中单击“Holograms”文件夹。
- 将“空间映射”资产拖放到“层次结构”的根目录中。
- 在“层次结构”中单击“空间映射”对象。
- 在“检查器面板”中更改以下属性:
- 选中“绘制视觉网格”框。
- 找到“绘制材料”并单击右侧的圆圈。 在顶部的搜索字段中输入“线框”。 单击结果,然后关闭窗口。 执行此操作时,“绘制材料”的值应设置为“线框”。
- 导出、生成应用并将其部署到 HoloLens。
- 当应用运行时,线框网格将覆盖现实世界。
- 观察滚动的球体如何从场地掉落到地面上!
现在我们演示如何将 OrigamiCollection 移到新位置:
- 在“Scripts”文件夹中,创建名为“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 章 - 全息的乐趣
目标
- 揭示全息地下世界的入口。
说明
现在我们演示如何揭示全息地下世界:
- 从“项目”面板中的“Holograms”文件夹中:
- 将“Underworld”拖放到“层次结构”中,使之成为“OrigamiCollection”的子项。
- 在“Scripts”文件夹中,创建名为“HitTarget”的脚本。
- 在“层次结构”中,展开“OrigamiCollection”。
- 展开“Stage”对象并选择“Target”对象(蓝色扇形)。
- 将“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 中,选择“Target”对象。
- 现在,“命中目标”组件上会显示两个公共属性,它们需要引用我们场景中的对象:
- 将“Underworld”从“层次结构”面板拖放到“命中目标”组件上的“地下世界”属性。
- 将“Stage”从“层次结构”面板拖放到“命中目标”组件上的“要隐藏的对象”属性。
- 导出、生成并部署应用。
- 将折纸藏品放在地面上,然后使用“选择”手势使球体掉落。
- 当球体命中目标(蓝色扇形)时,会发生爆炸。 藏品将会隐藏,并出现一个通往地下世界的洞口。
结束
本教程到此结束!
你已了解:
- 如何在 Unity 中创建全息应用。
- 如何使用凝视、手势、语音、音效和空间映射。
- 如何使用 Visual Studio 生成和部署应用。
现在你可以开始创建自己的全息体验了!