# OptiTrack Unity Plugin NaturalPoint OptiTrack 모션캡처 시스템과 Unity를 연결하는 플러그인. NatNet SDK(C# Managed Wrapper)를 통해 Motive 소프트웨어로부터 실시간 스켈레톤/리짓바디 데이터를 수신한다. --- ## 폴더 구조 ``` OptiTrack Unity Plugin/ └── OptiTrack/ ├── Scripts/ # 핵심 런타임 스크립트 │ ├── OptitrackStreamingClient.cs ← 네트워크 클라이언트 (메인 허브) │ ├── OptitrackSkeletonAnimator_Mingle.cs ← 커스텀 스켈레톤 애니메이터 (Streamingle 전용) │ ├── OptitrackRigidBody.cs ← 리짓바디 추적 │ ├── OptitrackRawDataReceiver.cs ← 원시 NatNet 데이터 수신 │ ├── OptitrackVisualizer.cs ← 씬뷰 디버그 시각화 │ └── OptitrackTrainedMarkerset.cs ← Trained Markerset 지원 ├── Plugins/ │ ├── Managed/NatNetLib/ ← C# NatNet 래퍼 (Client.cs, Native.cs) │ ├── x86/NatNetLib.dll ← 32bit 네이티브 라이브러리 │ └── x86_64/NatNetLib.dll ← 64bit 네이티브 라이브러리 ├── Editor/ ← Inspector 커스텀 에디터 ├── Prefabs/ ← 씬 배치용 프리팹 │ ├── Client - OptiTrack.prefab ← StreamingClient 오브젝트 │ ├── BaseAvatar - OptiTrack.prefab ← 기본 스켈레톤 아바타 │ ├── BaseAvatar - OptiTrack ver 2.prefab ← 개선된 스켈레톤 아바타 │ └── Rigid Body.prefab ← 리짓바디 추적 오브젝트 └── BaseAvatar/ ← Y Bot 베이스 메시 에셋 ``` --- ## 핵심 클래스 설명 ### OptitrackStreamingClient **역할:** Motive(서버) ↔ Unity(클라이언트) 간 NatNet 통신 허브 **Inspector 설정:** | 항목 | 설명 | |---|---| | `ServerAddress` | Motive PC IP (로컬: `127.0.0.1`) | | `LocalAddress` | Unity PC 네트워크 인터페이스 IP | | `ConnectionType` | `Multicast` / `Unicast` | | `SkeletonCoordinates` | `Local` (권장) / `Global` | | `BoneNamingConvention` | `Motive` / `FBX` / `BVH` | | `RecordOnPlay` | 재생 시 Motive 녹화 자동 시작 | | `SkipDataDescriptions` | 리짓바디만 쓸 때 네트워크 절약 | **주요 공개 API:** ```csharp // 스켈레톤 최신 상태 조회 OptitrackSkeletonState GetLatestSkeletonState(int skeletonId) // 리짓바디 최신 상태 조회 OptitrackRigidBodyState GetLatestRigidBodyState(int rigidBodyId, bool useNetworkCompensation) // 이름으로 스켈레톤 정의 조회 OptitrackSkeletonDefinition GetSkeletonDefinitionByName(string name) // 이름으로 리짓바디 ID 조회 int GetRigidBodyIdByName(string name) // 등록 void RegisterSkeleton(MonoBehaviour component, string skeletonAssetName) void RegisterRigidBody(MonoBehaviour component, int rigidBodyId) ``` **스레드 안전성:** - `OnNatNetFrameReceived`는 백그라운드 스레드에서 호출됨 - `m_frameDataUpdateLock` 으로 상태 딕셔너리 보호 - Unity API 접근은 반드시 메인 스레드에서 --- ### OptitrackSkeletonAnimator_Mingle **역할:** Streamingle 전용 커스텀 스켈레톤 애니메이터. 기존 원본 `OptitrackSkeletonAnimator.cs`를 대체함. **원본과의 차이점:** - Humanoid Avatar 없이 FBX 노드 직접 매핑 방식 - `GetBoneTransform(HumanBodyBones)` 제공 → `KindRetargeting`에서 `Animator.GetBoneTransform()` 대신 사용 - 스파인/넥 체인 전용 헬퍼 메서드 (`GetSpineChainTransforms`, `GetNeckChainTransforms`) - torn read 방지 스냅샷 버퍼 적용 - 0.1mm 이하 위치 노이즈, 0.00001 이하 쿼터니언 노이즈 스냅 처리 **본 매핑 구조 (`OptiTrackBoneMapping`):** ``` Motive 본 이름 (optiTrackBoneName) → FBX 노드 이름 (fbxNodeName) → Transform (cachedTransform) ``` **Motive → FBX 기본 본 이름 매핑:** | Motive (OptiTrack) | FBX 노드 접미사 | Unity HumanBodyBones | |---|---|---| | `Hip` | `Hips` | `Hips` | | `Ab` | `Spine` | `Spine` | | `Spine2~4` | `Spine1~3` | `Chest` 분배 | | `Chest` | `Spine4` | `UpperChest` | | `Neck` / `Neck2` | `Neck` / `Neck1` | `Neck` 분배 | | `Head` | `Head` | `Head` | | `LShoulder` / `LUArm` / `LFArm` / `LHand` | `LeftShoulder~Hand` | 왼팔 | | `RShoulder` / `RUArm` / `RFArm` / `RHand` | `RightShoulder~Hand` | 오른팔 | | `LThigh` / `LShin` / `LFoot` / `LToe` | `LeftUpLeg~ToeBase` | 왼다리 | | `RThigh` / `RShin` / `RFoot` / `RToe` | `RightUpLeg~ToeBase` | 오른다리 | | `LThumb1~3`, `LIndex1~3` ... | `LeftHandThumb1~3` ... | 왼손 손가락 | | `RThumb1~3`, `RIndex1~3` ... | `RightHandThumb1~3` ... | 오른손 손가락 | **Inspector에서 매핑 설정 방법:** 1. Inspector → `FBX 분석 → 자동 매핑 생성` 버튼 클릭 2. 매핑 실패 본은 `logUnmappedBones = true`로 확인 3. 수동 수정 가능 (`boneMappings` 리스트) **주요 공개 API (KindRetargeting 연동):** ```csharp // HumanBodyBones로 Transform 조회 (CustomRetargetingScript에서 사용) Transform GetBoneTransform(HumanBodyBones bone) // OptiTrack 본 이름으로 Transform 직접 조회 Transform GetMappedTransform(string optiTrackBoneName) // 스파인/넥 체인 Transform 리스트 반환 List GetSpineChainTransforms() // Ab → Spine2 → Spine3 → Spine4 → Chest List GetNeckChainTransforms() // Neck → Neck2 // 체인 누적 로컬 회전 계산 static Quaternion ComputeChainRotation(List chain) ``` **실행 순서:** `[DefaultExecutionOrder(-100)]` — 대부분의 스크립트보다 먼저 실행 --- ### OptitrackRigidBody **역할:** Motive에서 스트리밍된 리짓바디의 위치/회전을 실시간으로 GameObject에 적용 **요구 컴포넌트:** `PropTypeController` (KindRetargeting 의존성) **동작:** - `propName` 설정 시 이름으로 ID 자동 해석 (ID 직접 입력 불필요) - `useNetworkCompensation`: 네트워크 지연 보상 활성화 - `OnBeforeRender` 훅으로 렌더 직전 최신 포즈 적용 (레이턴시 최소화) --- ## 데이터 클래스 (상태/정의) ``` OptitrackSkeletonDefinition └── BoneDefinition[] (Id, ParentId, Name, Offset) └── BoneIdToParentIdMap Dictionary OptitrackSkeletonState └── BonePoses Dictionary ← 전역 좌표 └── LocalBonePoses Dictionary ← 로컬 좌표 OptitrackRigidBodyState └── Pose (Position, Orientation) └── IsTracked └── DeliveryTimestamp OptitrackPose └── Position (Vector3) └── Orientation (Quaternion) ``` --- ## 씬 설정 절차 ### 기본 설정 1. **`Client - OptiTrack` 프리팹** 씬에 배치 2. `ServerAddress` = Motive PC IP 설정 3. `LocalAddress` = 현재 Unity PC IP 설정 4. `ConnectionType` = `Unicast` (단일 PC 권장) 또는 `Multicast` 5. `SkeletonCoordinates` = `Local` ### 스켈레톤 추적 설정 1. **`BaseAvatar - OptiTrack ver 2` 프리팹** 씬에 배치 2. `SkeletonAssetName` = Motive에서 설정한 스켈레톤 에셋 이름 (예: `"Skeleton1"`) 3. Inspector → `FBX 분석 → 자동 매핑 생성` 실행 4. Play Mode 진입 후 `isSkeletonFound = true` 확인 ### 리짓바디 추적 설정 1. **`Rigid Body` 프리팹** 배치 2. `propName` = Motive 리짓바디 이름 설정 (또는 `rigidBodyId` 직접 입력) --- ## KindRetargeting과의 연동 ```csharp // CustomRetargetingScript.cs 에서 optitrackSource 참조 [SerializeField] public OptitrackSkeletonAnimator_Mingle optitrackSource; // 본 Transform 접근 래퍼 private Transform GetSourceBoneTransform(HumanBodyBones bone) { if (optitrackSource != null) return optitrackSource.GetBoneTransform(bone); return null; } ``` **의존 방향:** ``` KindRetargeting.CustomRetargetingScript → OptitrackSkeletonAnimator_Mingle.GetBoneTransform(HumanBodyBones) → OptitrackStreamingClient.GetLatestSkeletonState() → NatNetLib (네이티브 DLL) ``` --- ## 주의사항 - `NatNetLib.dll`은 x86/x86_64 두 버전 존재 — 빌드 타겟에 맞게 설정 확인 - `OptitrackStreamingClient.cs`에 `using UnityEditor` 포함 → **빌드 시 에러 발생 가능** (Editor 전용 코드는 `#if UNITY_EDITOR` 처리 필요) - Motive 버전에 따라 `BoneNamingConvention`이 다를 수 있음 — 매핑 실패 시 확인 - `SkeletonCoordinates = Local` 사용 시 `BonePoses`가 아닌 `LocalBonePoses` 사용 - 스켈레톤 본 이름 앞에 Motive가 접두사(`SkeletonName_`)를 붙임 → `Mingle`에서 `_` 기준 split 처리 --- ## 디버그 | 방법 | 설명 | |---|---| | `logUnmappedBones = true` | 매핑 실패한 OptiTrack 본을 콘솔에 출력 | | `DrawMarkers = true` | 씬뷰에 마커 큐브 시각화 | | `DrawCameras = true` | OptiTrack 카메라 위치 시각화 (Motive 3.0+) | | `isSkeletonFound` 확인 | Inspector에서 연결 성공 여부 확인 | | `debugReceivedBoneNames` | Motive에서 수신된 실제 본 목록 Inspector 확인 |