'전체 글'에 해당되는 글 1804건

  1. 2024.01.02 [OpenXR] Two Hand Grab (XR Interaction Toolkit 2.4.3)
  2. 2024.01.02 XR-Interaction-Toolkit-Examples
  3. 2024.01.02 [OpenXR] Grab transformers / XR Interaction Toolkit 2.4.3
  4. 2024.01.02 [OpenXR] When using Two Hand Grab in XR Interaction Toolkit 2.4.3
  5. 2024.01.01 [OpenXR] Custom Hand Pose (Mirror Pose) with XR Interaction Toolkit 2.4.3
  6. 2023.12.30 XR Interaction Toolkit 2.4.3 (Hand Pose)

[OpenXR] Two Hand Grab (XR Interaction Toolkit 2.4.3)

VR/XR Interaction Toolkit 2024. 1. 2. 18:21
반응형

 
https://youtube.com/playlist?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j&si=XhyHS26tNDP6u7CA

XR Interaction Toolkit 2.4.3

XR Interaction Toolkit 2.4.3 OpenXR Oculus

www.youtube.com


새 씬을 만들고 메인 카메라를 지운후 XR Origin을 생성 합니다. 

 
 Left / Right Controller를 선택후 XR Controller를 제외한 나머지 컴포넌트를 지웁니다.

 
 
다음과 같은 구조로 만들어 주고 

 
 
핸드 프리팹을 Offset으로 가져옵니다.

 
왼손, 오른손의 Offset의 회전을 설정 합니다.

왼손 Offset
오른손 Offset

 
 
 
Left/Right Controller를 선택해

 
Model Prefab을 None으로 설정 합니다.

 
 
실행후 결과를 확인 합니다.
 

 
 


 
 

인터렉터 설정 

 
 
Direct Interactor를 검색해 

Left, Right Controller자식으로 넣어 줍니다.

 
 
 
Direct Interactor들을 선택해 

햅틱 이벤트는 꺼줍시다 (베터리 절약)

 
 
 
Sphere Collider 컴포넌트의 Radius의 값은 0.1로 설정 합니다.

 
 
 


 
빈오브젝트(Rifle)를 만들고 

 
다음과 같은 구조로 만든후 라이플 프리팹을 넣어 줍니다.

 
기본으로 부착 되어 있는 스크립트를 제거 하고 사이즈와 회전각을 조절해 다음과 같이 설정 합니다.

 
 
라이플을 선택 하고 Rigidbody와 XR Grab Interactable컴포넌트를 부착 합니다.

 
 
라이플 메쉬 콜라이더의 Convex 속성을 체크 합니다.

 
플레이 후 결과를 확인 합니다.
 

 
위와 같이 둥둥 떠다닌다면 
리지드 바디의 Is Kinematic을 체크 하거나 

 
Throw On Detach를 체크 해제 하면 됩니다.

 

 
 
 
 
콜라이더 포지션으로 잡혀 있기 때문에 

잡았을경우 Direct Interactor의 위치가 콜라이더 포지션으로 마춰 집니다.

 
 

 
 
만약 Direct Interactor의 위치를 수정 하고 다시 테스트 해보면 


 
다음과 같이 동일하게 작동 한다는것을 알수 있습니다 

 
 
Direct Interactor의 위치를 원래대로 해놓고 

 
 
다음 단계를 진행 합니다.
 


 
 
 
Rifle을 선택 한 후 빈오브젝트를 만들고 그 아래 Sphere를 생성하고 Collider는 제거 합니다.

 
XR Grab Interactable의 Attach Transform 속성에 넣어 봅니다.

 
 
다시 실행후 결과를 확인 하면 그랩 위치가 Attach Point로 마춰진다는것을 확인 합니다.
이때 우리의 Select Mode는 기본속성인 Single인것도 같이 확인 합니다.


 
 
Attach Point를 복사 합니다.

 
위치를 설정 하고 

 
Select Mode를 Multiple로 변경하고 

 
Secondary Attach Transform에 넣어 줍니다.

 
실행후 결과를 확인 합니다.
이때 두번째 잡은 손은 앞뒤로 첫번째 잡은 손은 좌우로 움직이며 물체가 회전 한다는것을 확인 합니다.

 
 
이것은 Add Default Grab Transformers가 체크 되어 있기 때문에 

 
XR General Grab Transformer 컴포넌트가 자동을 부착 되며 

 
다음 프로퍼티에 추가 됩니다.

 
 
 
이때 Two Handed Rotation Mode의 설정이 First Hand Directed Towards Second Hand 로 되어 있기 때문입니다.

첫 번째 손을 사용하여 양손 회전을 결정한 다음 두 번째 손을 향해 개체의 방향을 지정합니다.


 
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.4/api/UnityEngine.XR.Interaction.Toolkit.Transformers.XRGeneralGrabTransformer.TwoHandedRotationMode.html

Enum XRGeneralGrabTransformer.TwoHandedRotationMode    | XR Interaction Toolkit | 2.4.3

Two handed rotation mode. Assembly : solution.dll public enum TwoHandedRotationMode Fields Name Description Value FirstHandOnly Determines rotation using only first hand. 0 FirstHandDirectedTowardsSecondHand Determines two handed rotation using first hand

docs.unity3d.com

 
 
 
이외 다른 옵션은 다음과 같으며 

FirstHandOnlyDetermines rotation using only first hand.

0
FirstHandDirectedTowardsSecondHandDetermines two handed rotation using first hand and then directing the object towards the second one.

1
TwoHandedAverageDirects first hand towards second hand, but uses the two handed average to determine the base rotation.

 
각 손을 고정 시키는 방법은 제공 하지 않습니다.
 


 
대안은 무엇인가?
 

반응형
:

XR-Interaction-Toolkit-Examples

VR/XR Interaction Toolkit 2024. 1. 2. 14:19
반응형

https://github.com/Unity-Technologies/XR-Interaction-Toolkit-Examples/tree/main

 

GitHub - Unity-Technologies/XR-Interaction-Toolkit-Examples: This repository contains various examples to use with the XR Intera

This repository contains various examples to use with the XR Interaction Toolkit - GitHub - Unity-Technologies/XR-Interaction-Toolkit-Examples: This repository contains various examples to use with...

github.com

 

 

반응형
:

[OpenXR] Grab transformers / XR Interaction Toolkit 2.4.3

VR/XR Interaction Toolkit 2024. 1. 2. 13:39
반응형

https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.3/manual/xr-grab-interactable.html

XR Grab Interactable | XR Interaction Toolkit | 2.3.2

XR Grab Interactable Interactable component that allows for basic grab functionality. When this behavior is selected (grabbed) by an Interactor, this behavior will follow it around and inherit velocity when released. Property Description Interaction Manage

docs.unity3d.com

이 XR Grab Interactable 동작은 하나 이상의 IXRGrabTransformer 구현으로 계산된 위치, 회전 및 로컬 배율을 적용하는 역할을 합니다. (xref:UnityEngine.XR.Interaction.Toolkit.Transformers.XRGeneralTransformer) 그랩 변환기는 Unity에 의해 자동으로 추가되지만(기본 그랩 변환기 추가가 활성화된 경우) 이 기능을 비활성화하여 이 동작에 사용되는 그랩 변환기를 수동으로 설정할 수 있습니다. 이 구성 요소가 개체가 이동하고 회전해야 하는 위치를 결정하는 방법을 사용자 정의할 수 있습니다. 이 기본 그랩 변환기에는 변환, 양손 회전 및 양손 스케일링(기본적으로 비활성화됨)에 대한 축 제약 조건을 허용하는 구성 가능한 옵션 세트도 함께 제공됩니다.
 
