Table of Contents

Laborator VR ​04. Meta XR. Setup și interacțiuni de bază ​

Dispozitivele dezvoltate de către Meta (în trecut, cunoscute ca și Oculus) sunt soluții `all-in-one` ce permit dezvoltarea aplicațiilor de realitate virtuală și oferă suport pentru cele mai recente funcționalități din acest domeniu, precum hand tracking, realitate mixtă (MR), audio spațializat, eye și body tracking.

Dispozitive

În mod curent, în seria Quest, regăsim 4 astfel de dispozitive ce oferă suport activ pentru dezvoltarea aplicațiilor VR, anume:

Puteți studia o comparație mai detaliată aici.

Integrări

Pentru dezvoltarea aplicațiilor de VR, există suport pentru OpenXR (aplicații native), sau SDK-uri ce oferă integrare cu game engine-urile standard, precum Unity și Unreal Engine.

Vom lucra folosind integrarea din Unity. Cel mai probabil puteți lucra pe oricare platformă, în următoarele link-uri aveți informații despre integrările folosind OpenXR sau Unreal Engine.

Tehnologii disponibile

Integrarea SDK-ului vine cu suport pentru diverse feature-uri pe care le putem utiliza, în funcție de necesitățile aplicației pe care dorim s-o dezvoltăm:

Setup mediu de dezvoltare

În imaginea de mai jos puteți studia un flow generalizat al dezvoltării aplicațiilor de VR folosind dispozitivele celor de la Meta.

Cerințe pentru setup

Configurarea celor enunțate anterior a fost deja descrisă în Laborator VR 02. SteamVR. Setup și interacțiuni de bază! În cazul în care nu aveți acest setup făcut, urmăriți pașii din subcapitolele Meta Quest App, Quest Link (cablu) și Air Link.

Meta Quest Developer Hub (MQDH)

Aplicația vă oferă setări adiționale pentru un dispozitiv Quest conectat la calculator. Folosind MQDH puteți să:

Setup Meta XR

În cele ce urmează vom prezenta modalitatea de integrare a SDK-ului Meta XR în Unity. Veți avea și voi de integrat Meta XR în proiectul Unity suport.

XR Loader

În cazul în care nu vă apare acest meniu, sau dacă sunt actualizări ale acestuia, stergeți din proiect folder-ul Assets → Project Setup → CustomXRPluginProvider si reimportați ultima versiune a pachetului IRVA_ProjectSetup_XRPluginProviderController, care se găsește în folder-ul UnityPackages din folder-ul root al proiectului, după care restartați Unity.

Instalare Meta XR SDK folosind Unity Package Manager (UPM)

Începând cu versiunea 59, pachetul de integrare se folosește de UPM (Unity Package Manager), oferind astfel o modalitate mai ușoară de integrare, realizarea rapidă a upgrade-urilor, iar probabil cel mai important, un management ușor în alegerea pachetelor esențiale de folosit (Core, Integration, Audio, etc.). Versiunea up-to-date la momentul redactării acestui text este 81.0.0.

Există Meta XR All-in-One SDK care conține toate SDK-urile menționate în secțiunea Tehnologii disponibile, dar vă recomandăm să includeți în proiectele voastre doar SDK-urile necesare.

Până în Octombrie 2023, pachetul oficial, care se putea descărca de pe Unity Asset Store era Oculus Integration SDK. Atenție, acesta a fost deprecated începând cu versiunea 57. Nu o să-l folosim pe acesta, dar pentru suport legacy acesta se poate încă descărca de pe Asset Store de aici.

Build profiles, OpenXR

Din File → Build Profiles vă recomandăm să vă creați un nou profil de build derivat din platforma Meta Quest.

O să primiți un prompt pentru instalarea backend-ului OpenXR, acceptați-l.

