아바타가 뒤를 볼 때 확인할 VRM 정면 방향과 좌표축

뷰어에 VRM 파일을 올렸는데 아바타가 등을 보이고 서 있다면, 모델이 망가진 것이 아니라 “정면은 어느 축인가”라는 약속이 어긋난 경우가 대부분입니다. VRM 정면 방향은 버전마다 달라서, 0.x는 -Z를, 1.0은 +Z를 바라보도록 규정되어 있습니다. 한쪽 규약을 전제로 만든 프로그램에 다른 버전의 파일을 넣으면 정확히 180도 돌아선 모습이 나옵니다. 반대로 아바타가 바닥에 누워 있거나 엎어져 있다면 원인은 정면이 아니라 위쪽 축, 즉 Y-up과 Z-up의 차이입니다. 두 증상은 고치는 자리가 다르므로 먼저 어느 쪽인지 가려야 합니다.

VRM 아바타가 뒤를 볼 때 자세, 버전, 왼팔 좌표 부호, 표시 회전, 재출력 순서로 점검하는 다섯 단계
정면 문제인지 위쪽 축 문제인지부터 가리고, 파일 버전과 실제 방향을 대조합니다.

등을 보이나, 누워 있나: 180도와 90도

  • 똑바로 서 있는데 등이 보인다(180도): 정면 축 문제입니다. VRM 0.x와 1.0의 규약 차이이거나, 원본 데이터가 처음부터 반대쪽을 보도록 만들어진 경우입니다.
  • 누워 있거나 머리가 앞쪽을 향한다(90도): 위쪽 축 문제입니다. Z를 위로 쓰는 도구에서 나온 데이터가 Y를 위로 쓰는 환경으로 변환 없이 들어온 경우입니다.
  • 방향은 맞는데 글자나 비대칭 장식이 거울처럼 뒤집혀 있다: 좌표계의 손 방향(오른손·왼손)을 바꾸는 과정에서 축 하나를 뒤집는 단계가 빠졌거나 두 번 들어간 경우입니다.

여기서는 앞의 두 경우를 중심으로 다룹니다. 위 그림의 다섯 단계를 순서대로 따라가면 어느 쪽 문제인지, 어디를 고쳐야 하는지가 가려집니다.

좌표계는 세 가지 약속으로 이루어집니다

3D 좌표계를 맞춘다는 것은 어느 축이 위인지, 어느 축이 앞인지, 오른손 좌표계인지 왼손 좌표계인지 세 가지를 합의한다는 뜻입니다. 앞의 두 가지가 정해지면 나머지 한 축의 방향은 손 방향이 결정합니다. 문제는 도구마다 이 약속이 다르다는 점입니다.

three.js는 Y가 위입니다. 공식 문서에 모든 객체의 기본 위쪽 방향인 Object3D.DEFAULT_UP이 (0,1,0)으로 적혀 있고, 카메라는 기본적으로 자기 로컬 -Z 축을 바라본다고 되어 있습니다. 반면 Blender 매뉴얼(5.2 LTS, 2026년 9월 확인 기준)에서 FBX 내보내기를 맡는 FBX(Legacy) 애드온 항목은 Blender가 “Y Forward, Z Up”을 쓰며, 그 이유로 정면(Front) 뷰가 +Y 방향을 바라보기 때문이라고 설명합니다. 같은 항목은 Y를 위로 쓰는 프로그램으로 보낼 때 -Z Forward, Y Up 조합이 필요하다고 안내합니다. Autodesk FBX SDK 문서의 미리 정의된 축 체계에서도 3ds Max용 설정은 위쪽이 +Z인 오른손 좌표계, Maya의 Y-up 설정은 위쪽이 +Y인 오른손 좌표계로 구분되어 있습니다.