Grab 변환기 구성 요소를 GameObject에 추가하여 XR Grab Interactable과 연결할 수 있습니다. 구성 요소 > XR > 변환기 메뉴에서 찾을 수 있습니다. 그런 다음 둘 이상의 Grab 변환기가 있고 실행 순서를 지정해야 하는 경우 또는 Grab 변환기가 자동으로 추가되는 목록을 재정의해야 하는 경우 해당 구성 요소에 대한 참조를 명시적으로 Single Grab 변환기 시작 또는 다중 Grab 변환기 시작에 추가할 수 있습니다. 에게.

Add Default Grab Transformers단일 또는 다중 Grab 변환기 목록이 비어 있는 경우 Unity가 기본 Grab 변환기 세트를 추가할지 여부입니다.
Starting Multiple Grab Transformers이 Interactable이 시작 시 자동으로 연결되는 그랩 변환기입니다(선택 사항, 비어 있을 수 있음). 다중 인터랙터 선택에 사용됩니다. 시작 후에는 이 속성이 사용되지 않습니다. 처리해야 하는 Grab Transformer가 두 개 이상 있고 순서를 지정해야 할 때 유용합니다.
Starting Single Grab Transformers이 Interactable이 시작 시 자동으로 연결되는 그랩 변환기입니다(선택 사항, 비어 있을 수 있음). 단일 인터랙터 선택에 사용됩니다. 시작 후에는 이 속성이 사용되지 않습니다. 처리해야 하는 Grab Transformer가 두 개 이상 있고 순서를 지정해야 할 때 유용합니다.
Multiple Grab Transformers(재생 모드에만 해당) 이 개체를 선택하는 여러 인터랙터가 있을 때 사용되는 잡기 변환기입니다.
Single Grab Transformers(재생 모드에만 해당) 이 객체를 선택하는 단일 인터랙터가 있을 때 사용되는 그랩 변환기입니다.
Interactable Events상호작용 가능한 이벤트 페이지를 참조하세요.

 
"Starting Multiple Grab Transformers"은 XR Interaction Toolkit의 기능 중 하나로, 개체를 여러 개의 그랩(transform) 포인트를 사용하여 그랩할 때 시작되는 그랩 변환(transformer)의 그룹을 정의하는 데 사용됩니다. 이를 통해 한 개체에 대해 여러 가지 다른 그랩 동작을 구성할 수 있습니다.
여기서 "그랩(transform) 포인트"란 개체의 다양한 위치 또는 그랩 가능한 지점을 나타냅니다. 이러한 포인트는 개체의 다른 부분을 그랩할 때 다양한 동작을 수행하도록 설정됩니다.
다음은 "Starting Multiple Grab Transformers"의 중요한 점을 설명합니다:

  1. 그랩(transform) 포인트 그룹 설정:
    • "Starting Multiple Grab Transformers"를 사용하면 개체의 그랩 포인트를 여러 그룹으로 구성할 수 있습니다. 각 그룹은 고유한 그랩 동작을 정의합니다.
    • 예를 들어, 컵 개체의 경우 그립 포인트 그룹을 상단 림과 손잡이로 나눌 수 있습니다. 이렇게 하면 상단 림을 그랩할 때와 손잡이를 그랩할 때 각각 다른 동작을 설정할 수 있습니다.
  2. 그랩(transform) 포인트 간 전환:
    • 여러 그랩 포인트 그룹이 설정되어 있을 때, 사용자가 그랩 포인트를 전환할 때 어떻게 동작할지를 정의할 수 있습니다.
    • 예를 들어, 두 그랩 포인트 그룹을 설정한 경우 사용자가 그랩 포인트를 전환할 때마다 어떤 그랩 동작이 시작될지를 지정할 수 있습니다.
  3. 상호 작용 경험의 다양성:
    • "Starting Multiple Grab Transformers"을 사용하면 개체의 상호 작용 경험을 다양화할 수 있습니다. 다른 그랩 포인트 그룹에서 시작되는 다양한 동작은 사용자에게 더 풍부한 경험을 제공합니다.
  4. 커스터마이징 및 디버깅:
    • 각 그랩 포인트 그룹은 세부적으로 커스터마이즈할 수 있으며, 개체와 손 간의 상호 작용 동작을 조정할 수 있습니다.
    • 디버깅 및 조정이 필요한 경우 각 그랩 포인트 그룹의 설정을 수정하여 원하는 동작을 얻을 수 있습니다.

"Starting Multiple Grab Transformers"은 XR Interaction Toolkit의 강력한 기능 중 하나로, 상호 작용을 더욱 다양하게 만들고 개체와의 상호 작용을 조절하는 데 유용합니다. 이를 통해 VR/AR 애플리케이션 및 게임에서 더 풍부하고 현실적인 상호 작용을 제공할 수 있습니다.
 
 
 


 
 
"Starting Single Grab Transformers"는 XR Interaction Toolkit의 기능 중 하나로, 개체를 하나의 그랩(transform) 포인트를 사용하여 그랩할 때 시작되는 그랩 변환(transformer)을 정의하는 데 사용됩니다. 이 설정을 통해 개체의 단일 그랩 포인트에서 어떤 그랩 동작을 수행할지를 정의할 수 있습니다.
여기서 "그랩(transform) 포인트"란 개체의 특정 위치 또는 그랩 가능한 지점을 나타냅니다. 이러한 포인트는 개체의 일부 또는 특정 위치를 그랩할 때 사용됩니다.
다음은 "Starting Single Grab Transformers"의 주요 특징을 설명합니다:

  1. 단일 그랩 포인트 설정:
    • "Starting Single Grab Transformers"를 사용하면 개체의 하나의 그랩 포인트를 정의할 수 있습니다. 이 포인트는 개체를 그랩하는 데 사용됩니다.
    • 예를 들어, 도어 개체의 경우 도어 손잡이가 하나의 그랩 포인트로 설정될 수 있습니다.
  2. 그랩(transform) 포인트 간 전환:
    • 단일 그랩 포인트가 설정된 경우 사용자가 그랩 포인트를 변경할 때 어떻게 동작할지를 정의할 수 있습니다.
    • 예를 들어, 다른 그랩 포인트로 이동할 때마다 어떤 그랩 동작이 시작될지를 지정할 수 있습니다.
  3. 상호 작용 경험의 제어:
    • "Starting Single Grab Transformers"를 사용하면 개체와 손 간의 상호 작용 동작을 정확하게 제어할 수 있습니다. 단일 그랩 포인트에서 시작되는 동작은 사용자 경험을 다양화하고 개체와의 상호 작용을 더욱 현실적으로 만듭니다.
  4. 커스터마이징 및 디버깅:
    • 단일 그랩 포인트의 설정을 세부적으로 조정하고 개체와 손 간의 상호 작용을 수정할 수 있습니다.
    • 필요한 경우 설정을 디버그하고 조정하여 원하는 동작을 얻을 수 있습니다.

"Starting Single Grab Transformers"를 사용하면 단일 그랩 포인트에서 시작되는 그랩 동작을 세밀하게 제어하고 상호 작용 경험을 커스터마이즈할 수 있습니다. 이를 통해 VR/AR 애플리케이션 및 게임에서 더 풍부하고 현실적인 상호 작용을 제공할 수 있습니다.