OpenXR reprezintă backend-ul necesar pentru a dezvolta și face build-uri folosind Meta XR. Se poate instala, în mod alternativ, din Package Manager, apăsați pe (+), selecați Add package from git URL, introduceți com.unity.xr.openxr.

Meta XR Project Setup Tool

În Unity Editor ar trebui să vă apară un nou buton Meta VR Tools (în stânga-sus, sub toolbar).

De aici puteți deschide Project Setup Tool, care vă permite să vă configurați proiectul de Unity pentru a fi compatibil cu cerințele SDK-ului.

  • Aveți aici detalii legate de cum trebuie configurat proiectul de Unity
  • Este probabil ca setări ulterioare și alte integrări să ridice probleme în acest tool, așadar reveniți și rezolvați probleme de ori câte ori este nevoie

În următorul GIF este prezentat feature-ul de tip desktop virtual – puteți astfel controla PC-ul pe care l-ați conecat cu casca. În GIF proiectul este rulat din Unity prin acest desktop virtual.

Realizare build-uri

Puteți realiza build-uri în cel mai simplu mod folosiți opțiunea de Build And Run din meniul profilului de build selectat (Meta Quest). La finalul build-ului, aplicația este instalată pe headset și pornită în mod automat. Aplicația se poate apoi regăsi în submeniul Unknown Sources.

Pentru alte variante de build, în urma creării APK-ului din Unity (așadar Build simplu, nu Build And Run), se pot folosi Meta Quest Developer Hub sau SideQuest pentru instalarea acestor aplicații pe headset (proces cunsocut ca și sideloading).

Aveți aici un ghid pentru folosirea MQDH.

Dacă întâmpinați erori de compilare la build, cel mai probabil sunt datorită script-ului GravityGunController din laboratorul 3. Pe scurt, build-ul din laboratorul curent încearcă să compileze acest script, care conține componente specifice SteamVR, iar cum SteamVR nu este inclus în build, acele componente nu sunt găsite.

Unity package-ul pentru L3 a fost actualizat pentru a rezolva această problemă, dar pentru a rezolva pe loc eroarea cuprindeți întreg script-ul GravityGunController într-o directivă de preprocesare corespunzătoare build-urilor Standalone, astfel:

#if UNITY_STANDALONE 
 
// ... script GravityGunController ...
 
#endif

Setup laborator

Import schelet laborator

Building Blocks

Veți învăța în continuare cum se pot integra diverse componente ale SDK-ului aplicația voastră: headset-ul virtual, controller-ele, hand tracking-ul, funcționalitatea de passthrough, etc.

SDK-ul a introdus recent o nouă modalitate de setup a acestor elemente printr-un meniu de Building Blocks.

Vă recomandăm să faceți drag-and-drop pentru building block-uri din meniul dedicat către ierarhia scenei și nu către Scene View, întrucât unele elemente s-ar putea parenta greșit.

Interactions Rig

În primele iterații ale sistemului de building blocks componentele player-ului trebuiau alcătuite din building block-uri separate. De exemplu, dacă doream un player care să poată folosi atât controllerele cât și mâinile pentru a interacționa cu un obiect trebuiau inserate:

Fiecare dintre acestea adăuga câte o componentă și făcea setup în mod automat în ierarhie. Spre exemplu:

În noile versiuni ale SDK-ului a fost introdus un building block numit Interactions Rig care este util întrucât unifică toate componentele menționate mai sus, nefiind necesar setup-ul individual. Introduceți-l pe acesta în scenă via drag-and-drop.

Următoarele subsecțiuni au scop informativ - vă recomandăm să le parcurgeți pentru a vă familiariza cu modul în care este configurat player-ul!

Camera Rig și OVRManager

Selectați obiectul [BuildingBlock] Camera Rig. În cadrul acestui laborator nu va trebui să-l modificați dar rețineți că acesta conține configurări esențiale atunci când o să lucrați la proiectele voastre.

Nagivagați în inspector la script-ul OVRManager. Acesta conține numeroase setări ce țin de sesiunea VR, precum:

