- FinalIK IKSolverTrigonometric 의존성 제거, 자체 솔버 구현 - cosine law 대신 소스 무릎 위치를 비율 스케일하여 타겟 무릎 직접 배치 - 180° 특이점 없이 정상↔역관절 자연스러운 전환 - FromToRotation 기반 본 회전으로 twist 보존 - 팔/다리 모두 소스 본 참조 설정, 소스 없으면 cosine law fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
KindRetargeting
OptiTrack 모션캡처 데이터를 대상 아바타에 리타게팅하는 커스텀 파이프라인. FinalIK 기반 Two-Bone IK, 발 접지, 어깨 보정, 손가락 포즈 제어, 다중 아바타 동기화를 포함한다.
폴더 구조
KindRetargeting/
├── CustomRetargetingScript.cs ← 메인 허브 (전신 리타게팅 + 설정 저장/로드)
├── TwoBoneIKSolver.cs ← FinalIK 기반 사지 IK 솔버
├── ShoulderCorrectionFunction.cs ← 팔 높이 기반 어깨 보정
├── 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 ← 어깨 보정
├── 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 |
머리 크기 독립 조정 |
캘리브레이션 메서드:
void I_PoseCalibration() // I-포즈에서 회전 오프셋 캐싱
void ResetPoseAndCache() // 캘리브레이션 초기화
void CalibrateHeadToForward() // 현재 머리 방향을 정면으로 캘리브레이션
void SaveSettings() // Assets/StreamingleData/ 에 JSON 저장
void LoadSettings() // 저장된 JSON 로드
bool HasCachedSettings() // 저장된 캘리브레이션 여부 확인
외부 접근 API:
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 구조:
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자동 적용
ShoulderCorrectionFunction — 어깨 보정
팔이 들릴 때 어깨 본을 자동으로 들어올리는 보정.
동작 원리:
- 팔꿈치 Y - 어깨 Y = 높이 차이 계산
shoulderCorrectionCurve로 비선형 가중치 산출- 어깨 본만 회전 → UpperArm 원래 회전 즉시 복원
- 어깨 움직임이 팔 포즈에 영향 주지 않음
설정 파라미터:
blendStrength 보정 강도 배율 (0~5)
maxShoulderBlend 최대 회전 비율 상한 (0~1)
reverseLeft/RightRotation 어깨 회전 반전
maxHeightDifference 보정 시작 높이 차 상한
minHeightDifference 보정 시작 높이 차 하한
shoulderCorrectionCurve 커스텀 응답 커브
FingerShapedController — 손가락 포즈
HumanPose Muscle 값으로 손가락 포즈를 수동으로 제어.
작동 원리 (트릭 포함):
- SetHumanPose 전에 비손가락 본 로컬 회전 저장
HumanPoseHandler.SetHumanPose()로 손가락 머슬 적용- 저장했던 비손가락 본 회전 즉시 복원
→ SetHumanPose가 전체 본에 영향 주는 문제를 해결
Muscle 인덱스 오프셋:
- 왼손:
muscles[55]부터 - 오른손:
muscles[75]부터 - 각 손가락: Thumb(0
3), Index(47), Middle(811), Ring(1215), 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:
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-포즈 기준 회전 차이 계산:
boneRotationDifferences[targetIndex, boneIndex] =
Quaternion.Inverse(sourceBone.rotation) * targetBone.rotation
런타임 적용:
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 순서:
ResetScale()+hipsOffsetY다리 길이 차이로 설정- 1프레임 대기
- 목 높이 비율(
sourceNeck.y / targetNeck.y)로 avatarScale 계산 - 1프레임 대기
- hipsOffsetY 재계산 +
CalibrateHeadToForward()
주의: Reflection(BindingFlags.NonPublic)으로 private 필드 접근 → 필드명 변경 시 원격 제어 무음 실패
열거형
// 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= 리타게팅된 메인 아바타 AnimatortargetBones= 동기화할 아바타 Animator 배열
자주 발생하는 문제
| 증상 | 원인 | 해결 |
|---|---|---|
| 아바타가 전혀 안 움직임 | optitrackSource 미연결 또는 isSkeletonFound = false |
StreamingClient IP 확인, 본 매핑 재생성 |
| 힙이 떠있음 | hipsOffsetY 보정 필요 |
Remote 또는 Inspector에서 조정, autoHipsOffset 사용 |
| 어깨가 부자연스러움 | ShoulderCorrectionFunction 미설정 |
blendStrength / shoulderCorrectionCurve 튜닝 |
| 손가락이 이상하게 꺾임 | FingerCopyMode 설정 문제 |
MuscleData 또는 Rotation 모드 변경 |
| 멀티 아바타 포즈 틀어짐 | SimplePoseTransfer Init 타이밍 문제 |
Play 후 Init() 수동 호출 또는 실행 순서 확인 |
| 원격 파라미터 변경 안 됨 | Reflection 필드명 불일치 | RetargetingRemoteController 필드명 확인 |