"XR Dual Grab Free Transformer"는 XR Interaction Toolkit에서 제공하는 그랩 변환(transformer) 유형 중 하나입니다. 이 변환 유형은 개체를 두 개의 그랩(transform) 포인트를 사용하여 그랩할 때, 그리고 그랩 중에 두 개의 그랩 포인트 간의 상호 작용을 자유롭게 조절하고 움직이게 할 때 사용됩니다.
다음은 "XR Dual Grab Free Transformer"의 주요 특징과 역할에 대한 설명입니다:

  1. 두 개의 그랩 포인트:
    • "XR Dual Grab Free Transformer"를 사용하면 하나의 개체를 두 개의 그랩 포인트로 그랩할 수 있습니다. 이 두 개의 포인트는 각각 다른 손에 의해 조종됩니다.
  2. 자유로운 상호 작용:
    • 이 변환 유형은 개체를 자유롭게 움직이거나 회전시킬 수 있는 상호 작용을 허용합니다. 사용자는 두 개의 그랩 포인트를 사용하여 개체를 조종할 수 있습니다.
    • 두 개의 그랩 포인트 간의 상호 작용을 사용자가 직접 조절하고 제어할 수 있습니다.
  3. 물리 시뮬레이션 및 그랩 효과:
    • "XR Dual Grab Free Transformer"를 사용하면 물리 시뮬레이션과 그랩 효과를 개체에 적용할 수 있습니다. 이를 통해 개체가 물리적으로 반응하고 그랩 포인트 간의 상호 작용이 더욱 현실적으로 보이도록 만들 수 있습니다.
  4. 커스터마이징 및 디버깅:
    • 개발자는 "XR Dual Grab Free Transformer"의 설정을 세부적으로 커스터마이즈할 수 있습니다. 그랩 동작, 물리 시뮬레이션, 그랩 포인트 간의 거리 및 각도 등을 조절할 수 있습니다.
    • 디버깅 및 조정이 필요한 경우 설정을 수정하여 원하는 동작을 얻을 수 있습니다.

"XR Dual Grab Free Transformer"를 사용하면 두 손을 사용하여 개체를 그랩하고 상호 작용할 때 더 다양한 동작 및 경험을 제공할 수 있습니다. 이는 VR/AR 애플리케이션 및 게임에서 물체를 조작하고 상호 작용하는 데 유용한 유형 중 하나입니다.
 


"XR General Grab Transformer"는 XR Interaction Toolkit의 그랩 변환(transformer) 중 하나로, 개체를 그랩할 때의 동작과 상호 작용을 다양하게 조절하고 제어할 수 있는 강력한 도구입니다. 이 변환 유형은 개발자가 그랩한 개체와 손 사이의 동작을 세밀하게 조정하고 커스터마이즈할 수 있도록 해줍니다.
다음은 "XR General Grab Transformer"의 주요 특징과 역할에 대한 설명입니다:

  1. 그랩 동작의 정의:
    • "XR General Grab Transformer"를 사용하면 그랩한 개체를 어떻게 조작할지를 정의할 수 있습니다. 개발자는 개체를 움직이거나 회전시키는 데 사용할 동작을 정의할 수 있습니다.
    • 예를 들어, 손의 움직임에 따라 개체를 움직이도록 설정하거나 특정 키 입력에 반응하여 개체를 회전시키도록 할 수 있습니다.
  2. 물리 시뮬레이션 지원:
    • "XR General Grab Transformer"는 물리 시뮬레이션을 지원합니다. 개체에 물리 엔진을 적용하여 그랩 포인트와 개체 간의 물리적 상호 작용을 표현할 수 있습니다.
    • 이를 통해 개체가 중력, 관성 및 충돌과 같은 물리적 요소에 따라 움직이게 할 수 있습니다.
  3. 커스터마이징 가능성:
    • 개발자는 "XR General Grab Transformer"의 동작을 세부적으로 커스터마이즈할 수 있습니다. 그랩 포인트 간의 거리, 속도 제한, 회전 각도 제한 등을 조절하여 원하는 동작을 얻을 수 있습니다.
    • 이로써 다양한 그랩 동작을 구현하고 사용자 경험을 조정할 수 있습니다.
  4. 다양한 상호 작용 경험 제공:
    • "XR General Grab Transformer"를 사용하면 다양한 상호 작용 경험을 제공할 수 있습니다. 손의 움직임, 사용자 입력 및 물리 시뮬레이션을 조합하여 다양한 동작을 생성할 수 있습니다.
    • 이로써 VR/AR 애플리케이션 및 게임에서 다양한 상호 작용을 구현할 수 있습니다.

"XR General Grab Transformer"는 개발자가 개체의 그랩 동작을 완벽하게 제어하고 커스터마이즈할 수 있는 강력한 도구입니다. 이를 통해 VR/AR 환경에서 더 다양하고 현실적인 상호 작용을 구현할 수 있으며, 사용자 경험을 향상시킬 수 있습니다.
 


"XR Single Grab Free Transformer"는 XR Interaction Toolkit에서 제공하는 그랩 변환(transformer) 중 하나로, 개체를 하나의 그랩(transform) 포인트를 사용하여 그랩할 때 사용되는 도구입니다. 이 변환 유형은 개체를 하나의 그랩 포인트로 그랩하고 이를 자유롭게 움직이고 회전시키는 상호 작용을 제공합니다.
다음은 "XR Single Grab Free Transformer"의 주요 특징과 역할에 대한 설명입니다:

  1. 단일 그랩 포인트 사용:
    • "XR Single Grab Free Transformer"를 사용하면 개체를 하나의 그랩 포인트로 그랩할 수 있습니다. 이 포인트는 개체를 그랩하는 데 사용됩니다.
    • 개체의 특정 위치 또는 그랩 가능한 지점이 단일 그랩 포인트로 설정됩니다.
  2. 자유로운 상호 작용:
    • 이 변환 유형은 개체를 자유롭게 움직이거나 회전시키는 상호 작용을 허용합니다. 사용자는 개체를 단일 그랩 포인트를 사용하여 조작할 수 있습니다.
    • 개체를 움직일 때 물리 시뮬레이션을 적용하거나, 사용자 입력에 반응하여 회전시킬 수 있습니다.
  3. 물리 시뮬레이션 및 그랩 효과:
    • "XR Single Grab Free Transformer"를 사용하면 물리 시뮬레이션과 그랩 효과를 개체에 적용할 수 있습니다. 이를 통해 개체가 물리적으로 반응하고 그랩 포인트 간의 상호 작용이 더욱 현실적으로 보이도록 만들 수 있습니다.
  4. 커스터마이징 및 디버깅:
    • 개발자는 "XR Single Grab Free Transformer"의 설정을 세부적으로 커스터마이즈할 수 있습니다. 그랩 동작, 물리 시뮬레이션, 그랩 포인트 간의 거리 및 각도 등을 조절하여 원하는 동작을 얻을 수 있습니다.
    • 필요한 경우 설정을 디버그하고 조정하여 원하는 동작을 얻을 수 있습니다.

"XR Single Grab Free Transformer"를 사용하면 단일 그랩 포인트에서 시작되는 그랩 동작을 세밀하게 제어하고 상호 작용 경험을 커스터마이즈할 수 있습니다. 이는 VR/AR 애플리케이션 및 게임에서 물체를 조작하고 상호 작용하는 데 유용한 도구 중 하나입니다.


"Add Default Grab Transformer"는 XR Interaction Toolkit에서 XR Grab Interactable에 대한 그랩(transform) 변환(transformer)을 추가하는 데 사용되는 메서드입니다. 이 메서드를 사용하면 개체에 대한 그랩 동작을 정의하고 커스터마이즈할 수 있습니다.
다음은 "Add Default Grab Transformer"에 대한 주요 특징과 역할에 대한 설명입니다:

  1. 그랩 동작의 추가:
    • "Add Default Grab Transformer"를 사용하면 XR Grab Interactable에 그랩 동작을 추가할 수 있습니다. 이 동작은 개체를 그랩할 때 어떻게 상호 작용할지를 정의합니다.
    • 예를 들어, 개체를 그랩한 후 손을 움직이면 개체도 함께 움직이도록 그랩 동작을 추가할 수 있습니다.
  2. 기본 설정 제공:
    • 메서드의 이름에서 알 수 있듯이, "Add Default Grab Transformer"는 개체에 대한 기본 그랩 동작을 추가합니다. 이러한 동작은 개체를 그랩할 때 사용자에게 일반적으로 기대되는 표준 동작을 포함합니다.
    • 개발자는 이러한 기본 동작을 기초로 하여 추가적인 커스터마이즈를 할 수 있습니다.
  3. 커스터마이즈 가능성:
    • "Add Default Grab Transformer"를 사용하면 추가된 그랩 동작을 세부적으로 커스터마이즈할 수 있습니다. 그랩 동작의 속도, 회전 각도, 물리 시뮬레이션 등을 조절하여 원하는 동작을 얻을 수 있습니다.
    • 개발자는 이를 통해 상호 작용 경험을 미세 조정하고 사용자에게 더 나은 그랩 동작을 제공할 수 있습니다.
  4. 다중 그랩 동작 추가:
    • "Add Default Grab Transformer"를 여러 번 호출하여 여러 그랩 동작을 추가할 수 있습니다. 이를 통해 여러 그랩 포인트나 다양한 그랩 동작을 지원할 수 있습니다.