Este locul central în care se poate configura sesiunea VR în cadrul acestui SDK.

Controllere și mâini virtuale

Obiectele OVRHands și OVRControllers conțin atașate script-uri ce implementază structurile de date ce reprezintă în esență controllerele sau mâinile virtuale și pot fi utilizate pentru a citi date legate de input.

Obiectele OVRLeftHandVisual, OVRRightHandVisual, OVRLeftControllerVisual și OVRRightControllerVisual oferă logica și asset-urile pentru reprezentarea vizuală a mâinilor, respectiv controllerelor.

Interacțiuni, interactor, interactibil

Ce este esențial de reținut este că orice interacțiune necesită pe de-o parte de un interactor, de exemplu, un HandGrabInteractor (vedeți în ierarhie [BuildingBlock] OVRInteractionComprehensive → LeftInteractions → Hand and No Controller → HandGrabInteractor) care poate interacționa doar cu un obiect care are atașat un script de tip interactibil, în cazul acesta, un HandGrabInteractable

Rețineți, de asemenea, că interactorul poate interacționa cu un interactibil de același tip - de exemplu, un interactor de tip Poke nu poate comunica cu un interactibil de tip Distance Grab.

Atenție la faptul că se face distincție între interactori/interactibile compatibili pentru controller sau mâini. De exemplu:

  • Interactorul pentru controller pentru grab este GrabInteractable
  • Interactorul pentru mâini pentru grab este HandGrabInteractable

Același principiu se aplică și pentru interactibili.

Interactibili

Adăugați în scenă building block-ul Grab Interaction.

Uitați vă în inspector la obiectul [BuildingBlock] Cube → [BuildingBlock] HandGrabInstallationRoutine. Observați că există preconfigurate script-urile necesare de tip interactibil:

Rețineți existența acestora în cazul în care aveți nevoie de interacțiuni de tip grab pe obiecte custom.

Laborator

În cadrul task-urilor din laborator veți avea de completat implementarea unei arme pe care o veți folosi pentru a trage în niște obiecte (sticle) regăsite în scena suport.

Arhitectură, script-uri

Funcționarea armei este descrisă în mod modular printr-o serie de script-uri, unele dintre acestea le veți avea de completat:

Construcție pose pentru hand grab

Dacă ați testat scena sample ComprehensiveRigExample poate ați observat faptul modelul mâinii se conformează unor pose-uri prestabilite atunci obiectele din scenă (torța și cana).

De asemenea, SDK-ul oferă suport pentru diferite dimenisuni ale mâinii (în funcție de utilizator).

Acest feature oferă un grad sporit de imersivitate, întrucât dezvoltatorii se pot asigura de faptul că interacțiunea mână-obiect virtual este desenată fără probleme cum ar fi clipping-ul.

În continuare în laborator sunt prezentați pașii necesari pentru realizarea acestui pose custom - găsiți mai multe detalii legate de proces în documenția oficială:

Meta SDK oferă un utilitar pentru a crea pose-uri custom pentru obiecte/modele artbitrare printr-un proces de înregistrare a unui snapshot al pose-ului.

În acest laborator, obiectul [Gun] are atașat doar un script Grabbable, dar momentan nu are un HandGrabInteractable. În loc să atașăm manual această componentă (care nu definește un pose al mâinii), vom crea cu acest utilitar un nou pose, care va crea la rândul său componenta HandGrabInteractable necesară.

Deschideți utilitarul din toolbar, Meta → Interaction → Hand Grab Pose Recorder.

Completați câmpurile de la secțiunea 1.

Pentru a înregistra un pose custom, urmați următorii pași (aveți și un GIF de ajutor după lista de mai jos)

Ajustări pose custom

În continuare, puteți rafina pose-ul prin manipularea încheieturilor de pe obiectul HandGrabInteractable → HandGrabPose. Ajustați poziția degetelor după caz.

