- OptitrackStreamingClient.FillBoneSnapshot: L/R 쌍 본은 위치 유지 + 회전 YZ반사 후 스왑, 중심 본(Hip/척추 등)은 위치 X반전 + 회전 YZ반사 - OptitrackStreamingClient.GetLatestRigidBodyState: 리짓바디 위치·회전 YZ반사 (기존) - GetOrBuildMirrorBoneIdMap: SkeletonName_BoneName 접두사 형식 지원 - 마커/TMarkerset 마커/TMarkerset BonePoses 미러 적용 - OptitrackSkeletonAnimator_Mingle: MirrorMode 토글 시 1€ 필터 상태 자동 리셋 - 월드 공간 미러 코드(ApplyWorldSpaceMirror 등) 제거, 데이터 수신 레벨에서 처리 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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:
// 스켈레톤 최신 상태 조회
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에서 매핑 설정 방법:
- Inspector →
FBX 분석 → 자동 매핑 생성버튼 클릭 - 매핑 실패 본은
logUnmappedBones = true로 확인 - 수동 수정 가능 (
boneMappings리스트)
주요 공개 API (KindRetargeting 연동):
// 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)
씬 설정 절차
기본 설정
Client - OptiTrack프리팹 씬에 배치ServerAddress= Motive PC IP 설정LocalAddress= 현재 Unity PC IP 설정ConnectionType=Unicast(단일 PC 권장) 또는MulticastSkeletonCoordinates=Local
스켈레톤 추적 설정
BaseAvatar - OptiTrack ver 2프리팹 씬에 배치SkeletonAssetName= Motive에서 설정한 스켈레톤 에셋 이름 (예:"Skeleton1")- Inspector →
FBX 분석 → 자동 매핑 생성실행 - Play Mode 진입 후
isSkeletonFound = true확인
리짓바디 추적 설정
Rigid Body프리팹 배치propName= Motive 리짓바디 이름 설정 (또는rigidBodyId직접 입력)
KindRetargeting과의 연동
// 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 확인 |