"Add Default Grab Transformer" 메서드는 XR Grab Interactable를 통해 개체의 그랩 동작을 시작할 때 유용하게 사용할 수 있는 메서드입니다. 개발자는 이를 통해 빠르게 기본 그랩 동작을 설정하고, 이후에 필요한 커스터마이즈를 추가할 수 있습니다.
 


"Add Default Grab Transformer"를 체크 해제하면 XR Grab Interactable에서 기본 그랩 동작이 자동으로 추가되지 않습니다. 즉, 개체를 그랩할 때 어떤 동작도 자동으로 실행되지 않습니다.
체크 해제한 경우, 개발자가 직접 XR Grab Interactable에 그랩(transform) 변환(transformer)을 추가하고 설정해야 합니다. 이를 통해 개발자는 그랩 동작을 완전히 커스터마이즈하고 사용자 경험을 조절할 수 있습니다. 추가적인 그랩 변환을 수동으로 설정해야 하므로 더 많은 제어권을 얻을 수 있습니다.
요약하면, "Add Default Grab Transformer"를 체크 해제하면 개발자가 그랩 동작을 직접 추가하고 설정해야 하며, XR Grab Interactable에서 자동으로 기본 동작이 추가되지 않습니다. 이렇게 하면 더 많은 유연성을 가지고 그랩 동작을 디자인할 수 있습니다.


 

반응형
:

[OpenXR] When using Two Hand Grab in XR Interaction Toolkit 2.4.3

VR/XR Interaction Toolkit 2024. 1. 2. 13:31
반응형

https://forum.unity.com/threads/when-using-two-hand-grab-in-xr-interaction-toolkit-2-4-3-the-position-of-the-second-grabbed-hand-is.1532110/#post-9557029

When using Two Hand Grab in XR Interaction Toolkit 2.4.3, the position of the second grabbed hand is

[ATTACH] When using Two Hand Grab in XR Interaction Toolkit 2.4.3, the position of the second grabbed hand is not fixed. How do I fix this? [ATTACH]

forum.unity.com

 
 
예제에서도 저럼 
이것이 그냥 기본인가보네 -_-;;
 

 
 
 
핸드 비주얼을 꺼버리고 고스트 핸드를 보여주면된다
 

 

반응형
:

[OpenXR] Custom Hand Pose (Mirror Pose) with XR Interaction Toolkit 2.4.3

VR/XR Interaction Toolkit 2024. 1. 1. 01:08
반응형

https://youtube.com/playlist?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j&si=XhyHS26tNDP6u7CA

XR Interaction Toolkit 2.4.3

XR Interaction Toolkit 2.4.3 OpenXR Oculus

www.youtube.com


이것은 오큘러스 Integration SDK와 비슷하게 만드는것이다 

 
 
 
 
 
새 씬을 만들고 메인 카메라를 제거후 XR Origin을 생성 한다 
 

 
 
Left/Right Controller를 선택후 XR Controller를 제외한 나머지 컴포넌트를 제거 한다 

 
다음과 같은 구조로 만들고 핸드 프리팹을 넣어 준다 

 
 
 
Left Controller / Hand Visual / Offset을 선택후 Rotation 90, 0, -90으로 설정 한다 

 
Right Controller / Hand Visual / Offset을 선택후 Rotation -90, 0, -90으로 설정 한다 

 
 
실행후 결과를 확인 한다 

 
 
Left/Right Controller를 선택후 XR Controller의 Model Prefab을 None으로 설정 한다 

 
 
실행후 결과를 확인 한다 
 

 
 
프로젝트 창에서 Direct Interactor를 검색해 프리팹을 Left, Right Controller자식으로 넣어준다 

 
 
Direct Controller를 선택해 Sphere Collider의 Radius값을 0.1로 설정 한다 
 

 
 
 
 
 
빈 오브젝트 (Grabbable Cube)를 만들고

 
자식으로 빈오브젝트 Visuals를 만들고 그 자식으로 Cube를 생성한다 

 
 
큐브의 크기를 0.1로 만들어 주고 

 
 
Grabbable Cube를 선택해 위치를 조절 한다 

 
Grabbable Cube를 선택하고 
Rigidbody, XR Grab Interactable을 부착 한다 
Use Gravity는 체크 해제 하고 
Throw on Detach도 체크 해제 하자 

 
 
실행후 결과를 확인하고 
 

 
 
프로젝트 창에서 Interaction Affordance를 검색해 Grabbable Cube자식으로 넣어 주고 

 
 
Interaction Affordance 자식으로 있는 Color Affordance 를 선택해 Renderer프로퍼티에 Cube를 넣어 준다 

 
 
Color Affordance Theme를 살펴 보면 각 상태에 따라 컬러를 변경해준다 

 

 
 
실행해 결과를 확인 하자 
 

 


 
Grabbable Cube를 선택 하고 빈 오브젝트 (Hand Pose)를 만들어 주고 
Left/Right Controller에서 사용했던 프리팹과 동일한 프리팹을 넣어 준다 

 
 
이제 핸드를 선택하고 위치를 적당히 잡아 준다 

 
 
 
HandPose 스크립트와 Editor폴더를 만들고 EditorHandPose스크립트를 만든다 

 
다음과 같이 작성하고 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandPose : MonoBehaviour
{
    [SerializeField] private GameObject attachPointPrefab;
    [SerializeField] private Transform wrist;

    public void CreateAttachPoint()
    {
        var point = Instantiate(attachPointPrefab, this.transform);
        point.transform.localPosition =this.transform.InverseTransformPoint(wrist.position); 
    }
}

 

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(HandPose))]
public class EditorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        HandPose script = (HandPose)target;

        if (GUILayout.Button("Create Attach Point"))
        {
            script.CreateAttachPoint();
        }
    }
}

 
Hand Pose L 을 선택해 Hand Pose컴포넌트를 부착 한다 

 
 
계층뷰에서 Sphere를 하나 생성하고 크기를 0.01로 설정한후 콜라이더를 제거 하고 이름을 AttachPoint로 변경후 프리팹으로 만들어 준다 
 
 

 
 
Hand Pose L 을 선택후 프로퍼티를 할당한다 
 

 
 
이제 Create Attach Point버튼을 누르면 b_l_wrist위치에 Attach Point오브젝트가 생성된다 

 
 


 
HandData 스크립트를 생성후 
다음과 같이 작성한다 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandData : MonoBehaviour
{
    public enum  HandType
    {
        Left, Right
    }

    public HandType handType;
    public Transform[] bones;
    public Transform root;
    public Animator anim;
    
}

 
핸드 포즈 아래 핸드를 선택후 부착 한다 

 
 

 
 
 
Animator를 제거 하고 Root를 할당 한다 
인스펙터를 잠그고 

 
 
17개의 본을 선택후 

 
 
