FBX를 VRM으로 바꿨더니 팔이 한 관절씩 밀려 돌거나, 필수 본이 비어 있다는 경고가 나온다면 먼저 본 매핑을 의심해야 합니다. VRM 본 매핑은 FBX의 본을 VRM 휴머노이드 규격이 정해 둔 칸(hips, leftUpperArm 같은 이름)에 짝지어 주는 단계입니다. Mixamo나 3ds Max 파일이 여기서 틀어지는 경우는 대개 다섯 가지입니다. 이름 규칙 충돌(Collar와 Shoulder), 같은 이름의 중복 본, mixamorig: 접두어, 손가락 누락과 엄지 이름 차이, 계층 사이 빈칸입니다.
이 사이트의 FBX → VRM 변환기는 브라우저에서 three.js r160 FBXLoader로 FBX를 읽어 VRM 1.0으로 내보냅니다. 이 도구를 만들며 3ds Max 리그 Character_0726.fbx에서 이름 충돌과 계층 빈칸 문제를 직접 겪었고, 같은 이름의 중복 본도 걸러 내야 했습니다. 팔 이름 규칙 감지와 계층 빈칸 채우기를 넣은 지금은 이 리그의 휴머노이드 55칸을 모두 자동으로 채웁니다. Mixamo 리그는 손가락까지 52개 본이 한 번에 잡힙니다.

증상으로 원인 찾기
표의 마지막 줄은 매핑 문제가 아닌데 매핑 문제처럼 보이는 경우입니다.
| 증상 | 흔한 원인 | 먼저 볼 곳 |
|---|---|---|
| 위팔 칸이 비고 어깨 칸에 위팔 본이 들어감 | Collar/Shoulder 이름 충돌 | shoulder 칸 본이 정말 쇄골인지 |
| 팔 회전이 한 관절씩 밀림 | 같은 충돌이 한 칸씩 밀려 채워짐 | shoulder→upperArm→lowerArm→hand 순서 |
| 매핑 표는 멀쩡한데 한 부위가 안 움직임 | 중복 본 중 스킨에 안 묶인 쪽이 잡힘 | 스킨이 참조하는 본 목록 |
| 매핑이 거의 0칸 | mixamorig: 접두어, 이름 정리 규칙 | 로더가 읽은 실제 본 이름 |
| 손가락이 비거나 엄지가 한 마디 밀림 | 손가락 본 없음, Pinky/Little, VRM 0.x 엄지 이름 | Thumb1이 thumbMetacarpal인지 |
| 척추·가슴·목 칸 일부가 빔 | 번호로만 구분되는 중간 본 | hips에서 head까지의 부모 사슬 |
| 55칸이 찼는데 모델이 찢어지거나 아주 작음 | 바인드 행렬·스켈레톤 저장 문제 | 파일을 그대로 읽은 치수 |
VRM 휴머노이드 칸은 55개, 필수는 15개
VRM 1.0 규격의 humanoid 문서는 휴머노이드 본을 몸통 5개(hips, spine, chest, upperChest, neck), 머리 4개(head, 두 눈, jaw), 다리 8개, 팔 8개, 손가락 30개로 정의합니다. 합하면 55개, 변환기 화면의 55칸입니다. 필수는 15개뿐입니다. hips, spine, head, 그리고 좌우 각각 upperLeg·lowerLeg·foot, upperArm·lowerArm·hand입니다. 어깨(shoulder), 가슴, 목, 눈, 턱, 발가락, 손가락은 모두 선택입니다. 손가락이 통째로 비어도 VRM은 만들어지지만, 위팔 하나가 비면 규격에 맞지 않습니다.
매핑과 관련된 규칙은 세 가지입니다(2026년 9월 확인 기준).
- 휴머노이드 본은 유일해야 합니다. 한 칸에는 노드 하나만 들어갑니다.
- 각 칸의 부모 칸이 정해져 있습니다. leftUpperArm의 부모는 leftShoulder, leftShoulder의 부모는 upperChest입니다. 선택 칸인 부모가 비어 있으면 부모의 부모 칸을 찾아 올라갑니다.
- 휴머노이드 본 사이에 휴머노이드가 아닌 노드가 끼어 있어도 됩니다.
기본 자세는 +Z 방향을 보고 서서 팔을 X축과 나란하게 뻗고 손바닥이 아래(-Y)를 향하는 T포즈입니다. Character_0726은 처음부터 T포즈였습니다.
Collar가 어깨이고 Shoulder가 위팔인 리그
Character_0726.fbx는 3ds Max에서 내보낸 2.4MB 바이너리 FBX입니다. 문제는 관절 이름 규칙이었습니다. 쇄골 자리의 본 이름에 Collar가, 위팔 자리의 본 이름에 Shoulder가 들어갑니다. VRM의 shoulder는 쇄골 쪽 선택 칸이고 위팔은 필수 칸 upperArm이니, 같은 단어가 한 관절 어긋난 뜻으로 쓰인 셈입니다.
Mixamo와 3ds Max Biped는 이 점에서 VRM과 같은 편입니다. three-vrm 공식 예제의 Mixamo 대응표는 mixamorigLeftShoulder를 leftShoulder로, mixamorigLeftArm을 leftUpperArm으로 보냅니다. Autodesk MotionBuilder SDK의 Biped 템플릿 예제도 LeftShoulder를 L Clavicle, LeftArm을 L UpperArm에 대응시킵니다.