Va trebui să modificați pose-ul astfel încât acesta indică apăsarea completă a trăgaciului armei. Workflow recomandat (aveți GIF mai jos):

Reguli de grabbing și de pose

Ultimele ajustări pe care trebuie să le faceți țin de regulile care determină grab-ul obiectului, precum și specificarea constrângerilor degetelor în momentul în care arma este ținută în mână.

  • Nu vrem să facem grab folosind degetul arătător (index) întrucat s-ar putea să declanșăm tragerea armei în momentul grab-ului!
  • De asemenea, cât timp ținem arma în mână am dori să constrângem pose-ul tuturor degetelor (să fie fixe), mai puțin pe cel arătător, pentru a putea interacționa cu trăgaciul armei

Pentru a realiza aceste setări, modicați de pe HandGrabInteractable atât Pinch Grab Rules cât și Palm Grab Rules astfel:

În final, ajustați pose-ul degetelor astfel (de pe HandGrabPose, Fingers Freedom):

Finalizare task hand pose custom

Pentru a valida setup-ul pentru pose-ul custom al mâinii:

Detecție eveniment grab / release armă

Pentru a realiza acest feature, va trebui în esență să detectați schimarea de stare a interactibilului atașat de armă. Rețineți că HandGrabInteractable a fost adăugat atunci când ați creat pose-ul custom.

Adăugați pentru script-ul GunHandGrabController elementele lipsă din inspector.

În script-ul GunHandGrabController va trebui să interogați starea variabilei handGrabInteractable - Pentru a realiza acest lucru, abonați-vă evenimentului numit WhenStateChanged de pe handGrabInteractable.

În event handler-ul creat, va trebui să vă folosiți de proprietățile NewState și PreviousState ale argumentului InteractableStateChangeArgs pentru a testa starea de grab / release.

// Object has been grabbed.
if (state.NewState == InteractableState.Select) { ... }
// Object has been released.
if (state.PreviousState == InteractableState.Select && 
    state.NewState      != InteractableState.Select) { ... }

Pentru a accesa interactorul care a declanșat interacțiunea și în esență obiectul tip IHand aferent, puteți interoga structura SelectingInteractors - aceasta descrie intractorii care interacționează în mod curent cu interactibilul (arma) astfel:

var handInteractor = handGrabInteractable.SelectingInteractors.FirstOrDefault();

Pentru a extrage componenta tip IHand:

if (handInteractor != null)
{
    var hand = handInteractor.Hand;
}

Urmăriți comentariile din script pentru a finaliza acest task.

Citire input hand tracking

În continuare va trebui să citiți nivelul de curl (sau gradul de îndoire a degetului) arătător, valoare cu ajutorul căreia vom determina atât rotația trăgaciului armei cât și momentul în care aceasta declașează un eveniment de tragere.

SDK-ul ne permite să citim varii proprietați ale mâinilor și degetelor, precum:

Mai multe detalii le găsiți aici

Accesați script-ul GunFingerCurlController.

Pentru a citi aceste informații ne vom folosi de datele furnizate de FingerFeatureStateProvider (deja atașate pe GunHandGrabController) – în metoda Update a script-ului curent observați că este deja implementată extragerea provider-ului curent în variabila provider.

Pentru a citi gradul de îndoire (curl) a degetului arătăror (index) putem face astfel:

var curlValue = provider.GetFeatureValue(HandFinger.Index, FingerFeature.Curl);
var curlFloat = curlValue ?? 0f;

Afișați în consolă valoarea pe care o obțineți. În continuare va trebui să setați valorile min și max (din inspector) pentru usableFingerCurlRange folosind valoarea afișată în consolă:

Normalizați valoarea curl-ului între valorile de minim și maxim obținute. De exemplu, dacă min/max slider sunt [200;250], remapați în range-ul [0;1]. În script-ul Utils aveți metoda ajutătoare Remap.