HandData의 Bones프로퍼티에 드레그 드롭 한다 

 
이제 핸드 잠금을 해제 한다 

 
 
 
이번에는 Left Controller아래 있는 핸드를 선택하고 HandData를 부착 하고 프로퍼티를 넣어 주자 

 


 
GrabInteractable 스크립트를 만들고 

 
다음과 같이 작성한후 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class GrabInteractable : XRGrabInteractable
{
    [SerializeField]
    private Transform LeftHandAttachTransform;
    [SerializeField]
    XRDirectInteractor LeftController;
    [SerializeField]
    XRDirectInteractor RightController;
    
    protected override void OnSelectEntering(SelectEnterEventArgs args)
    {
        if (args.interactorObject == LeftController)
        {
            Debug.Log($"Left hand");
            attachTransform.SetPositionAndRotation(LeftHandAttachTransform.position, LeftHandAttachTransform.rotation);
        }
        else if (args.interactorObject == RightController)
        {
            Debug.Log($"Right hand");
        }
        base.OnSelectEntering(args);
    }
}

 
 
Grabbable Cube를 선택하고 XR Grab Interactable을 제거 후 부착 한다 
 

 
 
 
 
Grabbable Cube를 선택하고 빈오브젝트 Attach Point를 생성한다 

 
다음과 같이 프로퍼티를 할당한다 

 
 
DirectInteractor 스크립트를 생성후 
 

다음과 같이 작성하고 Direct Interactor를 선택하 XR Direct Interactor를 제거 하고 부착 한다 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class DirectInteractor : XRDirectInteractor
{
    public HandData.HandType handType;
}

 

 
 
그리고 Hand Type을 변경 한다 

 
 
 
HandPose스크립트를 다음과 같이 수정 한다 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;

public class HandPose : MonoBehaviour
{
    [SerializeField] private GameObject attachPointPrefab;
    [SerializeField] private Transform wrist;


    [SerializeField] private GrabInteractable grabInteractable;
    [SerializeField] private HandData handDataPose;


    private void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {
            var directInteractor = arg.interactorObject as DirectInteractor;

            if (directInteractor.handType == this.handDataPose.handType)
            {
                Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();

                Debug.LogFormat("handData : {0}", handData);

                handData.anim.enabled = false;

                handData.root.parent.localRotation = handDataPose.root.localRotation;

                for (int i = 0; i < handDataPose.bones.Length; i++)
                {
                    handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
                }
            }
        });


        grabInteractable.selectExited.AddListener((arg) =>
        {
            var directInteractor = arg.interactorObject as DirectInteractor;

            if (directInteractor.handType == this.handDataPose.handType)
            {
                Debug.LogFormat("Select Exited : {0}", arg.interactorObject);

                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();

                Debug.LogFormat("handData : {0}", handData);

                handData.anim.enabled = true;

                //초기화 
                handData.root.localPosition = Vector3.zero;

                if (this.handDataPose.handType == HandData.HandType.Left)
                    handData.root.parent.localRotation = Quaternion.Euler(new Vector3(90, 0, -90));
            }
        });
    }


    public void CreateAttachPoint()
    {
        var point = Instantiate(attachPointPrefab, this.transform);
        point.transform.localPosition = this.transform.InverseTransformPoint(wrist.position);
    }
}

 
핸드 포즈를 눌러 프로퍼티를 할당한다 

 
 
Grabbable Cube를 선택해 Grab Interactable 프로퍼티를 넣어준다 

 
 
 
핸드 포즈 자식의 핸드를 선택해 Hand컴포넌트를 제거 한다 

 
 
마지막으로 핸드 포즈 자식의 핸드의 메터리얼을 변경하고 이름을 GhostHandL로 변경 한다 

 
실행후 결과를 확인한다 

 
 
이제 손을 조금 구부려 본다 

 
위치가 변경되었다면 반드시 핸드 포즈의 Attach Point를 제거 하고 다시 만들어 주자 

 

 
 
그리고 Grabbable Cube를 선택해 Grab Interactable 의 Left hand Attach Transform에 할당해줘야 한다 

 
 
실행후 결과를 확인 한다 

 
오른손도 테스트 

 
 
 


오른손 미러 포즈 만들기 
MirrorHandPose, EditorMirrorHandPose스크립트를 생성하고 

 
작성한다 

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class MirrorHandPose : MonoBehaviour
{
    [SerializeField] private HandData handDataPoseFrom;
    [SerializeField] private HandData handDataPose;

    public void UpdateMirrorPose()
    {
        Vector3 mirroredPosition = handDataPoseFrom.root.localPosition;
        mirroredPosition.x *= -1;

        Quaternion mirroredQuaternion = handDataPoseFrom.root.localRotation;
        mirroredQuaternion.y *= -1;
        mirroredQuaternion.z *= -1;

        // Z축을 중심으로 180도 회전하는 쿼터니언 생성
        Quaternion rotate180 = Quaternion.Euler(0, 0, 180);

        // 반전된 회전에 추가 회전 적용
        mirroredQuaternion = rotate180 * mirroredQuaternion;

        handDataPose.root.localPosition = mirroredPosition;
        handDataPose.root.localRotation = mirroredQuaternion;

        for (int i = 0; i < this.handDataPoseFrom.bones.Length; i++)
        {
            this.handDataPose.bones[i].localRotation = handDataPoseFrom.bones[i].localRotation;
        }
    }
}
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(MirrorHandPose))]
public class EditorMirrorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        MirrorHandPose script = (MirrorHandPose)target;

        if (GUILayout.Button("Update Mirror Pose"))
        {
            script.UpdateMirrorPose();
        }
    }
}

 
 
왼손 포즈를 복사 하고 

HandData를 넣어 주고 

 
 
 
 
핸드 포즈 L 에 Mirror Hand Pose를 부착한다 

 
 
From에 L을 To 에 R을 넣어주고 

 
Update Mirror Pose를 누른다 
그리고 각을 잘 잡아 주자 

 
 
 
 
 
 
 
 
 

 

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(MirrorHandPose))]
public class EditorMirrorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        MirrorHandPose script = (MirrorHandPose)target;
        
        if (GUILayout.Button("Save Initial Pose"))
        {
            script.SaveInitialPose();
        }
        

        // "Update Mirror Pose" 버튼
        if (GUILayout.Button("Update Mirror Pose"))
        {
            script.UpdateMirrorPose();
        }

        // "Reset Pose" 버튼 추가
        if (GUILayout.Button("Reset Pose"))
        {
            script.ResetPose();
        }
        
        
    }
}

 

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class MirrorHandPose : MonoBehaviour
{
    [SerializeField] private HandData handDataPoseFrom;
    [SerializeField] private HandData handDataPose;
    // 초기 상태를 저장하기 위한 변수들
    [HideInInspector]
    public Vector3 initialRootPosition;
    [HideInInspector]
    public Quaternion initialRootRotation;
    [HideInInspector]
    public Quaternion[] initialBoneRotations;

    public void ResetPose()
    {
        // root의 초기 위치와 회전으로 재설정
        handDataPose.root.localPosition = initialRootPosition;
        handDataPose.root.localRotation = initialRootRotation;

        // 모든 bones의 초기 회전으로 재설정
        for (int i = 0; i < handDataPose.bones.Length; i++)
        {
            handDataPose.bones[i].localRotation = initialBoneRotations[i];
        }
    }

    

    public void UpdateMirrorPose()
    {
        Vector3 mirroredPosition = handDataPoseFrom.root.localPosition;
        mirroredPosition.x *= -1;

        Quaternion mirroredQuaternion = handDataPoseFrom.root.localRotation;
        mirroredQuaternion.y *= -1;
        mirroredQuaternion.z *= -1;

        // Z축을 중심으로 180도 회전하는 쿼터니언 생성
        Quaternion rotate180 = Quaternion.Euler(0, 0, 180);

        // 반전된 회전에 추가 회전 적용
        mirroredQuaternion = rotate180 * mirroredQuaternion;

        handDataPose.root.localPosition = mirroredPosition;
        handDataPose.root.localRotation = mirroredQuaternion;

        for (int i = 0; i < this.handDataPoseFrom.bones.Length; i++)
        {
            this.handDataPose.bones[i].localRotation = handDataPoseFrom.bones[i].localRotation;
        }
    }
    
    public void SaveInitialPose()
    {
        // handDataPose의 root와 bones에서 초기 상태 저장
        initialRootPosition = handDataPose.root.localPosition;
        initialRootRotation = handDataPose.root.localRotation;

        initialBoneRotations = new Quaternion[handDataPose.bones.Length];
        for (int i = 0; i < handDataPose.bones.Length; i++)
        {
            initialBoneRotations[i] = handDataPose.bones[i].localRotation;
        }
    }
    
    
}

 x 위치에 -1을 곱하는 이유는 3D 공간에서 객체의 거울 이미지를 생성하기 위해서입니다. 유니티에서 3D 객체의 위치는 `(x, y, z)` 형식의 `Vector3`로 표현됩니다. 여기서 x 축은 왼쪽과 오른쪽을, y 축은 위와 아래를, z 축은 앞과 뒤를 나타냅니다.