이 사이트 변환기도 처음에는 이름만 보고 짝을 지었습니다. 그러자 이름이 LeftShoulder인 본, 즉 실제로는 위팔인 본이 어깨 칸을 차지했고 위팔 칸은 비었습니다. 위팔은 필수 칸이라 이 상태로는 규격에 맞는 VRM이 되지 않습니다. 그래서 리그가 어떤 팔 이름 규칙을 쓰는지 먼저 감지해, 이 리그처럼 Collar가 있으면 Collar를 어깨로, Shoulder를 위팔로 읽도록 고쳤습니다. 여기에 뒤에서 설명할 계층 빈칸 채우기를 더하자 55칸이 모두 찼고, 맨 위 그림이 그 결과 표입니다.
직접 확인할 때는 이름보다 부모-자식 순서를 믿는 편이 정확합니다. shoulder 칸 본 아래로 upperArm, lowerArm, hand 칸 본이 차례로 이어져야 합니다. 사이에 보조 노드가 낄 수 있으니 조상과 자손 순서만 봅니다. hand 칸 위로 팔 본이 셋인데 칸은 둘만 찼다면 어딘가에서 한 칸이 밀렸거나 빠진 것입니다.
같은 이름 본이 여러 번 나올 때
Character_0726에는 본 노드가 132개 있었지만 같은 이름을 걸러 내면 80개였습니다. 이 사이트가 따로 분석한 3ds Max 모션캡처 FBX에서도 중첩된 중복 본 노드가 179개 나왔는데, 월드 위치가 전부 같아서 제거해도 결과가 달라지지 않았습니다. 적어도 그 파일에서 복제본은 모양을 바꾸지 않았고, 문제는 매핑에서 생깁니다.
같은 이름이 둘 이상이면 매핑 도구는 하나를 골라야 합니다. 이때 스킨이 실제로 참조하지 않는 복제본을 고르면, 그 본을 돌려도 메시가 따라오지 않습니다. 매핑 표의 이름은 멀쩡해서 애니메이션을 붙인 뒤에야 드러납니다. 같은 이름이 여럿이면 스킨 메시가 쓰는 본 목록(three.js라면 SkinnedMesh의 skeleton.bones)에 들어 있는 쪽을 칸에 넣으면 됩니다.
three.js에서는 이름 정리 때문에 중복이 새로 생길 수도 있습니다. r160 FBXLoader가 본 이름에 적용하는 PropertyBinding.sanitizeNodeName은 공백을 밑줄로 바꾸고 대괄호·마침표·콜론·빗금을 지웁니다. 원래 L Hand와 L_Hand로 달랐던 두 이름은 읽은 뒤 같은 이름이 됩니다.
Mixamo 리그의 접두어와 손가락 이름
mixamorig: 접두어
Mixamo에서 받은 FBX의 본 이름은 mixamorig:Hips처럼 접두어로 시작합니다. 접두어를 떼지 않고 Hips와 정확히 비교하면 하나도 못 잡고, arm이 들어가는지만 보는 비교는 LeftArm과 LeftForeArm을 구분하지 못합니다. three.js에서 읽으면 sanitizeNodeName이 콜론을 지워 mixamorigHips가 되고, three-vrm 예제 대응표의 키도 이 콜론 없는 형태입니다(2026년 9월 확인 기준). 비교 전에 콜론 유무와 상관없이 mixamorig 접두어를 떼고 대소문자를 맞추면 안전합니다.
손가락이 비거나 엄지가 한 마디 밀릴 때
Mixamo 손가락은 LeftHandThumb1, LeftHandIndex1처럼 이름 뒤에 번호가 붙습니다. three-vrm 예제 대응표는 Thumb1·2·3을 thumbMetacarpal·Proximal·Distal로, 나머지 손가락의 1·2·3을 Proximal·Intermediate·Distal로 보냅니다. 새끼손가락은 Mixamo에서 Pinky, VRM에서 Little이라 단어 사전에 Pinky가 없으면 새끼손가락 칸만 빕니다. 3ds Max Biped는 손가락을 L Finger0, L Finger01처럼 번호로만 부르고, 앞서 본 MotionBuilder 예제는 Finger0 계열을 엄지, Finger4 계열을 새끼손가락에 대응시킵니다.
엄지는 VRM 버전에 따라 칸 이름이 다릅니다. VRM 0.x의 엄지는 thumbProximal, thumbIntermediate, thumbDistal이고, VRM 1.0은 인체 구조에 맞춰 thumbMetacarpal, thumbProximal, thumbDistal로 바꿨습니다. 0.x 기준 대응표를 1.0에 그대로 쓰면 Thumb1이 1.0의 thumbProximal에 들어가 엄지가 한 마디씩 밀립니다.
손가락은 선택 칸이라 비어도 변환은 끝나지만, 손가락까지 추적하는 웹캠 모션캡처에 올려도 움직일 본이 없습니다. Mixamo 리그에서 잡히는 본 수 52는 three-vrm 예제 대응표의 항목 수와 같고, 그 표에는 선택 칸인 두 눈과 턱이 없습니다. 55에서 이 셋을 빼면 52입니다.
계층 사이 빈칸이 생길 때
hips, head, hand, foot은 이름으로 쉽게 잡히지만 그 사이의 척추·가슴·목은 Spine, Spine1, Spine2처럼 번호로만 구분되거나 리그마다 개수가 달라 이름만으로 칸을 정하기 어렵습니다. Character_0726을 고칠 때 팔 규칙 감지와 함께 넣은 것이 계층 사이 빈칸 채우기입니다. 이름으로 확실히 잡힌 위아래 본 사이에 빈칸이 남으면 둘을 잇는 부모 사슬에서 사이의 본을 찾아 채웁니다. 사이 노드가 비틀림 보조 본이라면 억지로 칸에 넣지 말고 휴머노이드가 아닌 중간 노드로 남겨 둡니다.
몸통에는 규격이 정한 조건이 하나 더 있습니다. upperChest는 chest가 있을 때만 쓸 수 있습니다. 척추 본이 둘뿐인 리그에서 chest를 비우고 upperChest만 채우면 규격에 어긋나므로, 두 본은 spine과 chest에 넣습니다.
3ds Max Biped에는 알아 둘 설정이 하나 있습니다. 3ds Max 2018 도움말에 따르면 Structure 롤아웃의 Triangle Neck은 켜면 쇄골을 목 대신 척추 맨 윗마디에 연결하고, 기본값은 꺼짐입니다. 즉 기본 Biped는 쇄골이 목 아래에 붙습니다. VRM 규격은 leftShoulder의 부모를 upperChest로 정해 두었으므로, 이 리그의 목 노드를 neck 칸에 넣으면 부모 관계가 어긋납니다. 규격 문장대로라면 neck 칸을 비워 목 노드를 중간 노드로 남기거나 Triangle Neck을 켜고 다시 내보내면 되지만, 이 사이트에서 Biped 리그로 시험해 보지는 않았습니다.
매핑이 끝나면 변환기 화면의 파란 화살표(+Z)가 캐릭터 정면을 향하는지 보고, 결과 VRM을 웹캠 모션캡처에 올려 팔과 손가락을 움직여 봅니다. 표에서 놓친 한 칸 밀림은 이때 드러납니다.
자주 묻는 질문
55칸이 다 찼는데 모델이 찢어지거나 아주 작게 나오면 왜 그런가요?
이때는 매핑보다 바인드 정보나 스켈레톤 저장 방식을 의심해야 합니다. 이 사이트에서 본 모캡 FBX 중에는 bind 행렬이 어긋나 폭 303 × 키 172 × 두께 181cm로 찢어진 파일이 있었고, 원인은 bind 행렬이 어긋난 FBX 글에 정리했습니다. 본 위치가 전부 0이라 30×39×22cm로 쪼그라든 Character_0630.fbx 같은 경우, 변환기는 본의 90% 이상이 위치 0이면 스킨에 남은 바인드 행렬로 스켈레톤을 자동 복원합니다.
VRM 0.x용 매핑 표를 1.0에 써도 되나요?
엄지 세 칸은 반드시 다시 맞춰야 합니다. Mixamo 기준으로 Thumb1이 thumbMetacarpal에 들어가야 1.0에 맞습니다. 반대로 1.0에서 선택인 chest와 neck은 0.x에서 필수였으므로, 1.0용으로 만든 매핑을 0.x 도구에 넘길 때는 이 두 칸이 비어 있지 않은지 봐야 합니다.
변환 전에 FBX가 제대로 읽히는지 먼저 볼 수 있나요?
3D 모델 뷰어에 열어 보면 됩니다. Character_0726은 오브젝트 141개, 메시 6개, 정점 87,210개로 표시됩니다. 여기서 모양이 정상이면 남은 원인은 매핑 쪽에서 찾으면 됩니다.
참고한 자료
- VRMC_vrm 1.0 humanoid specification (vrm-c/vrm-specification)
- VRMC_vrm 1.0 T-Pose specification (vrm-c/vrm-specification)
- VRM 0.x humanoid bone schema (vrm-c/vrm-specification)
- VRM1.0-beta以降のアップデート (thumb bone rename, vrm-c/vrm-specification issue #327)
- three-vrm example mixamoVRMRigMap.js (pixiv/three-vrm)
- three.js r160 PropertyBinding.js (sanitizeNodeName)
- three.js r160 FBXLoader.js
- Autodesk 3ds Max 2018 Help: Structure Rollout (Triangle Neck)
- Autodesk MotionBuilder SDK: 3ds Max Biped Template.py