233 lines
9.3 KiB (Stored with Git LFS)
Markdown
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 확인 |
|