거울 이미지를 만들기 위해서는 한 축을 기준으로 객체를 '뒤집어야' 합니다. 이 코드에서는 x 축을 기준으로 뒤집습니다. 즉, 객체의 x 위치를 그대로 반대 방향으로 이동시키는 것입니다. 예를 들어, 객체가 원래 x 축에서 +3의 위치에 있었다면, -1을 곱하면 -3의 위치로 이동하게 되어, 거울상에서의 위치가 됩니다.

이러한 변환은 주로 대칭적인 장면이나 객체를 만들 때 사용됩니다. 예를 들어, 캐릭터의 한 손 동작을 다른 손에 정확히 반대로 적용하려 할 때 유용합니다.




회전에서 y축과 z축에 -1을 곱하는 것은 3D 공간에서의 객체의 거울 이미지 회전을 생성하기 위한 것입니다. 유니티에서 객체의 회전은 `Quaternion`으로 표현되며, 이는 복잡한 3D 회전을 나타내는 데 사용되는 수학적 구조입니다.

손 또는 다른 객체를 거울상으로 반전시키기 위해서는 단순히 위치를 반전시키는 것 이상의 조치가 필요합니다. 회전도 반전시켜야 합니다. 3D 공간에서의 회전은 x, y, z 축 주위로 정의됩니다. 거울상 회전을 만들기 위해선, 특정 축(이 경우 y와 z) 주위의 회전 방향을 반대로 해야 합니다.

- **y축 회전 반전:** 객체가 수직(y축)을 중심으로 시계 방향으로 회전했다면, 이를 반대로 반전시키려면 시계 반대 방향으로 같은 각도만큼 회전해야 합니다. 이를 수학적으로 나타내기 위해 y축 회전 성분에 -1을 곱합니다.

- **z축 회전 반전:** 마찬가지로, 객체가 수평(z축)을 중심으로 앞뒤로 회전했다면, 거울상에서는 그 회전이 반대 방향으로 보여야 합니다. z축 회전 성분에 -1을 곱함으로써 이를 달성할 수 있습니다.

이러한 변환을 통해, 객체의 3D 회전이 거울상에서 보는 것처럼 정확하게 반전됩니다. 예를 들어, VR/AR 게임에서 사용자의 한 손 동작을 다른 손에 정확히 반대로 적용하려 할 때 이 방법이 유용합니다.




코드에서 180도 회전을 적용하는 이유는 3D 공간에서의 객체의 거울 이미지를 보다 정확하게 생성하기 위해서입니다. 특히, 이 경우에는 손의 위치와 방향을 거울 이미지처럼 만들기 위해 사용됩니다.

위에서 언급한 것처럼, y축과 z축의 회전 성분에 -1을 곱하는 것만으로는 완벽한 거울 이미지를 생성할 수 없습니다. 이는 객체가 거울을 통해 본 것처럼 완전히 반대 방향을 보도록 하지 않기 때문입니다. 여기서 필요한 것이 바로 Z축을 중심으로 180도 회전하는 추가적인 변환입니다.

Z축 주위로 180도 회전(`Quaternion.Euler(0, 0, 180)`)을 적용하면, 객체가 수평 평면을 기준으로 정확히 반대 방향을 바라보게 됩니다. 이는 객체가 거울에 반사된 것처럼 보이게 하기 위해 필요한 마지막 조정입니다.

결론적으로, y축과 z축 회전 성분에 -1을 곱하고, Z축 주위로 180도 추가 회전을 적용함으로써, 객체는 거울상에서 보는 것처럼 완전히 반대 방향을 바라보게 되며, 이는 거울 이미지 회전을 정확하게 재현하는 데 중요합니다.


 
 public class MirrorPoseUtil
{
    // 3D 객체를 거울 이미지로 변환하는 메서드
    public static void UpdateMirrorPose(Transform objectTransform)
    {
        // 위치를 거울 이미지로 반전
        objectTransform.localPosition = MirrorPosition(objectTransform.localPosition);

        // 회전을 거울 이미지로 반전
        objectTransform.localRotation = MirrorRotation(objectTransform.localRotation);
    }

    // 위치를 거울 이미지로 반전하는 메서드
    private static Vector3 MirrorPosition(Vector3 originalPosition)
    {
        originalPosition.x *= -1;
        return originalPosition;
    }

    // 회전을 거울 이미지로 반전하는 메서드
    private static Quaternion MirrorRotation(Quaternion originalRotation)
    {
        originalRotation.y *= -1;
        originalRotation.z *= -1;

        // Z축을 중심으로 180도 회전 적용
        Quaternion rotate180 = Quaternion.Euler(0, 0, 180);
        return rotate180 * originalRotation;
    }
}


 
 


 
HandGrabInteractor 

 
 
 
HandGrabInteractable

 
 
 
HandGrabPose

 
 