VRM 0.0 스펙도 이 문제를 직접 언급합니다. 모델의 계층과 메시가 모두 Y-UP이어야 한다고 규정하면서, Blender나 3ds Max 같은 Z-UP 모델러에서 나온 모델은 계층 중간에 x축 90도 회전이 들어 있을 수 있다는 각주를 달아 두었습니다. 아바타가 누워 보인다면 이 90도가 중간 어디선가 처리되지 않았을 가능성이 큽니다.

이 사이트에서도 비슷한 흔적을 확인했습니다. 3ds Max에서 내보낸 수어 모션캡처 FBX 6개 중 3개가 three.js에서 찢어져 보였는데, 머리카락과 눈썹이 높이 대신 앞뒤(z)로 약 150cm 떨어진 자리에 있었습니다. 원인을 추적해 보니 스키닝에 -90° X 회전과 약 156cm 이동이 적용되고 있었습니다. X축 기준 -90° 회전은 +Z를 +Y로 옮기는 회전, 즉 Z-up 데이터를 Y-up으로 세우는 회전과 같은 꼴입니다. 바인드 이후에 축·단위 변환이 들어간 파일이었고, 자세한 과정은 bind 행렬이 어긋난 모캡 FBX 글에 적어 두었습니다.

three.js r160의 FBXLoader 소스를 보면, 파일의 GlobalSettings에서 읽는 값은 주변광 색과 UnitScaleFactor뿐이고 단위 배율도 userData에 담아 둘 뿐입니다. 파일 안의 축 설정을 보고 모델을 자동으로 세워 주지 않으므로, 불러온 뒤 실제 좌표를 보고 판단해야 합니다.

glTF는 정면을 +Z로 못 박았습니다

VRM은 glTF 2.0 위에 얹힌 형식이라 기준은 glTF 스펙입니다. 스펙 3.4절은 glTF가 오른손 좌표계를 쓰고, +Y를 위, +Z를 앞, -X를 오른쪽으로 정의하며, glTF 에셋의 정면은 +Z를 향한다고 적고 있습니다. 같은 절에서 거리 단위는 미터, 각도는 라디안으로 정합니다. FBX는 보통 센티미터라서 이 사이트의 변환기는 원본 키 172.36 단위짜리 모델에 배율 약 0.0093을 곱해 1.6m로 맞춥니다. 단위 문제는 FBX 센티미터와 미터 글에 따로 정리했습니다.

캐릭터가 +Z를 보고 서 있으면 캐릭터의 오른손은 -X 쪽, 왼손은 +X 쪽에 있습니다. 카메라를 +Z 쪽에 두고 원점을 바라보게 하면 얼굴이 보입니다. three.js 소스에서도 일반 객체의 getWorldDirection()은 월드 행렬의 세 번째 열, 즉 객체의 로컬 +Z 축을 “바라보는 방향”으로 돌려줍니다. 카메라만 이 메서드를 덮어써서 -Z를 돌려줍니다. 객체는 +Z를 보고 카메라는 -Z를 본다는 약속이 glTF와 맞물려 있는 셈입니다.

VRM 0.x는 -Z, 1.0은 +Z를 봅니다

여기서 VRM의 두 버전이 갈라집니다. VRM 0.0 스펙의 “VRM rules” 항목은 모델이 원점에 놓이고, -Z 방향을 바라보며, 계층과 메시가 Y-UP이고 T-Pose여야 한다고 적습니다. glTF의 +Z 정면과 정반대입니다.

VRM 1.0은 이것을 glTF 쪽으로 돌려놓았습니다. 1.0 스펙의 T-pose 정의 문서는 모든 축을 전역 좌표 기준으로 둔다고 밝힌 뒤, 다리·몸통·머리·눈이 +Z 축을 향하고 X: 0.0을 기준으로 좌우 대칭이 되게 곧게 서야 한다고 정의합니다. 발끝도 +Z를 향해야 하고, 발바닥은 Y: 0.0에 닿아야 합니다. 엄지 규정을 보면 왼손 엄지는 +X와 +Z 사이, 오른손 엄지는 -X와 +Z 사이를 향합니다. 즉 1.0 모델의 왼쪽은 +X입니다.

