Mixed Reality Academy 자습서는 HoloLens(1세대), Unity 2017 및 Mixed Reality 몰입형 헤드셋을 염두에 두고 설계되었습니다. 따라서 이러한 디바이스 개발에 대한 지침을 계속 찾고 있는 개발자를 위해 이러한 자습서를 그대로 두는 것이 중요합니다. 이러한 자습서는 HoloLens 2 사용되는 최신 도구 집합 또는 상호 작용으로 업데이트되지 않으며 최신 버전의 Unity와 호환되지 않을 수 있습니다. 대신 지원되는 디바이스에서 계속 작동하도록 유지 관리됩니다. HoloLens 2에 대한 새로운 자습서 시리즈가 게시되었습니다.
이 자습서에서는 응시, 제스처, 음성 입력, 공간 사운드 및 공간 매핑을 포함하여 HoloLens의 핵심 Windows Mixed Reality 기능을 보여 주는 Unity에서 빌드된 전체 프로젝트를 안내합니다.
Visual Studio의 위쪽 도구 모음을 사용하여 대상을 디버그에서 릴리스 로, ARM에서 X86으로 변경합니다.
디바이스 단추 옆에 있는 화살표를 클릭하고 원격 머신 을 선택하여 Wi-Fi를 통해 배포합니다.
주소를 HoloLens의 이름 또는 IP 주소로 설정합니다. 장치 IP 주소를 모르는 경우 설정 > 네트워크 & 인터넷 > 고급 옵션을 확인하거나 Cortana에게 "안면 Cortana, 내 IP 주소는 무엇입니까?"
HoloLens가 USB를 통해 연결된 경우 USB를 통해 배포할 디바이스 를 대신 선택할 수 있습니다.
인증 모드를 유니버설로 설정된 상태로 둡니다.
선택 클릭
디버깅하지 않고 디버그 > 시작을 클릭하거나 Ctrl + F5를 누릅니다. 디바이스에 처음 배포하는 경우 Visual Studio와 페어링해야 합니다.
이제 Origami 프로젝트가 빌드되고 HoloLens에 배포된 다음 실행됩니다.
HoloLens를 켜고 주위를 둘러보면 새 홀로그램을 볼 수 있습니다.
2장 - 응시
이 장에서는 홀로그램과 상호 작용하는 세 가지 방법 중 첫 번째 방법인 응시를 소개합니다.
목표
월드 잠금 커서를 사용하여 응시를 시각화합니다.
지침
Unity 프로젝트에 돌아가기 빌드 설정 창이 열려 있는 경우 닫습니다.
프로젝트 패널에서 Holograms 폴더를 선택합니다.
Cursor 개체를 루트 수준의 계층 패널로 끌어옵니다.
Cursor 개체를 두 번 클릭하여 자세히 살펴봅니다.
프로젝트 패널에서 스크립트 폴더를 마우스 오른쪽 단추로 클릭합니다.
하위 메뉴 만들기 를 클릭합니다.
C# 스크립트를 선택합니다.
스크립트 이름을 WorldCursor로 지정합니다. 참고: 이름은 대/소문자를 구분합니다. .cs 확장을 추가할 필요가 없습니다.
계층 패널에서 Cursor 개체 를 선택합니다.
WorldCursor 스크립트를검사기 패널로 끌어서 놓습니다.
WorldCursor 스크립트를 두 번 클릭하여 Visual Studio에서 엽니다.
이 코드를 복사하여 WorldCursor.cs 에 붙여넣고 모두 저장합니다.
cs
using UnityEngine;
publicclassWorldCursor : MonoBehaviour
{
private MeshRenderer meshRenderer;
// Use this for initializationvoidStart()
{
// Grab the mesh renderer that's on the same object as this script.
meshRenderer = this.gameObject.GetComponentInChildren<MeshRenderer>();
}
// Update is called once per framevoidUpdate()
{
// 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 스크립트를 열고 다음 코드를 추가합니다.
cs
using UnityEngine;
using UnityEngine.XR.WSA.Input;
publicclassGazeGestureManager : MonoBehaviour
{
publicstatic GazeGestureManager Instance { get; privateset; }
// Represents the hologram that is currently being gazed at.public GameObject FocusedObject { get; privateset; }
GestureRecognizer recognizer;
// Use this for initializationvoidAwake()
{
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 framevoidUpdate()
{
// 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에서 스크립트를 열고 기본 코드를 다음으로 바꿉 있습니다.
cs
using UnityEngine;
publicclassSphereCommands : MonoBehaviour
{
// Called by GazeGestureManager when the user performs a Select gesturevoidOnSelect()
{
// 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 - 음성
이 챕터에서는 두 개의 음성 명령에 대한 지원을 추가합니다. 즉, 삭제된 구를 원래 위치로 반환하는 "월드 초기화"와 구가 떨어지도록 하는 "놓기 구"를 추가합니다.
목표
항상 백그라운드에서 수신 대기하는 음성 명령을 추가합니다.
음성 명령에 반응하는 홀로그램을 만듭니다.
지침
Scripts 폴더에서 SpeechManager라는 스크립트를 만듭니다.
SpeechManager 스크립트를 계층 구조의 OrigamiCollection 개체로 끌어다 놓습니다.
Visual Studio에서 SpeechManager 스크립트를 엽니다.
이 코드를 복사하여 SpeechManager.cs 에 붙여넣고 모두 저장합니다.
cs
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Windows.Speech;
publicclassSpeechManager : MonoBehaviour
{
KeywordRecognizer keywordRecognizer = null;
Dictionary<string, System.Action> keywords = new Dictionary<string, System.Action>();
// Use this for initializationvoidStart()
{
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();
}
privatevoidKeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
{
System.Action keywordAction;
if (keywords.TryGetValue(args.text, out keywordAction))
{
keywordAction.Invoke();
}
}
}
Visual Studio에서 SphereCommands 스크립트를 엽니다.
다음과 같이 읽도록 스크립트를 업데이트합니다.
cs
using UnityEngine;
publicclassSphereCommands : MonoBehaviour
{
Vector3 originalPosition;
// Use this for initializationvoidStart()
{
// 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 gesturevoidOnSelect()
{
// 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" commandvoidOnReset()
{
// 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" commandvoidOnDrop()
{
// Just do the same logic as a Select gesture.
OnSelect();
}
}
HoloLens에 앱을 내보내고, 빌드하고, 배포합니다.
구 중 하나를 살펴보고 "Drop Sphere"라고 말합니다.
"세계 재설정"을 말하여 초기 위치로 되돌립니다.
5장 - 공간 소리
이 챕터에서는 앱에 음악을 추가한 다음 특정 작업에 대한 사운드 효과를 트리거합니다.
공간 사운드를 사용하여 소리에 3D 공간의 특정 위치를 제공합니다.
목표
당신의 세계에서 홀로그램을 들을 수 있습니다.
지침
Unity의 위쪽 메뉴에서 프로젝트 설정 > 오디오 편집 > 을 선택합니다.
오른쪽의 검사기 패널에서 Spatializer 플러그 인 설정을 찾아 MS HRTF Spatializer를 선택합니다.
프로젝트 패널의 Holograms 폴더에서 Ambience 개체를 계층 구조 패널의 OrigamiCollection 개체로 끕니다.
OrigamiCollection을 선택하고 검사기 패널에서 오디오 원본 구성 요소를 찾습니다. 다음 속성을 변경합니다.
Spatialize 속성을 확인합니다.
깨어있는 재생을 확인합니다.
슬라이더를 오른쪽으로 끌어 공간 혼합 을 3D 로 변경합니다. 슬라이더를 이동할 때 값이 0에서 1로 변경되어야 합니다.
Loop 속성을 확인합니다.
3D 사운드 설정을 확장하고 도플러 수준에0.1을 입력합니다.
볼륨 롤오프를 로그 롤오프로 설정합니다.
최대 거리를20으로 설정합니다.
Scripts 폴더에서 SphereSounds라는 스크립트를 만듭니다.
SphereSounds를 계층 구조의 Sphere1 및 Sphere2 개체로 끌어서 놓습니다.
Visual Studio에서 SphereSounds를 열고 다음 코드를 업데이트하고 모두 저장합니다.
cs
using UnityEngine;
publicclassSphereSounds : MonoBehaviour
{
AudioSource impactAudioSource = null;
AudioSource rollingAudioSource = null;
bool rolling = false;
voidStart()
{
// 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 objectvoidOnCollisionEnter(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 objectvoidOnCollisionStay(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.elseif (rolling && rigid.velocity.magnitude < 0.01f)
{
rolling = false;
rollingAudioSource.Stop();
}
}
// Occurs when this object stops colliding with another objectvoidOnCollisionExit(Collision collision)
{
// Stop the rolling sound if the object falls off and stops colliding.if (rolling)
{
rolling = false;
impactAudioSource.Stop();
rollingAudioSource.Stop();
}
}
}
그리기 재질을 찾아 오른쪽의 원을 클릭합니다. 위쪽의 검색 필드에 "wireframe"을 입력합니다. 결과를 클릭한 다음 창을 닫습니다. 이렇게 하면 그리기 재질의 값이 Wireframe으로 설정됩니다.
HoloLens에 앱을 내보내고, 빌드하고, 배포합니다.
앱이 실행되면 와이어프레임 메시가 실제 세계를 오버레이합니다.
구체가 어떻게 무대에서 떨어져 바닥에 떨어지는지 보세요!
이제 OrigamiCollection을 새 위치로 이동하는 방법을 보여 드리겠습니다.
Scripts 폴더에서 TapToPlaceParent라는 스크립트를 만듭니다.
계층 구조에서 OrigamiCollection을 확장하고 Stage 개체를 선택합니다.
TapToPlaceParent 스크립트를 Stage 개체로 끌어옵니다.
Visual Studio에서 TapToPlaceParent 스크립트를 열고 다음으로 업데이트합니다.
cs
using UnityEngine;
publicclassTapToPlaceParent : MonoBehaviour
{
bool placing = false;
// Called by GazeGestureManager when the user performs a Select gesturevoidOnSelect()
{
// 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 framevoidUpdate()
{
// 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 폴더에서 다음을 수행합니다.
하위 세계를 계층 구조로 끌어서 OrigamiCollection의 자식으로 만듭니다.
Scripts 폴더에서 HitTarget이라는 스크립트를 만듭니다.
계층 구조에서 OrigamiCollection을 확장합니다.
Stage 개체를 확장하고 Target 개체(파란색 팬)를 선택합니다.
HitTarget 스크립트를Target 개체로 끌어옵니다.
Visual Studio에서 HitTarget 스크립트를 열고 다음으로 업데이트합니다.
cs
using UnityEngine;
publicclassHitTarget : 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 objectvoidOnCollisionEnter(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 개체를 선택합니다.
이제 적중 대상 구성 요소에 두 개의 공용 속성이 표시되며 장면에서 개체를 참조해야 합니다.
계층 구조 패널에서 적중 대상 구성 요소의 언더월드 속성으로 언더월드를 끕니다.
계층 구조 패널에서 적중 대상 구성 요소의 Object to Hide 속성으로스테이지를 끌어옵니다.
앱을 내보내고, 빌드하고, 배포합니다.
Origami 컬렉션을 바닥에 놓고 선택 제스처를 사용하여 구를 놓습니다.
구가 대상(파란색 팬)에 도달하면 폭발이 발생합니다. 컬렉션이 숨겨지고 지하에 구멍이 나타납니다.