02. 휴머노이드 불러오기 — Unitree G1을 무대에 세우기

이번 단계 목표: 진짜 휴머노이드 로봇(Unitree G1)을 시뮬레이터에 불러와서 ① 관절이 몇 개이고 어떻게 움직이는지 표로 확인하고, ② 아무것도 안 하면 어떻게 되는지(=쓰러짐) 직접 봅니다.


로봇 모델은 어디서 구하나?

00번에서 깐 Genesis에는 강아지 로봇(Go2), 로봇팔(Panda) 같은 모델은 들어 있지만 Unitree 휴머노이드(G1/H1)는 기본으로 들어 있지 않습니다. 그래서 Unitree가 공개한 공식 저장소에서 로봇 설계도(URDF)와 3D 부품 파일(mesh)을 받아와야 합니다.

# Unitree 공식 저장소에서 G1, H1 부분만 받기 (전체는 무거우니 일부만)
git clone --no-checkout --depth 1 --filter=blob:none \
    https://github.com/unitreerobotics/unitree_ros.git
cd unitree_ros
git sparse-checkout init --cone
git sparse-checkout set robots/g1_description robots/h1_description
git checkout

받은 g1_description, h1_description 폴더를 이 프로젝트의 assets/unitree/ 아래에 두었습니다. 폴더 안에는 .urdf(설계도)와 meshes/(3D 모양 파일, STL 포맷)가 들어 있습니다.

G1 버전이 왜 이렇게 많죠? g1_23dof, g1_29dof, ..._with_hand 등 파일이 많은데, 숫자는 관절 개수(자유도, DOF) 를 뜻합니다. 손가락까지 있는 버전, 허리를 고정한 버전 등 용도별 변형입니다. 우리는 가장 표준적인 g1_29dof(전신 29관절) 를 씁니다.


코드: 불러오고 → 관절 출력하고 → 중력에 맡기기

핵심 코드는 scripts/01_load_humanoid.py이고, 로봇을 불러오는 부분은 딱 이렇습니다.

robot = scene.add_entity(
    gs.morphs.URDF(file="assets/unitree/g1_description/g1_29dof.urdf",
                   pos=(0, 0, 0.8))   # 바닥에서 0.8m 띄워서 세움
)

00번에서 본 Box, Plane 대신 URDF 모프(morph)를 쓴 것뿐입니다. 로봇이든 상자든 “씬에 엔티티를 추가한다”는 점은 똑같아요.

실행:

python scripts/01_load_humanoid.py            # G1
python scripts/01_load_humanoid.py --robot h1 # H1
python scripts/01_load_humanoid.py --vis      # 실시간 창으로 쓰러지는 모습 보기

결과 ① — 이렇게 생겼습니다

제어 없이 두면 (쓰러짐) 자세를 잡아주면
제어 없이 쓰러진 G1 G1 서 있는 모습

가슴의 UNITREE 로고까지 또렷하게 렌더링됩니다. 화면(창) 없이 카메라로만 뽑은 이미지예요.

결과 ② — 관절(DOF) 지도

스크립트가 출력한 관절 표의 일부입니다. G1은 전체 35 DOF = 떠다니는 베이스 6 + 구동 관절 29.

  전체 DOF: 35  (= 떠다니는 베이스 6 + 구동 관절 29)
  링크(부품) 수: 30
   #  관절 이름                        DOF           가동범위(rad)
   0  left_hip_pitch_joint           6  [  -2.53,    2.88]
   9  left_knee_joint               15  [  -0.09,    2.88]
  11  left_shoulder_pitch_joint     17  [  -3.09,    2.67]
  21  left_elbow_joint              27  [  -1.05,    2.09]
  ...

읽는 법: – “떠다니는 베이스 6 DOF” 란? 로봇 몸통이 공간에서 자유롭게 움직일 수 있는 6가지 — 앞뒤·좌우·위아래 이동(3) + 굴리기·끄덕임·돌기 회전(3). 이건 모터가 아니라 “로봇 전체가 공중/땅에서 어디에 어떤 자세로 있는지”를 나타냅니다. – 나머지 29개가 실제로 모터가 달려 우리가 명령을 내릴 수 있는 구동 관절입니다. hip(고관절), knee(무릎), shoulder(어깨), elbow(팔꿈치), wrist(손목), waist(허리)… 사람 관절 이름과 똑같죠. – 가동범위(rad) 는 그 관절이 굽을 수 있는 최소~최대 각도(라디안). 예: 무릎은 [-0.09, 2.88] → 거의 못 펴고(0 근처) 많이 굽힐 수 있음(2.88rad ≈ 165°). 사람 무릎과 비슷합니다.

라디안(rad)이 뭐예요? 각도를 재는 단위입니다. 180° = 약 3.14rad(π), 90° = 약 1.57rad. 로봇·물리에서는 도(°) 대신 라디안을 표준으로 씁니다.

결과 ③ — 가만 두면 무너진다

아무 제어도 하지 않고 2초만 흘려보냈더니:

몸통 높이(z):  시작 0.800 m  →  2초 후 0.064 m
→ 아무 제어가 없으니 휴머노이드가 무너졌습니다. 그래서 '제어'가 필요합니다!

당연합니다. 사람도 근육으로 끊임없이 균형을 잡지 않으면 서 있을 수 없죠. 로봇도 모터가 매 순간 관절 각도를 붙들어 줘야 서 있습니다. 그 “붙드는 일”이 바로 다음 단계, 제어입니다.


회고 — 막혔던 점 & 배운 것

  • 휴머노이드는 기본 제공이 아니다. Go2(강아지)나 Panda(팔)는 Genesis에 내장돼 있지만 G1/H1은 따로 받아야 했습니다. sparse-checkout으로 필요한 로봇 폴더만 받아 용량을 아꼈습니다.
  • “DOF 35개”에 잠깐 당황. 분명 29관절 로봇인데 35로 떠서 헷갈렸는데, 떠다니는 베이스(free joint) 6개가 더해진 값이었습니다. 제어할 땐 이 6개를 빼고 모터 29개만 다룹니다. (코드에서 joint.type == REVOLUTE인 것만 골라냄 → scripts/common.pyget_motor_dofs)
  • 사소한 경고: 발목 링크 질량이 “dubious(수상)”하다는 경고가 떴지만, URDF에 적힌 값이 형상 추정치와 달라 알려주는 것일 뿐 동작에는 문제 없었습니다.
  • base_height 잡기. 처음 띄우는 높이를 너무 낮게 주면 발이 바닥에 파묻혀 튕깁니다. G1은 0.8m 정도가 적당했습니다(H1은 더 커서 1.05m).

다음 단계

이제 로봇이 무너지지 않도록 붙들어 세우는 가장 기본 제어(PD 제어) 를 배웁니다 → 03. PD 제어로 자세 유지