381 lines
14 KiB (Stored with Git LFS)
Markdown
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` 필드명 확인 |
|