233 lines
9.3 KiB (Stored with Git LFS)
Markdown

# 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<Transform> GetSpineChainTransforms() // Ab → Spine2 → Spine3 → Spine4 → Chest
List<Transform> GetNeckChainTransforms() // Neck → Neck2
// 체인 누적 로컬 회전 계산
static Quaternion ComputeChainRotation(List<Transform> chain)
```
**실행 순서:** `[DefaultExecutionOrder(-100)]` — 대부분의 스크립트보다 먼저 실행
---
### OptitrackRigidBody
**역할:** Motive에서 스트리밍된 리짓바디의 위치/회전을 실시간으로 GameObject에 적용
**요구 컴포넌트:** `PropTypeController` (KindRetargeting 의존성)
**동작:**
- `propName` 설정 시 이름으로 ID 자동 해석 (ID 직접 입력 불필요)
- `useNetworkCompensation`: 네트워크 지연 보상 활성화
- `OnBeforeRender` 훅으로 렌더 직전 최신 포즈 적용 (레이턴시 최소화)
---
## 데이터 클래스 (상태/정의)
```
OptitrackSkeletonDefinition
└── BoneDefinition[] (Id, ParentId, Name, Offset)
└── BoneIdToParentIdMap Dictionary<int,int>
OptitrackSkeletonState
└── BonePoses Dictionary<int, OptitrackPose> ← 전역 좌표
└── LocalBonePoses Dictionary<int, OptitrackPose> ← 로컬 좌표
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 확인 |