Synthetic Hand 

 
 
 
 
 


 
테스트 코드들 

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(MirrorHandPose))]
public class EditorMirrorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        MirrorHandPose script = (MirrorHandPose)target;

        if (GUILayout.Button("Update Mirror Pose"))
        {
            script.UpdateMirrorPose();
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class XRHandedGrabInteractable : XRGrabInteractable
{
    [SerializeField]
    private Transform LeftHandAttachTransform;
    [SerializeField]
    private Transform RightHandAttachTransform;
    [SerializeField]
    XRDirectInteractor LeftController;
    [SerializeField]
    XRDirectInteractor RightController;
    
    protected override void OnSelectEntering(SelectEnterEventArgs args)
    {
        if (args.interactorObject == LeftController)
        {
            Debug.Log($"Left hand");
            attachTransform.SetPositionAndRotation(LeftHandAttachTransform.position, LeftHandAttachTransform.rotation);
        }
        else if (args.interactorObject == RightController)
        {
            Debug.Log($"Right hand");
            attachTransform.SetPositionAndRotation(RightHandAttachTransform.position, RightHandAttachTransform.rotation);
        }
        base.OnSelectEntering(args);
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;


public class MirrorHandPose : MonoBehaviour
{
    [SerializeField] private HandData handDataPoseFrom;
    [SerializeField] private HandData handDataPose;

    public void UpdateMirrorPose()
    {
        Vector3 mirrorredPosition = handDataPoseFrom.root.localPosition;
        Quaternion mirroredQuaternion = handDataPoseFrom.root.localRotation;
        handDataPose.root.localPosition = mirrorredPosition;
        handDataPose.root.localRotation = mirroredQuaternion * Quaternion.Euler(new Vector3(0, 0, 180));

        for (int i = 0; i < this.handDataPoseFrom.bones.Length; i++)
        {
            this.handDataPose.bones[i].localRotation = handDataPoseFrom.bones[i].localRotation;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;

public class Hand : MonoBehaviour
{
    [SerializeField] private InputDeviceCharacteristics inputDeviceCharacteristics;
    [SerializeField] private Animator anim;
    private InputDevice targetDevice;
    void Start()
    {
        this.StartCoroutine(this.WaitForGetDevices());
    }

    private IEnumerator WaitForGetDevices()
    {
        WaitForEndOfFrame wait = new WaitForEndOfFrame();
        List<InputDevice> devices = new List<InputDevice>();
        
        while (devices.Count == 0)
        {
            yield return wait;
            InputDevices.GetDevicesWithCharacteristics(inputDeviceCharacteristics, devices);
        }
        
        this.targetDevice = devices[0];
    }

    void Update()
    {
        if (this.targetDevice.isValid)
        {
            UpdateHand();
        }
    }

    private void UpdateHand()
    {
        if (this.targetDevice.TryGetFeatureValue(CommonUsages.grip, out float gripValue))
        {
            this.anim.SetFloat("Grip", gripValue);
        }
        
        if (this.targetDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerValue))
        {
            this.anim.SetFloat("Trigger", triggerValue);
        }
        
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandData : MonoBehaviour
{
    public enum  HandType
    {
        Left, Right
    }

    public HandType handType;
    public Transform[] bones;
    public Transform root;
    public Animator anim;
    
}
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;
    [SerializeField] private HandData handDataPose;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {

            var directInteractor = arg.interactorObject as DirectInteractor;

            if (directInteractor.handType == this.handDataPose.handType)
            {
                
                    

                Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
                Debug.LogFormat("handData : {0}", handData);
            
                handData.anim.enabled = false;
            
                //handData.root.localRotation = handDataPose.root.localRotation * Quaternion.Euler(new Vector3(-90, 0, 90));
            
                handData.root.parent.localRotation = handDataPose.root.localRotation;
            
                for (int i = 0; i < handDataPose.bones.Length; i++)
                {
                    handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
                }
            
                //handDataPose.gameObject.SetActive(false);
            }
        });

        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            var directInteractor = arg.interactorObject as DirectInteractor;
            if (directInteractor.handType == this.handDataPose.handType)
            {
                Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
            
                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
                Debug.LogFormat("handData : {0}", handData);

                handData.anim.enabled = true;

                //초기화 
                handData.root.localPosition = Vector3.zero;
                
                if(this.handDataPose.handType == HandData.HandType.Left)
                    handData.root.parent.localRotation = Quaternion.Euler(new Vector3(90, 0, -90)); 
                if(this.handDataPose.handType == HandData.HandType.Right)
                    handData.root.parent.localRotation = Quaternion.Euler(new Vector3(-90, 0, -90)); 

                //handDataPose.gameObject.SetActive(true);
            }

            
        });
    }

    
}

 
https://youtu.be/JdspLj4fZlI?list=RDCMUC-BligqNSwG0krJDfaPhytw
https://youtu.be/TW3eAJqWCDU

반응형
:

XR Interaction Toolkit 2.4.3 (Hand Pose)

VR/XR Interaction Toolkit 2023. 12. 30. 23:05
반응형

https://youtu.be/kq9AucaBeOw?list=PLTFRwWXfOIYBIPKhWi-ZO_ITXqtNuqj6j

 

 

 

 

 
 


 
새 씬을 만들어 줍니다.
 
메인 카메라를 제거 하고 XR Origin을 생성한뒤 
Left/Right Controller를 선택해 XR Controller를 제외한 나머지 컴포넌트들을 제거 합니다.

 
실행해서 컨트롤러가 잘 나오는지 확인 합니다.
 

 


 
 
 
Left / Right Controller를 선택하고 다음 구조로 만들어 준뒤 핸드 프리팹을 Offset 자식으로 넣어 줍니다.

 
왼손 Offset 회전을 90, 0 -90으로 설정 합니다.

 
 
오른손 Offset회전을 -90, 0, -90으로 설정 합니다.

 
 
 
 
프로젝트 창에서 Direct Interactor를 검색해서 Left Controller, Right Controller에 각각 넣어 줍니다.

 
 
Sphere콜라이더의 Radius의 값을 0.1로 설정 합니다.

 
위치를 손바닥의 중앙으로 설정 합니다.

 


 
이제 빈 오브젝트 (Grabbable Cube)를 만들고

 
다음과 같은 구조로 만들어 줍니다.

 
Grabbable Cube를 선택하고 적당한 위치로 설정 합니다.

 
Rigidbody와 XR Grab Interactable컴포넌트를 부착 합니다.
그리고 Throw on Detach를 비활성화 합니다 

 
 
Left/Right Controller를 선택해 XR controller의 model prefab을 none으로 설정합니다.

 
실행후 결과를 확인 합니다.


 
Grabbable Cube를 선택하고 Hand Pose 빈오브젝트를 생성 합니다.

 
Left/Right Controller에 붙어 있는 핸드와 동일한 핸드 프리팹을 넣어 줍니다.

 
 
 
적당히 포즈를 잡아 줍니다.,

 
 
Hand Pose 아래 핸드 프리팹을 선택한뒤 Animator와 Hand컴포넌트를 제거 합니다.
 

 
 
 
 
 
 
 
핸드 포즈 오브젝트 자식으로 Sphere를 만들고 빨강 메터리얼을 적용하고 콜라이더는 제거 합니다.

 
그리고 이름을 Attach Point로 변경 합니다.

 
이제 Attach Point를 드레그 해서 Hand Pose자식으로 넣습니다.

회전값을 초기화 합니다.

 
 
Grabbable Cube를 선택하고

 
XR Grab Interactable 컴포넌트의 Attach Transform에 Attach Point를 넣어 줍니다.

 
 


 
 
Left Right Controller의 Direct Interactor 를 선택해 위치를 초기화 합니다.
 
Direct Interactor를 선택하고 Sphere를 생성하고 메터리얼을 빨강색, 콜라이더는 제거 합니다.

 
 
실행해보기 
 


 
 
HandData스크립트를 만들어 다음과 같이 작성 합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandData : MonoBehaviour
{
    public enum  HandType
    {
        Left, Right
    }

    public HandType handType;
    public Transform[] bones;
    public Transform root;
    public Animator anim;
    
}

 
핸드 포즈 자식으로 있는 핸드를 선택해 HandData를 부착 합니다.

 
인스펙터를 잠그고 

 
다음과 같이 선택해서 Bones에 넣어 줍니다.
 

 
 
이제 인스펙터 잠금을 해제 합니다

 
 


 
 
HandPose 스크립트를 생성하고 

다음과 같이 작성 합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {
            Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
        });
        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
        });
    }

    
}

 


 
 
Left Controller의 핸드를 선택 하고 HandData를 부착 합니다.
그리고 프로퍼티를 채워줍니다.

 
다음과 같이 코드를 수정 합니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {
            Debug.LogFormat("Select Entered : {0}", arg.interactorObject);

            var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
            Debug.LogFormat("handData : {0}", handData);
        });
        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
        });
    }

    
}

 
 
 
코드를 다음과 같이 수정하고 실행 합니다.
 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;

    [SerializeField] private HandData handDataPose;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {
            Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
        
            var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
            Debug.LogFormat("handData : {0}", handData);
            
            handData.anim.enabled = false;
            
            handData.root.localRotation = handDataPose.root.localRotation;
            
            for (int i = 0; i < handDataPose.bones.Length; i++)
            {
                handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
            }
            
        });

        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
            
            var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
            Debug.LogFormat("handData : {0}", handData);

            handData.anim.enabled = true;

            //초기화 
            handData.root.localPosition = Vector3.zero;
            handData.root.localRotation = Quaternion.Euler(new Vector3(90, 0, -90));
        });
    }

    
}

 

