# KindRetargeting OptiTrack 모션캡처 데이터를 대상 아바타에 리타게팅하는 커스텀 파이프라인. FinalIK 기반 Two-Bone IK, 발 접지, 어깨 보정, 손가락 포즈 제어, 다중 아바타 동기화를 포함한다. --- ## 폴더 구조 ``` KindRetargeting/ ├── CustomRetargetingScript.cs ← 메인 허브 (전신 리타게팅 + 설정 저장/로드) ├── TwoBoneIKSolver.cs ← FinalIK 기반 사지 IK 솔버 ├── ShoulderCorrectionFunction.cs ← 팔 높이 기반 어깨 보정 ├── FootGroundingController.cs ← 2-Pass HIK 스타일 발 접지 ├── LimbWeightController.cs ← 거리/상황 기반 IK 가중치 자동화 ├── FingerShapedController.cs ← 손가락 포즈 수동 제어 (Muscle 기반) ├── PropLocationController.cs ← 프랍 부착점 관리 (손/머리) ├── SimplePoseTransfer.cs ← 1:N 아바타 포즈 동기화 ├── OffsetTransfer.cs ← 오프셋 Transform 전송 유틸 ├── IKTargetGizmo.cs ← IK 타겟 기즈모 시각화 ├── PropTypeController.cs ← 프랍 종류 분류 컴포넌트 ├── Enums/ │ └── RetargetingEnums.cs ← FingerCopyMode, PropType 열거형 ├── Remote/ │ ├── RetargetingRemoteController.cs ← WebSocket 원격 제어 (포트 64212) │ └── RetargetingWebSocketServer.cs ← WS 서버 구현 └── Editor/ ├── BaseRetargetingEditor.cs ← 공통 에디터 베이스 ├── CustomRetargetingScriptEditor.cs ← 메인 Inspector UI ├── SimplePoseTransferEditor.cs ← 포즈 전송 Inspector └── RetargetingControlWindow.cs ← 전용 에디터 윈도우 ``` --- ## 아키텍처 개요 ``` OptitrackSkeletonAnimator_Mingle (소스 데이터) ↓ CustomRetargetingScript (메인 허브) ├── TwoBoneIKSolver ← FinalIK 래퍼, 4사지 IK ├── ShoulderCorrectionFunction ← 어깨 보정 ├── FootGroundingController ← 발 접지 (Pre-IK / Post-IK) ├── LimbWeightController ← 다층 IK 가중치 자동화 ├── FingerShapedController ← HumanPose Muscle 제어 └── PropLocationController ← 손/머리 부착점 생성/관리 SimplePoseTransfer ← 독립 컴포넌트, 멀티 아바타 동기화 RetargetingRemoteController ← WebSocket으로 원격 파라미터 제어 ``` --- ## 실행 순서 | 타이밍 | 스크립트 | 작업 | |---|---|---| | `Update` (Order -100) | `OptitrackSkeletonAnimator_Mingle` | Motive → Transform 적용 | | `LateUpdate` (미지정) | `CustomRetargetingScript` | 전신 리타게팅 + IK | | `LateUpdate` (Order 16001) | `SimplePoseTransfer` | 멀티 아바타 동기화 | --- ## 핵심 스크립트 상세 ### CustomRetargetingScript — 메인 허브 전신 리타게팅, 캘리브레이션, 설정 저장/로드를 담당하는 핵심 컴포넌트. **Inspector 주요 파라미터:** | 헤더 | 파라미터 | 설명 | |---|---|---| | 힙 위치 보정 | `hipsOffsetX/Y/Z` | 캐릭터 로컬 좌표 기준 힙 오프셋 (-1 ~ 1) | | 무릎 조정 | `kneeInOutWeight` | 무릎 안/밖 위치 (-1 ~ 1) | | 무릎 조정 | `kneeFrontBackWeight` | 무릎 앞/뒤 위치 (기본 0.4) | | 발 IK | `footFrontBackOffset` | 발 앞뒤 오프셋 | | 발 IK | `footInOutOffset` | 발 벌리기/모으기 | | 바닥 | `floorHeight` | 바닥 높이 기준 오프셋 | | 발목 | `minimumAnkleHeight` | 최소 발목 높이 (수동) | | 머리 회전 | `headRotationOffsetX/Y/Z` | 머리 회전 보정 오프셋 | | 크기 | `avatarScale` | 아바타 전체 크기 (0.1 ~ 3) | | 크기 | `headScale` | 머리 크기 독립 조정 | **캘리브레이션 메서드:** ```csharp void I_PoseCalibration() // I-포즈에서 회전 오프셋 캐싱 void ResetPoseAndCache() // 캘리브레이션 초기화 void CalibrateHeadToForward() // 현재 머리 방향을 정면으로 캘리브레이션 void SaveSettings() // Assets/StreamingleData/ 에 JSON 저장 void LoadSettings() // 저장된 JSON 로드 bool HasCachedSettings() // 저장된 캘리브레이션 여부 확인 ``` **외부 접근 API:** ```csharp float GetHeadScale() void SetHeadScale(float value) void SetAvatarScale(float value) void ResetScale() ``` **축 정규화:** 소스 스켈레톤의 좌표축이 월드와 다를 경우 자동 감지: ``` localAxisForWorldRight // 월드 X(Right)를 담당하는 로컬 축 localAxisForWorldUp // 월드 Y(Up) localAxisForWorldForward // 월드 Z(Forward) ``` **설정 저장 경로:** `Assets/StreamingleData/RetargetingSettings/{캐릭터명}.json` --- ### TwoBoneIKSolver — IK 솔버 FinalIK의 `IKSolverTrigonometric.Solve()`를 래핑하여 양팔/양다리 4개 사지에 적용. **LimbIK 구조:** ```csharp class LimbIK { Transform target; // IK 목표 위치/회전 Transform bendGoal; // 무릎/팔꿈치 방향 힌트 float positionWeight; // 위치 IK 가중치 (0~1) float rotationWeight; // 회전 IK 가중치 (0~1) float bendGoalWeight; // bend goal 가중치 (0~1) // 내부 캐시 Transform upper, lower, end; float upperLength, lowerLength; Vector3 localBendNormal; } ``` **초기화:** T-포즈 시 `bendNormal` 계산 후 로컬 좌표로 캐싱 → 런타임 계산 최소화 **`CalculateAutoFloorHeight()`:** 왼다리 길이 기반 자동 바닥 높이 계산 --- ### LimbWeightController — IK 가중치 자동화 상황에 따라 IK 가중치를 자동으로 조절. 다층 레이어 구조로 동작. **가중치 레이어 구조:** | 인덱스 | 팔 가중치 | 다리 가중치 | 힙 가중치 | |---|---|---|---| | `[0]` | 양손 간 거리 (악수/박수 감지) | 앉기 시 발-힙 수평거리 | 의자 프랍과의 거리 | | `[1]` | 프랍과의 거리 | 발 높이 | 바닥 기준 힙 높이 | **합성 방식:** - 팔: `GetMaxValue()` — 어느 조건이든 하나라도 해당되면 IK 활성화 - 다리/힙: `GetMinValue()` — 모든 조건이 만족할 때만 IK 활성화 **스무딩:** `weightSmoothSpeed`로 `Mathf.Lerp` 처리 → 급격한 IK 전환 방지 **props 목록 (자동 수집):** - `PropTypeController`가 붙은 씬의 모든 오브젝트 - 씬 내 다른 캐릭터의 손(LeftHand, RightHand) Transform **의자 앉기 처리:** - `PropType.Chair`인 프랍과 힙 거리 계산 - 가까울수록 `hipsWeights[0]` 감소, `ChairSeatHeightOffset` 자동 적용 --- ### FootGroundingController — 발 접지 HIK 스타일 2-Pass 접지 시스템. **Pass 1 — OnUpdate (Pre-IK):** - IK 타겟 Y 좌표를 올려 발가락이 바닥을 뚫지 않도록 예방 - `activationHeight` 이하일 때만 동작 - Toes 본 존재 여부에 따라 처리 분기: - Toes 있음: 발가락 월드 Y 예측 후 보정 - Toes 없음: 최소 발목 높이(`footHeight`) 보장 **Pass 2 — OnLateUpdate (Post-IK):** - IK 후 실제 Toes Y의 잔차를 Foot 회전(Pitch)으로 미세 보정 - `plantThreshold` 이내 오차만 처리 **설정 파라미터:** ``` groundHeight 바닥 Y 좌표 (월드) groundingWeight 보정 강도 (0~1) activationHeight 이 높이 이상이면 공중으로 판정 (보정 안 함) plantThreshold 접지 판정 범위 (m) smoothSpeed 보정량 스무딩 속도 ``` --- ### ShoulderCorrectionFunction — 어깨 보정 팔이 들릴 때 어깨 본을 자동으로 들어올리는 보정. **동작 원리:** 1. 팔꿈치 Y - 어깨 Y = 높이 차이 계산 2. `shoulderCorrectionCurve`로 비선형 가중치 산출 3. 어깨 본만 회전 → UpperArm 원래 회전 즉시 복원 - 어깨 움직임이 팔 포즈에 영향 주지 않음 **설정 파라미터:** ``` blendStrength 보정 강도 배율 (0~5) maxShoulderBlend 최대 회전 비율 상한 (0~1) reverseLeft/RightRotation 어깨 회전 반전 maxHeightDifference 보정 시작 높이 차 상한 minHeightDifference 보정 시작 높이 차 하한 shoulderCorrectionCurve 커스텀 응답 커브 ``` --- ### FingerShapedController — 손가락 포즈 HumanPose Muscle 값으로 손가락 포즈를 수동으로 제어. **작동 원리 (트릭 포함):** 1. SetHumanPose 전에 비손가락 본 로컬 회전 저장 2. `HumanPoseHandler.SetHumanPose()` 로 손가락 머슬 적용 3. 저장했던 비손가락 본 회전 즉시 복원 → `SetHumanPose`가 전체 본에 영향 주는 문제를 해결 **Muscle 인덱스 오프셋:** - 왼손: `muscles[55]` 부터 - 오른손: `muscles[75]` 부터 - 각 손가락: Thumb(0~3), Index(4~7), Middle(8~11), Ring(12~15), Pinky(16~19) **제어 파라미터 (모두 -1~1):** ``` left/rightThumbCurl 엄지 구부리기 left/rightIndexCurl 검지 left/rightMiddleCurl 중지 left/rightRingCurl 약지 left/rightPinkyCurl 새끼 left/rightSpreadFingers 손가락 벌리기 ``` --- ### PropLocationController — 프랍 부착점 T-포즈를 기준으로 손/머리에 Target-Offset 계층을 생성하고 프랍 오브젝트를 부착. **생성되는 계층:** ``` [손 본] └── Left_Hand_Target (위치: 손 본 + 오프셋, 회전: 90°X) └── Left_Hand_Offset (로컬 zero) └── [부착된 프랍] ``` **오프셋 기본값:** - 왼손: `(-0.039, -0.022, 0)` - 오른손: `(+0.039, -0.022, 0)` - 머리: `(0, 0.16, 0)` **API:** ```csharp void MoveToLeftHand(GameObject obj) // 왼손에 부착 void MoveToRightHand(GameObject obj) // 오른손에 부착 void MoveToHead(GameObject obj) // 머리에 부착 void DetachProp(GameObject obj) // 분리 Transform GetLeftHandOffset() // 오프셋 Transform 반환 Transform GetRightHandOffset() Transform GetHeadOffset() GameObject[] GetLeftHandProps() // 현재 부착된 프랍 목록 ``` --- ### SimplePoseTransfer — 멀티 아바타 동기화 1개 소스 Animator의 포즈를 N개 타겟 Animator에 복사. `CustomRetargetingScript`와 독립적으로 동작. **초기화 시 T-포즈 기준 회전 차이 계산:** ```csharp boneRotationDifferences[targetIndex, boneIndex] = Quaternion.Inverse(sourceBone.rotation) * targetBone.rotation ``` **런타임 적용:** ```csharp targetBone.rotation = sourceBone.rotation * boneRotationDifferences[t, i] ``` **추가 기능:** - `transferHeadScale`: 소스의 머리 스케일을 타겟에 동기화 - Hips(0번 본) 위치 동기화 포함 --- ### RetargetingRemoteController — WebSocket 원격 제어 **포트:** `64212` (StreamDeck 서버 `64211`과 별도) **지원 액션:** | action | 설명 | |---|---| | `refresh` | 캐릭터 목록 + 손 포즈 프리셋 전송 | | `getCharacterData` | 특정 캐릭터의 모든 파라미터 조회 | | `updateValueRealtime` | 실시간 단일 파라미터 변경 | | `setHandPosePreset` | 손 포즈 프리셋 적용 (가위/바위/보/브이/검지/초기화) | | `calibrateIPose` | I-포즈 캘리브레이션 실행 | | `resetCalibration` | 캘리브레이션 초기화 | | `calibrateHeadForward` | 머리 정면 캘리브레이션 | | `autoHipsOffset` | 다리 길이 비교 힙 자동 보정 | | `autoCalibrateAll` | 전체 자동 보정 (크기+힙+머리 순서로) | **autoCalibrateAll 순서:** 1. `ResetScale()` + `hipsOffsetY` 다리 길이 차이로 설정 2. 1프레임 대기 3. 목 높이 비율(`sourceNeck.y / targetNeck.y`)로 avatarScale 계산 4. 1프레임 대기 5. hipsOffsetY 재계산 + `CalibrateHeadToForward()` **주의:** Reflection(`BindingFlags.NonPublic`)으로 private 필드 접근 → 필드명 변경 시 원격 제어 무음 실패 --- ## 열거형 ```csharp // RetargetingEnums.cs namespace KindRetargeting.EnumsList enum FingerCopyMode { None, // 손가락 복제 안 함 MuscleData, // Unity Muscle 시스템 사용 Rotation // Transform.rotation 직접 복제 } enum PropType { None, Object, // 일반 프랍 Chair, // 의자 (앉기 가중치 적용) Hand // 손 프랍 } ``` --- ## 씬 설정 체크리스트 ### 단일 캐릭터 설정 - [ ] `OptitrackStreamingClient` 씬에 배치 (포트/IP 설정) - [ ] `OptitrackSkeletonAnimator_Mingle` 배치 + 본 매핑 생성 - [ ] `CustomRetargetingScript` 배치 - `optitrackSource` 연결 - 서브컴포넌트(`ikSolver`, `shoulderCorrection` 등) 설정 - [ ] `RetargetingRemoteController` 배치 + 캐릭터 등록 ### 프랍 설정 - [ ] 프랍 오브젝트에 `PropTypeController` 추가 + `propType` 설정 - [ ] 의자 프랍은 `PropType.Chair` 설정 - [ ] 리짓바디 추적 프랍은 `OptitrackRigidBody` 추가 + `propName` 설정 ### 멀티 아바타 설정 - [ ] `SimplePoseTransfer` 배치 - `sourceBone` = 리타게팅된 메인 아바타 Animator - `targetBones` = 동기화할 아바타 Animator 배열 --- ## 자주 발생하는 문제 | 증상 | 원인 | 해결 | |---|---|---| | 아바타가 전혀 안 움직임 | `optitrackSource` 미연결 또는 `isSkeletonFound = false` | StreamingClient IP 확인, 본 매핑 재생성 | | 발이 바닥을 뚫음 | `FootGroundingController.groundHeight` 설정 오류 | `groundHeight` 값 조정 | | 힙이 떠있음 | `hipsOffsetY` 보정 필요 | Remote 또는 Inspector에서 조정, `autoHipsOffset` 사용 | | 어깨가 부자연스러움 | `ShoulderCorrectionFunction` 미설정 | `blendStrength` / `shoulderCorrectionCurve` 튜닝 | | 손가락이 이상하게 꺾임 | `FingerCopyMode` 설정 문제 | `MuscleData` 또는 `Rotation` 모드 변경 | | 멀티 아바타 포즈 틀어짐 | `SimplePoseTransfer` Init 타이밍 문제 | Play 후 `Init()` 수동 호출 또는 실행 순서 확인 | | 원격 파라미터 변경 안 됨 | Reflection 필드명 불일치 | `RetargetingRemoteController` 필드명 확인 |