Pentru a roti trăgaciul armei, apelați metoda SetCurl de pe script-ul GunTriggerCurlController.

Restul de script se ocupă de detecția evenimentului de tragere pe baza unui prag, anume triggerThreshold. Logica deja dată apelează evenimentele de trăgaci apăsat (OnTriggerPressed) sau trăgaci lăsat liber (OnTriggerReleased).

În mod opțional puteți varia valoarea threshold-ului din inspector.

Logică tragere armă

Partea aceasta este în mare parte implementată [moment de pauză 🧘‍♂️!]. În scop informativ:

Raycast armă

Pentru a finaliza implementarea armei, va trebui să completați logica de raycast astfel încât să puteți, pe baza tragerii armei, să spargeți sticlele din scenă.

Accesați script-ul GunRaycastController.

În metoda PerformRaycast va trebui să implementați logica de raycast.

Definim un Ray din poziția raycastOrigin pe direcția forward a acestuia.

var ray = new Ray(raycastOrigin.position, raycastOrigin.forward);

Efectuăm testul tip raycast:

if (Physics.Raycast(ray, out var hitInfo, raycastDistance)) { ... }

Testați dacă hitInfo.collider are atașat un script de tip Bottle - dacă testul trece, apelați metoda Shatter de pe Bottle.

Resetare sticle

Pentru a reseta sticlele de pe cea de-a doua masă din scenă, ne vom folosi de un intractibil de tip Poke - pe scurt, această funcționează ca un buton virtal și primește input de la interactorul de tip HandPokeInteractor.

Îl aveți deja definit în scenă.

Rețineți, aceste componente, precum acest interactibil de tip Poke, se pot adăuga în scenele voastre folosind meniul Building Blocks!

Legarea se poate realiza și în cod, dar pentru acest task, folosiți-vă de evenimentele expuse în inspector de pe script-ul InteractableUnityEventWrapper atașat obiectului BottleReset_PokeInteractable. Evenimentul de interes este Select.

Legați la acesta metoda SpawnBottles din script-ul BottlesManager - îl găsiți atașat de obiectul BottlesTable în scenă.

Demo implementare task-uri

Tasks

  1. Urmăriți pașii descriși în laborator pentru a face setup SDK-ului Meta XR
  2. Importați și testați în modul Quest Link scenele sample
  3. Realizați și testați un build pe headset al uneia dintre aceste scene sample
  4. Creați un nou pose custom pentru a ține arma în mână
    1. Folosiți utilitarul Hand Grab Pose Recorder
    2. Ajustați pose-ul după înregistrarea acestuia (poziția încheieturilor degetelor)
    3. Aplicați regulile pentru grabbing și pose de pe HandGrabInteractable și HandGrabPose
  5. Completați script-ul GunHandGrabController pentru a detecta momentul în care arma este grabbed
    1. Nu uitați să asignați întâi în inspectorul acestui script componentele necesare de pe Interaction Rig!
  6. Completați script-ul GunFingerCurlController pentru a citi și aplica inputul corespuzător valorii de curl a degetului arătător
  7. Completați script-ul GunRaycastController pentru a realiza raycast-ul în urma evenimentului de tragere și adăugați logica de spargere a sticlelor în cazul în care raza se intersectează cu un obiect de tip Bottle
  8. Legați poke interactable-ul “Reset Bottles” de script-ul BottlesManager pentru a reseta sticlele
  9. [✨Bonus✨] Creați un nou poke interactor
    • Atașat de armă (de ex. în partea stângă / lângă grip)
    • Prin apăsarea acestuia se va face toggle între modul de tragere semi-automatic și automat al armei
    • La apăsare să schimbați textul (opțional și culoarea) interactorului astfel încât acesta să indice modul curent de tragere al armei
    • Extindeți logica GunFireController pentru logica de tragere tip automat (sau creați-vă propriile script-uri, după plac/necesitate)