using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;

    [SerializeField] private HandData handDataPose;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener(OnSelectEntered);
        grabInteractable.selectExited.AddListener(OnSelectExited);
    }

    private void OnSelectEntered(SelectEnterEventArgs arg)
    {
        Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
        var handData = GetHandData(arg);
        
        ApplyHandPose(handData);
    }

    private void OnSelectExited(SelectExitEventArgs arg)
    {
        Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
        var handData = GetHandData(arg);
        
        ResetHandPose(handData);
    }

    private HandData GetHandData(BaseInteractionEventArgs arg)
    {
        var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
        Debug.LogFormat("handData : {0}", handData);
        return handData;
    }

    private void ApplyHandPose(HandData handData)
    {
        handData.anim.enabled = false;
        
        Vector3 invertedEulerAngles = InvertEulerAngles(handData.root.parent.localRotation.eulerAngles);
        Debug.LogFormat("{0} {1} {2}", invertedEulerAngles.x, invertedEulerAngles.y, invertedEulerAngles.z);
        handData.root.localRotation = handDataPose.root.localRotation * Quaternion.Euler(invertedEulerAngles);
        
        for (int i = 0; i < handDataPose.bones.Length; i++)
        {
            handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
        }
    }

    private void ResetHandPose(HandData handData)
    {
        handData.anim.enabled = true;
        handData.root.localPosition = Vector3.zero;
        handData.root.localRotation = quaternion.identity;
        handDataPose.gameObject.SetActive(true);
    }

    private Vector3 InvertEulerAngles(Vector3 eulerAngles)
    {
        return new Vector3(-eulerAngles.x, eulerAngles.y, -eulerAngles.z);
    }
}

 
 
 
 
 
 
 
 
 


 
 

 

using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;

    [SerializeField] private HandData handDataPose;
    
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {
            Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
        
            var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
            Debug.LogFormat("handData : {0}", handData);
            
            handData.anim.enabled = false;
            
            //handData.root.localRotation = handDataPose.root.localRotation * Quaternion.Euler(new Vector3(-90, 0, 90));
            
            handData.root.localRotation = handDataPose.root.localRotation;
            
            for (int i = 0; i < handDataPose.bones.Length; i++)
            {
                handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
            }
            
            //handDataPose.gameObject.SetActive(false);
            
        });

        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
            
            var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
            Debug.LogFormat("handData : {0}", handData);

            handData.anim.enabled = true;

            //초기화 
            handData.root.localPosition = Vector3.zero;
            handData.root.localRotation = Quaternion.Euler(new Vector3(90, 0, -90)); //quaternion.identity;

            handDataPose.gameObject.SetActive(true);
        });
    }

    
}

 
 
 


Mirror 포즈 만들기 
 
MirrorHandPose스크립트를 만들고 

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;


public class MirrorHandPose : MonoBehaviour
{
    [SerializeField] private HandData handDataPoseFrom;
    [SerializeField] private HandData handDataPose;

    public void UpdateHandPose()
    {
        Vector3 mirrorredPosition = handDataPoseFrom.root.localPosition;
        Quaternion mirroredQuaternion = handDataPoseFrom.root.localRotation;
        handDataPose.root.localPosition = mirrorredPosition;
        handDataPose.root.localRotation = mirroredQuaternion * Quaternion.Euler(new Vector3(0, 0, 180));

        for (int i = 0; i < this.handDataPoseFrom.bones.Length; i++)
        {
            this.handDataPose.bones[i].localRotation = handDataPoseFrom.bones[i].localRotation;
        }
    }
}

 
왼손 Hand Pose를 복사 한후 모델을 변경 해준다 (OculusHand_R)로 

 
 
오른손 모델을 선택 하고 Hand Data를 부착 후 프로퍼티를 설정한다 

 
 
 
 
Editor Mirror Hand Pose 스크립트를 만들어 주고 

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(MirrorHandPose))]
public class EditorMirrorHandPose : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        MirrorHandPose script = (MirrorHandPose)target;

        if (GUILayout.Button("Update Mirror Pose"))
        {
            script.UpdateMirrorPose();
        }
    }
}

 
Mirror Hand Pose 스크립트를 부착 한다 

 
 
인스펙터에서 Update mirror pose 버튼을 누른다 

 
 
왼손의 포즈가 오른손으로 적용된 모습이다 

 
 


 
 
왼손으로 잡았는지 오른손으로 잡았는지 알아내기 
 
문제는 이제 왼손 오른손에 HandPose가 달려 있기 때문에 2번씩 호출 된다 

 
 
Direct Interactor 오브젝트를 선택하고 XR Direct Interactor를 제거 한다 

 
DirectInteractor 스크립트를 만들고 다음과 같이 작성후 다시 붙여 주자 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class DirectInteractor : XRDirectInteractor
{
    public HandData.HandType handType;
    
    protected override void OnSelectEntered(SelectEnterEventArgs args)
    {
        base.OnSelectEntered(args);
        
        Debug.LogFormat("OnSelectEntered : <color=yellow>{0}</color>", this.handType);
    }
}

 
 
 
이제 HandPose스크립트를 수정 한다 

using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class HandPose : MonoBehaviour
{
    [SerializeField]
    private XRGrabInteractable grabInteractable;
    [SerializeField] private HandData handDataPose;
    void Start()
    {
        grabInteractable.selectEntered.AddListener((arg) =>
        {

            var directInteractor = arg.interactorObject as DirectInteractor;
            if (directInteractor.handType == this.handDataPose.handType)
            {
                Debug.LogFormat("Select Entered : {0}", arg.interactorObject);
                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
                Debug.LogFormat("handData : {0}", handData);
            
                handData.anim.enabled = false;
            
                //handData.root.localRotation = handDataPose.root.localRotation * Quaternion.Euler(new Vector3(-90, 0, 90));
            
                handData.root.parent.localRotation = handDataPose.root.localRotation;
            
                for (int i = 0; i < handDataPose.bones.Length; i++)
                {
                    handData.bones[i].localRotation = handDataPose.bones[i].localRotation;
                }
            
                //handDataPose.gameObject.SetActive(false);
            }
        });

        
        grabInteractable.selectExited.AddListener((arg) =>
        {
            var directInteractor = arg.interactorObject as DirectInteractor;
            if (directInteractor.handType == this.handDataPose.handType)
            {
                Debug.LogFormat("Select Exited : {0}", arg.interactorObject);
            
                var handData = arg.interactorObject.transform.parent.GetComponentInChildren<HandData>();
            
                Debug.LogFormat("handData : {0}", handData);

                handData.anim.enabled = true;

                //초기화 
                handData.root.localPosition = Vector3.zero;
                
                if(this.handDataPose.handType == HandData.HandType.Left)
                    handData.root.parent.localRotation = Quaternion.Euler(new Vector3(90, 0, -90)); 
                if(this.handDataPose.handType == HandData.HandType.Right)
                    handData.root.parent.localRotation = Quaternion.Euler(new Vector3(-90, 0, -90)); 

                handDataPose.gameObject.SetActive(true);
            }

            
        });
    }

    
}

 
 
 
 
 
 
 
 
 
 
양손모두 동작 하는지 확인 하자 


 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;

public class XRHandedGrabInteractable : XRGrabInteractable
{
    [SerializeField]
    private Transform LeftHandAttachTransform;
    [SerializeField]
    private Transform RightHandAttachTransform;
    [SerializeField]
    XRDirectInteractor LeftController;
    [SerializeField]
    XRDirectInteractor RightController;
    
    protected override void OnSelectEntering(SelectEnterEventArgs args)
    {
        if (args.interactorObject == LeftController)
        {
            Debug.Log($"Left hand");
            attachTransform.SetPositionAndRotation(LeftHandAttachTransform.position, LeftHandAttachTransform.rotation);
        }
        else if (args.interactorObject == RightController)
        {
            Debug.Log($"Right hand");
            attachTransform.SetPositionAndRotation(RightHandAttachTransform.position, RightHandAttachTransform.rotation);
        }
        base.OnSelectEntering(args);
    }
}

 

반응형
: