..

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 머리 크기 독립 조정

캘리브레이션 메서드:

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 활성화

스무딩: weightSmoothSpeedMathf.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(03), 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 순서:

  1. ResetScale() + hipsOffsetY 다리 길이 차이로 설정
  2. 1프레임 대기
  3. 목 높이 비율(sourceNeck.y / targetNeck.y)로 avatarScale 계산
  4. 1프레임 대기
  5. 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 = 리타게팅된 메인 아바타 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 필드명 확인