왜 이런 차이가 생겼는지는 vrm.dev의 좌표 변환 문서에 나옵니다. Unity는 왼손 좌표계에서 +Z가 앞입니다. UniVRM은 Unity와 VRM 0.x 사이를 오갈 때 Z축을 뒤집고, Unity와 VRM 1.0 사이에서는 X축을 뒤집습니다. Z를 뒤집으면 Unity에서 +Z를 보던 모델이 파일 안에서는 -Z를 보게 되고, X를 뒤집으면 정면은 +Z로 남고 좌우만 오른손 좌표계에 맞게 바뀝니다. vrm.dev의 VRM 특징 문서도 0.x는 오른쪽이 +X가 되도록 Z- 방향이었고 1.0에서 사양이 바뀌어 Z+ 방향이 되었다고 적고 있습니다. 같은 좌표 변환 표의 glTF 행에는 정면 칸 대신 “VRM-0 방식이 기본이고 VRM-1 방식은 옵션”이라는 설명이 붙어 있습니다(2026년 9월 확인 기준). 스펙상 glTF의 정면은 +Z이지만, UniVRM은 일반 glTF도 기본값에서는 Z축을 뒤집는 0.x 방식으로 다룬다고 읽힙니다.

glTF 2.0, VRM 0.x, VRM 1.0, Unity의 위쪽 축, 정면 축, 모델 오른쪽 방향, 좌표계 손 방향을 비교한 표
VRM 0.x만 정면이 -Z입니다. VRM 1.0은 glTF 스펙과 같은 +Z를 봅니다.

이 사이트의 FBX → VRM 변환기는 VRM 1.0(VRMC_vrm 확장)으로 내보내므로 결과물은 +Z를 봐야 합니다. 변환기는 정면 방향을 자동으로 감지하고, 화면에 +Z 방향을 파란 화살표로 표시합니다. 내보내기 전에 얼굴이 이 화살표 쪽을 향하는지 한 번 보면 됩니다.

파일이 어느 쪽을 보는지 확인하는 방법

1. 버전을 확인합니다

VRM 파일은 GLB이므로 안의 JSON을 보면 버전이 드러납니다. extensionsUsed에 VRMC_vrm이 있고 extensions.VRMC_vrm.specVersion이 “1.0”이면 1.0이고, VRM이라는 이름의 확장이 있으면 0.x입니다. three.js용 라이브러리인 three-vrm으로 불러왔다면 vrm.meta.metaVersion이 “0”인지 “1”인지로 구분할 수 있습니다. 0.x라면 -Z를 보는 것이 정상이고, 1.0이라면 +Z를 보는 것이 정상입니다.

2. 좌표 부호를 봅니다

버전과 실제 방향이 일치하는지는 본 위치로 확인합니다. T포즈 상태에서 휴머노이드의 왼쪽 위팔(leftUpperArm) 본의 월드 x 좌표가 양수이면 모델은 +Z를 보고 있고, 음수이면 -Z를 보고 있습니다. 1.0 T-pose 정의에서 왼쪽이 +X이기 때문입니다. 발가락(toes) 본이 있는 모델이라면 발가락 본의 z가 발목에 있는 발(foot) 본보다 클 때 +Z를 보고 있는 것입니다. 이 값은 뷰어가 화면용 회전을 걸기 전의 좌표로 봐야 합니다. 왼팔 부호와 발가락 방향이 서로 엇갈린다면 방향이 아니라 좌우 본 매핑이 바뀌었을 가능성이 큽니다. 1.0 파일인데 두 값이 모두 -Z를 가리킨다면 뷰어가 아니라 파일이 규약을 어긴 것입니다.

변환 전의 FBX가 서 있는지 누워 있는지는 3D 모델 뷰어에 먼저 열어 보면 바로 알 수 있습니다. 누워 있다면 정면을 따지기 전에 위쪽 축부터 맞춰야 합니다.

