381 lines
14 KiB (Stored with Git LFS)
Markdown

# 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` 필드명 확인 |