Differences

This shows you the differences between two versions of the page.

Link to this comparison view

irva:laboratoarevr:03 [2024/10/11 13:08]
andrei.lapusteanu Created first version
irva:laboratoarevr:03 [2024/10/29 12:34] (current)
andrei.lapusteanu typos
Line 1: Line 1:
 ====== Laborator VR 03. SteamVR. Advanced input ====== ====== Laborator VR 03. SteamVR. Advanced input ======
 +
 +În acest laborator vom studia câteva aspecte mai avansate legate de input - cum îl putem citi în cod, cum ne putem defini acțiuni custom - veți reuni aceste aspecte în implementarea unui gravity gun!
 +
 +<note warning>
 +Acest laborator presupune deja că aveți setup-ul de **SteamVR** funcțional pentru un anumit headset. [[irva:​laboratoarevr:​02|Laboratorul precedent]] prezintă în detaliu acest setup, așdar urmați pașii descriși acolo dacă este cazul, aceste aspecte nu vor fi prezentate aici.
 +</​note>​
 +
 +===== Notă referitoare la sistemele de input din Unity și SteamVR =====
 +
 +Sistemul de input folosit în SteamVR se bazează pe **acțiuni**.
 +
 +Înainte să discutăm despre el, vom prezenta pe scurt cele 2 paradigme de folosire a input-ului în **Unity**. ​
 +
 +<​note>​
 +Următoarele subsecțiuni au scop informativ, nu sunt esențiale în rezolvarea laboratorului,​ dar vă introduc în modalitatea în care se prelucreză input-ul în SteamVR.
 +</​note>​
 +
 +==== Sistemul de input legacy (Unity) ====
 +
 +În sistem legacy de input din Unity testarea acțiunilor este destul de simplă în cod, dar are câteva limitări. În cel mai simplu mod, input-ul se poate testa prin polling-ul unor butoane de input specifice. ​
 +
 +<code c#>
 +public class LegacyInputExample : MonoBehaviour
 +{
 +    // Update is needed to poll every frame.
 +    private void Update()
 +    {
 +        // I want 3 buttons to call the same logic: LMB (mouse), Space (key), LCtrl (key).
 +        // In the legacy system, I need to hardcode the keycodes.
 +        if (Input.GetMouseButtonDown(0) || 
 +            Input.GetKeyDown(KeyCode.Space) || 
 +            Input.GetKeyDown(KeyCode.LeftControl))
 +        {
 +            Debug.Log("​[LegacyInputExample] Action performed!"​);​
 +        }
 +    }
 +}
 +</​code>​
 +    ​
 +==== Sistemul de input baza pe acțiuni (Unity) ===
 +
 +Noul sistem de input abstractizează partea de testare manuală a acestor input-uri prin **mapping-uri** și **acțiuni**.
 +
 +Un **mapping** definește un set de acțiuni legate de anumite input-uri **fizice** (cum ar fi tastatura, mouse-ul, controller-ele,​ etc.). Practic, un mapping este o **schemă de control** care asociază diferite acțiuni cu diverse metode de input. Puteți să vă gândiți la un mapping ca la modul în care un player interacționează cu jocul în funcție de context. Câteva exemple de mapping-uri:​
 +  * **MovementOnFoot** pentru mișcare pedestră (WASD pe tastatură sau joystick pe un controller)
 +  * **MovementInCar** pentru condusul unei mașini (**aceleași taste WASD** pe tastatură sau joystick pe un controller, dar într-un alt context!)
 +  * **MovementInBoat** pentru navigarea cu o barcă
 +
 +O **acțiune** reprezintă o interacțiune definită de utilizator care poate fi activată prin diverse tipuri de input-uri. Acțiunile se pot lega la una sau mai multe intrări fizice printr-un **binding**.
 +
 +Pentru a folosi acest sistem de input:
 +  * Trebuie să aveți pachetul **Input System** integrat în proiect
 +  * La o cale dorită în **Project window** din Unity, **click-dreapta -> Create -> Input Actions**
 +  * În acest asset puteți crea mapping-uri,​ acțiuni și binding-uri,​ un exemplu este prezentat în imaginea de mai jos.
 +  * De asemenea, este util să bifați **Generate C# Class** din inspectorul acestui asset pentru a-l utiliza mai ușor în script-urile voastre
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_newinputsystem.png?​700 |}}
 +
 +În final, următorul cod prezintă modul de utilizare al acestui sistem. Comentariile din cod ar trebuie să fie explicative.
 +
 +<code c#>
 +public class ActionsInputExample : MonoBehaviour
 +{
 +    private InputActionAssetExample playerInputActions;​
 +    private InputAction fireAction;
 +    ​
 +    // Initialize input asset.
 +    private void Awake() => playerInputActions = new InputActionAssetExample();​
 +    ​
 +    // Get the action `Fire` from the mapping `PlayerMapping`. Enable it & bind event.
 +    private void OnEnable()
 +    {
 +        fireAction = playerInputActions.PlayerMapping.Fire;​
 +        fireAction.Enable();​
 +        fireAction.performed += FirePerformed;​
 +    }
 +    ​
 +    private void OnDisable()
 +    {
 +        fireAction.Disable();​
 +        fireAction.performed -= FirePerformed;​
 +    }
 +    ​
 +    // Called whenever any of the bindings defined for the `Fire` action are called.
 +    // No need for explicit checks or the `Update` method!
 +    private void FirePerformed(InputAction.CallbackContext context) => Debug.Log("​Fired action!"​);​
 +}
 +</​code>​
 +
 +==== Sistemul de input SteamVR versus cel din Unity ====
 +
 +Sistemul de input din SteamVR **nu** se bazează în mod expres pe sistemul de input din Unity; SteamVR are un sistem propriu bazat pe acțiuni. Cu toate acestea, conceptele utilizate (acțiuni, mapări, evenimente) sunt foarte similare. Înțelegerea sistemului de input bazat pe acțiuni din Unity vă va ajuta să înțelegeți mai bine modul în care funcționează cel din SteamVR.
 +
 +===== Import schelet laborator =====
 +
 +  * Importați ultima versiune a pachetului ''​IRVA_L3_VR_SteamVR_Skeleton''​ care se găsește în folder-ul **UnityPackages** din folder-ul root al proiectului
 +  * Folder-ul **Assets -> L3_VR_SteamVR_Advanced** conține asset-urile suport pentru acest laborator. Deschideți scena ''​L3_VR_SteamVR_GravityGun''​
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_labscene.png?​400 |}}
 +
 +Această scenă conține prefab-ul de player, câteva mese pe care se află câteva obiecte, precum și un gravity gun. 
 +
 +===== Sistemul de input din SteamVR =====
 +
 +Precum a fost menționat anterior, sistemul de input din SteamVR se bazează pe **acțiuni**.
 +
 +Deschideți fereastra de editor din **Window -> SteamVR Input**. Aici o să vedeți definite:
 +  * **Action sets**, seturi de acțiuni, analog cu un **mapping** din sistemul de input din Unity. Fiecare action set are un set de acțiuni; puteți, de asemenea, crea seturi noi de acțiuni
 +  * **Actions**,​ definite pentru fiecare set de acțiuni
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrinputwindow.png?​400 |}}
 +
 +In cod puteți face referire atât la **Action sets** cât și la **Actions**. Pentru a învăța modul de utilizare, urmăriți următorii pași:
 +  * În scena suport din laborator găsiți obiectul **[SteamVRInputActionsTestingObject]** care are atașat script-ul ''​SteamVRInputActionsTesting'',​ pe care-l va trebui să-l completați
 +  * În continuare vom interoga acțiunea **GrabPinch** din action set-ul **default**. Acesta este legat de buton fizic de trigger al controller-ului (vedem imediat cum)
 +
 +==== Citire input prin polling ====
 +
 +O metodă validă pentru a realiza acest task este prin **polling**. Urmăriți comentariile din snippet pentru detalii.
 +
 +<code c#>
 +public class SteamVRInputActionsTesting : MonoBehaviour
 +{
 +    private void Update()
 +    {
 +        // Explanation:​
 +        //  - From the `_default` mapping (action set), get the `GrabPinch` action'​s state via `GetState`.
 +        //  - `SteamVR_Input_Sources` can be used to set a specific device to read from. In this case any device which has this action.
 +        var grabPinchState = SteamVR_Actions._default.GrabPinch.GetState(SteamVR_Input_Sources.Any);​
 +        Debug.Log($"​[SteamVRInputActionsTesting] Polling: grabPinchState = {grabPinchState}"​);​
 +    }
 +}
 +</​code>​
 +
 +<note tip>
 +Pe lângă ''​GetState''​ puteți să folosiți și alte metode, care vă pot indica tranziții de la ''​true''​ la ''​false''​ (sau invers), etc.
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrgrabpinchmethodspng.png?​400 |}}
 +
 +</​note>​
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_steamvr_l3_1.gif?​500 |}}
 +
 +==== Citire input folosind evenimente (metodă recomandată) ====
 +
 +O altă metodă pentru a citi același input se bazează pe **evenimente**. Recomandăm această metodă întrucât face bypass polling-ului și considerăm că este un mod mai modular de a citi acest input.
 +
 +<code c#>
 +public class SteamVRInputActionsTesting : MonoBehaviour
 +{
 +    // Subscribe to event `onChange` from the `GrabPinch` contained in the `_default` action set
 +    // and call `OnGrabPinchChanged` on invocations.
 +    private void OnEnable() => SteamVR_Actions._default.GrabPinch.onChange += OnGrabPinchChanged;​
 +    ​
 +    // Unsubscribe from event.
 +    private void OnDisable() => SteamVR_Actions._default.GrabPinch.onChange -= OnGrabPinchChanged;​
 +    ​
 +    // Method called when `onChange` from the `GrabPinch` is invoked.
 +    private void OnGrabPinchChanged(SteamVR_Action_Boolean fromAction, SteamVR_Input_Sources fromSource, bool grabPinchState)
 +    {
 +        Debug.Log($"​[SteamVRInputActionsTesting] Events: grabPinchState = {grabPinchState}"​);​
 +    }
 +}
 +</​code>​
 +
 +<note tip>
 +Pe lângă ''​onChange''​ puteți să vă abonați și la alte evenimente, care vă pot indica tranziții de la ''​true''​ la ''​false''​ (sau invers), etc.
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrgrabpinchevents.png?​400 |}}
 +
 +</​note>​
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_steamvr_l3_2.gif?​500 |}}
 +
 +==== Task-ul vostru ====
 +
 +Pentru a exersa aceste aspecte implementați una din cele două metode prezentate mai sus în script-ul ''​SteamVRInputActionsTesting''​ pentru acțiunea de **GrabPinch** și testați-o (similar GIF-urilor prezentate mai sus).
 +
 +==== Binding-uri în SteamVR ====
 +
 +Pentru a extinde funcționalitatea standard și a vă permite să ve definiți acțiuni noi, este necesar să folosiți **sistemul de binding** implementat în SteamVR.
 +
 +Din mediul **Window -> SteamVR Input** apăsați pe opțiunea **Open binding UI**. Ar trebui să vi să deschidă o nouă fereastră ce prezintă binding-ul curent pentru controller-ele pe care le folosiți.
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrbindings1.png?​500 |}}
 +
 +Editați binding-ul curent (click pe **Edit** de pe **oculus_touch (Local changes)**.
 +
 +  * În noua ferestră puteți lega **butoanele fizice** de **acțiuni** (acesta este în esență un **binding**)
 +  * În partea de sus puteți naviga prin diverse **action set-uri** (default, platformer, etc.). Ramâneți pentru moment pe cel default.
 +  * Observați în partea stăngă binding-urile deja configurate pentru **Trigger** - găsiți aici acțiunea de **Grab Pinch**
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrbindings2.png?​500 |}}
 +
 +<note tip>
 +  * Ca să legăm informația învățată:​ Acest **Grab Pinch** este o **acțiune** binded pe butonul de **Trigger**,​ conținută în **action set-ul default**
 +  * În spate, SteamVR va genera cod care ne permite să referențiem în script-urile noastre aceste elemente
 +  * Pe scurt, acesta este motivul pentru care putem referenția în cod ''​SteamVR_Actions._default.GrabPinch''​
 +</​note>​
 +
 +==== My first (new) SteamVR binding ====
 +
 +În continuare veți învăța cum să vă creați propriile acțiuni. Pentru simplitate ne vom folosi de **action set-ul default** și de butonul fizic de **Trigger**.
 +
 +Ca și exemplu, vom crea o nouă acțiune de **atingere (touch)** a trigger-ului
 +
 +<​note>​
 +Controller-ele Meta Quest 2 și 3 oferă o formă de input prin **atingerea** unora dintre butoane. Pentru a declanșa acest tip de eveniment este suficient să **țineți degetul pe buton (fără a-l apăsa)**. Ne vom folosi de acest feature în continuare.
 +</​note>​
 +
 +Pentru început, este necesar să vă definiți întâi o nouă acțiune - în acest exemplu vom crea în fereastra **SteamVR Input** o nouă acțiune **TouchTrigger** în **action set-ul default**.
 +  * Definiți noua acțiune
 +  * Apăsați pe **Save and generate**
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrbindings3.png?​400 |}}
 +
 +În continuare trebuie să realizăm binding-ul. ​
 +  * Din fereastra de binding, găsiți intrarea pentru **Trigger** si apăsați pe (+). O să vi se deschidă un prompt care vă întreabă ce mod de input doriți să asignați acestui buton fizic
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrbindings4.png?​400 |}} 
 +
 +  * Opțiunea **Button** vă expune parametrii cum ar fi **Click** și **Touch**
 +  * Opțiunea **Trigger** vă expune parametrii cum ar fi **Click**, **Touch** dar și **Pull** (valoare 0-1 cât de mult este apăsat acest trigger)
 +
 +Așadar, acest sistem be binding vă permite astfel să configurați **comportamentul logic** al **butonului fizic**.
 +  * Pentru exemplul nostru puteți alege oricare variantă - pentru simplitate am ales modul **Button**
 +  * Ar trebui acum să aveți o nouă intrare sub meniul de Trigger
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrbindings5.png?​400 |}}
 +
 +Următorul pas necesar este configurarea **acțiunii**:​
 +  * Apăsați pe **None** în drept-ul evenimentului de **Touch**
 +  * Meniul care se va deschide vă va cere să alegeți **acțiunea** pe care doriți s-o legați - alegeți noua acțiune creată, **touchtrigger**
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_l3_steamvrbindings6.png?​400 |}}
 +
 +Awesome 🎉! În acest moment toată logica necesară acestei acțiuni (și al binding-ului) este finalizată.
 +
 +==== Task-ul vostru ====
 +
 +Pentru a testa dacă această nouă acțiune a fost configurată corect, completați script-ul ''​SteamVRInputActionsTesting''​ și afișați mesaje în consolă atunci când interogați această nouă acțiune **TouchTrigger**.
 +  * Puteți utilza oricare din cele două metode prezentate (polling via ''​Update''​ sau folosind evenimente)
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_steamvr_l3_3.gif?​500 |}}
 +
 +===== Rendering stereo =====
 +
 +În aplicațiile de VR dezvolatate cu Unity se poate seta parametrul **Stereo Rendering Mode**, care specifică modalitatea de desenare a scenei în realitate virtuală - acest aspect este controlabil întrucât în general există două ecrane (unul pentru fiecare ochi) care trebuie actualizate și există câteva moduri de bază:
 +  * **Multipass**:​ Scena este desenată de două ori - o dată pentru fiecare ochi
 +  * **Single Pass**: Scena este desenată o singură dată (într-un singur render pass, într-o singură textură)
 +  * **Single Pass Instanced**:​ La fel cu **Single Pass** dar folosește și tehnica de **GPU instancing** (aceasta grupează mai eficient draw call-uri, reducându-le numărul)
 +
 +Metodele de tip **single pass** sunt cele mai eficiente din punct de vedere computațional,​ dar pot suferi de **incompatibilități cu diverse shadere**. Un exemplu folosit în acest laborator este componenta de tip **LineRenderer**,​ care nu este desenată corect în cazul desenării single pass.
 +
 +<note tip>​Pentru acest laborator va trebui să schimbați din **Edit -> Project Settings -> XR Plug-In Management -> OpenVR Stereo Rendering Mode** în **Multipass**.</​note>​
 +
 +<note important>​
 +Este recomandat să dezvoltați aplicațiile în modurile cele mai eficiente (single pass) și să rețineți această setare pentru situații similare în viitor. În cadrul acestui laborator compromisul folosind Multipass este preferat pentru a ne ușura munca.
 +</​note>​
 +
 +===== Gravity gun =====
 +
 +Pentru a aplica cunoștințele pe care le-ați învățat pe parcursul acest laborator, va trebuie să finalizați implementarea unui **gravity gun**.
 +
 +Logica de funcționare a acestui **gravity gun** este deja implimentată,​ voi va trebui să configurați și să legați input-ului necesar folosind **acțiuni** și **binding-uri**.
 +
 +Obiectul **[GravityGun]** din scena suport este de tip **Throwable**,​ așadar îi puteți face grab. 
 +
 +<note tip>
 +Vă recomandăm (pentru acest obiect în mod particulat) să faceți grab folosind doar **grip-ul** (butonul lateral), întrucât trigger-ul va fi folosit pentru logica de funcționare.
 +</​note>​
 +
 +==== Funcționare gravity gun ====
 +
 +După ce **gravity gun-ul** a **fost grabbed**, va trebui să implementați următorul comportament:​
 +  * Dacă nu este apăsat nici un alt buton, nu trebuie să faceți nimic
 +  * Dacă butonul de **trigger** este **atins (touched)**,​ iar un obiect este în raza sa de acțiune, are loc un efect de **snap** al acelui obiect - acesta este adus și fixat de arma gravitațională
 +  * Dacă butonul de **trigger** este lăsat liber (eveniment-ul eferent **touch-ului** trece din ''​true''​ în ''​false'',​ obiectul snapped este **tossed**, anume lăsat să cadă liber
 +  * Dacă un obiect este snapped, iar butonul de **trigger** este **apăsat (click)**, obiectul este **propulsat (throw)**
 +
 +{{ :​irva:​laboratoarevr:​irva_2024_vr_steamvr_l3_4.gif?​500 |}}
 +
 +==== Detalii implementare ====
 +
 +Logica de funcționare este implementată în ''​GravityGunController'',​ iar obiectele interactibile care interacționează cu gravity gun-ul implementează ''​GravityGunObject''​.
 +  * În ''​GravityGunController''​ aveți metodele ''​SnapObject'', ​ ''​TossObject''​ și ''​ThrowObject''​ deja implementate - acestea vor trebui apelate pe baza input-ului definit de voi
 +  * Veți avea nevoie în esență de **2 acțiuni** (de ex. **GravityGunTouch** și **GravityGunClick**)
 +  * Va trebui să vă definiți **2 binding-uri** (de ex. pe evenimentele de tip **Click** și **Touch** din meniul de bind-uri ​ SteamVR)
 +  * În ''​GravityGunController''​ va trebui să implementați logica pentru input-urile definite anterior
 +    * Dacă optați pentru polling pe ''​Update'',​ vedeți ce metode de input trebuie să folosiți (de ex. ''​GetStateUp'',​ ''​GetStateDown'',​ etc.)
 +    * Dacă optați pentru implementarea bazată pe evenimente, vedeți ce evenimente de input trebuie să folosiți (de ex. ''​onStateUp'',​ ''​onStateDown'',​ etc.)
 +  * Legați input-ul de metodele de ''​SnapObject'', ​ ''​TossObject''​ și ''​ThrowObject''​ pentru a finaliza implementarea
 +
 +===== Task-uri =====
 +
 +  - Implementați în ''​SteamVRInputActionsTesting''​ logica de citire a input-ului pentru acțiunea **GrabPinch** folosind oricare dintre metodele prezentate (polling, evenimente). Afișați rezultatul în consolă pentru a confirma funcționarea
 +    * Faceți referire la secțiunile //Citire input prin polling// și //Citire input folosind evenimente (metodă recomandată)//​ pentru snippet-uri din cod și explicații suplimentare
 +  - Creați-vă o nouă acțiune **TouchTrigger**,​ realizați-i binding-ul pe logica de **Touch** și implementați în ''​SteamVRInputActionsTesting''​ logica de citire a input-ului. Afișați rezultatul în consolă pentru a confirma funcționarea
 +    * Faceți referire la secțiunea //My first (new) SteamVR binding// pentru explicații suplimentare
 +  - Finalizați logica pentru **gravity gun**
 +    * Definiți-vă noile acțiuni și binding-uri
 +    * Legați input-ul în script-ul ''​GravityGunController''​ și apelați metodele ''​SnapObject'', ​ ''​TossObject''​ și ''​ThrowObject''​ pe baza acestuia
 +    * Faceți referire la capitolul //Gravity gun// (și subcapitolele acestuia) pentru explicații suplimentare
 +  - **[✨Bonus✨]** Implementați o mecanică de **rotire** a obiectului snapped de către gravity gun folosind **stick-ul direcțional (joystick)** al controller-ului
 +    * Modalitatea de transformare a input-ului oferit de joystick în rotația efectivă este la latitudinea voastră (mai precis trebuie să vedeți față de ce axe (X, Y, Z, locale, globale) rotați bazat pe valoarea input-ului
 +    * Modulați viteza de rotație în funcție de valoarea analogică (0-1) oferită de joystick
  
irva/laboratoarevr/03.1728641286.txt.gz · Last modified: 2024/10/11 13:08 by andrei.lapusteanu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0