고치는 자리는 증상마다 다릅니다

0.x 파일이 1.0 전제의 프로그램에서 뒤를 보는 경우에는 파일에 잘못이 없습니다. 표시하는 쪽에서 Y축으로 180도 돌리면 됩니다. three-vrm은 이를 위한 함수 VRMUtils.rotateVRM0(vrm)를 제공하며, 소스를 보면 메타 버전이 “0”일 때 vrm.scene.rotation.y를 Math.PI로 두는 한 줄입니다(2026년 9월 확인 기준). 화면용 보정일 뿐 파일 안의 데이터는 여전히 -Z를 봅니다.

0.x 파일 자체를 1.0 규약으로 바꾸고 싶은 경우에는 UniVRM이 제공하는 간이 마이그레이션(업그레이드) 기능이 있습니다. vrm.dev 문서의 변환 규칙대로라면 0.x를 가져올 때 Z축을 뒤집어 Unity 안에서 +Z를 보게 되고, 1.0으로 내보낼 때는 X축을 뒤집으므로 정면이 +Z로 유지됩니다.

1.0 파일인데 -Z를 보는 경우에는 파일을 다시 만들어야 합니다. T-pose 정의가 전역 좌표 기준이므로 메시, 스켈레톤, 바인드 행렬을 한꺼번에 돌려 +Z를 보게 한 뒤 다시 내보냅니다. 앞의 모캡 사례는 바인드 시점의 행렬과 이후 메시의 월드 행렬이 어긋나 찢어진 경우였습니다.

누워 있는 경우에는 내보내는 쪽 설정을 먼저 봅니다. Blender 매뉴얼의 glTF 2.0 내보내기 항목에는 Transform 아래 “Y Up” 옵션이 있고, glTF 관례대로 +Y를 위로 내보내는 설정이라고 설명되어 있습니다. FBX로 내보낸다면 앞에서 본 -Z Forward, Y Up 조합이 해당합니다.

정면이 맞은 다음 순서는 팔 각도입니다. VRM 1.0은 +Z를 보는 T포즈를 기준 자세로 삼기 때문에, A포즈로 만든 모델은 정면이 맞아도 기준 자세 조건을 따로 맞춰야 합니다. 이 부분은 VRM T포즈와 A포즈 글에서 이어집니다.

자주 묻는 질문

뷰어에서 180도 돌려 놓으면 해결된 것인가요?

보기에는 해결됩니다. 하지만 돌린 것은 화면의 장면이지 파일이 아니므로, 같은 파일을 다른 프로그램에서 열면 다시 뒤를 볼 수 있습니다. 파일 자체가 1.0 규약을 따르게 하려면 +Z를 보도록 다시 내보내야 합니다.

Z-up과 Y-up 중 어느 쪽이 맞는 좌표계인가요?

둘 다 올바른 좌표계입니다. Blender와 3ds Max는 Z를 위로 쓰고, glTF·VRM·three.js는 Y를 위로 씁니다. 문제는 한 파일 안에서 두 약속이 섞이거나 변환이 빠질 때 생깁니다. VRM으로 내보낼 때는 오른손 좌표계, Y-up, +Z 정면(1.0 기준)에 맞추면 됩니다.

0.x와 1.0은 위쪽 축도 다른가요?

아닙니다. 둘 다 오른손 좌표계에 Y-up이고, 달라진 것은 정면이 -Z인지 +Z인지, 그에 따라 모델의 오른쪽이 +X인지 -X인지입니다.

이 사이트 변환기로 만든 VRM은 어느 쪽을 보나요?

VRM 1.0으로 내보내므로 +Z를 봐야 하며, 화면의 파란 화살표가 그 방향입니다. 얼굴이 화살표 반대쪽을 보고 있다면 내보내기 전에 원본의 축 설정을 다시 확인해 보십시오.

참고한 자료

위로 스크롤