2026.03.25삽질 일지
게임개발디버깅BabylonJS타일맵좌표계3D모델피벗포인트

계단 피벗 포인트 지옥 — 3D 모델 원점이 엉뚱한 곳에 있으면 벌어지는 일들

들어가며

게임 개발에서 3D 모델을 타일맵 위에 배치하는 건 단순해 보입니다. 좌표 찍고, 모델 로드하고, 끝. 그런데 모델의 **피벗 포인트(원점)**가 예상과 다른 곳에 있으면? 그때부터 지옥이 시작됩니다.

myDGC 프로젝트에서 던전 모델 팩을 타일 에디터에 통합하는 작업 중, 계단 모델 하나가 모든 것을 뒤흔들어 놓았습니다. 0레벨 타일이 전부 1레벨로 올라가고, 배치 위치는 계속 어긋나고, 디버깅할수록 미궁 속으로 빠져드는 경험을 공유합니다.

배경: 포레스트 팩 위에 던전 팩을 얹다

프로젝트의 타일맵 시스템은 원래 포레스트(숲) 모델 팩 기준으로 만들어져 있었습니다. 1타일 = 3유닛, Top/Cliff/Deco로 구성된 깔끔한 구조였죠. 여기에 던전 모델 팩(바닥 타일, 계단, 가구, 소품 등)을 추가하면서 문제가 시작됐습니다.

먼저 크기부터 안 맞았습니다.

code
포레스트 Top:      3 x 3 유닛 (1타일)
던전 floor_large:  4 x 4 유닛
던전 stairs:       5 x 5 x 5 유닛 (높이도 5!)
던전 props:        1 ~ 2 유닛

그리드 기준 자체가 달랐던 겁니다. 던전 모델에 0.75배 스케일(3/4)을 적용해서 일단 크기 문제는 해결했습니다. 하지만 진짜 악몽은 그 다음이었습니다.

문제 발생: 계단이 하늘로 날아가다

3D 모델 좌표계 문제를 디버깅하는 모습

계단 모델을 타일 위에 배치하면, 있어야 할 자리에 없었습니다. 0레벨(지면)에 놓으면 1레벨 높이에 떠 있고, Y좌표를 수동으로 내리면 이번엔 절반이 바닥에 묻혔습니다.

처음에는 스케일 문제인 줄 알았습니다. 0.75배를 적용하면서 Y축도 같이 줄어드니까 높이가 안 맞는 거라고 생각했죠. 그래서 Y 오프셋을 이리저리 조정해봤는데, 한 곳을 맞추면 다른 곳이 틀어졌습니다.

"모델링이 애초에 중심이 거기로 만들어진것 같다고!!!!!!!!"

맞습니다. 모델의 피벗 포인트(원점)가 계단 상단에 있었습니다. 일반적인 3D 모델은 원점이 바닥 중앙에 있어서, position.y = 0이면 바닥에 붙습니다. 그런데 이 계단은 원점이 꼭대기에 있어서, position.y = 0으로 놓으면 계단 전체가 위로 솟아오르는 거였습니다.

파급 효과: 0레벨이 전부 1레벨로

피벗 포인트 문제의 진짜 무서운 점은 연쇄 반응입니다. 계단의 원점이 상단이니까, 에디터에서 "0레벨에 배치"하면 실제로는 계단 상단이 0레벨에 걸립니다. 계단 하단은 -1레벨로 내려가고요.

그런데 게임에서는 음수 레벨이 없으니까, 시스템이 자동으로 모든 타일을 1레벨씩 올려서 보정합니다. 결과적으로 0레벨 타일이 전부 1레벨로 올라가는 버그가 터진 거죠.

javascript
// 문제의 핵심 — 피벗이 상단에 있는 모델
// 에디터에서 y=0에 배치하면...
stairsMesh.position.y = 0;  // 원점(상단)이 y=0
// → 계단 바닥은 y = -5 * 0.75 = -3.75 에 위치
// → 맵 전체 오프셋 보정 → 모든 타일 +1레벨

디버깅하면서 가장 혼란스러웠던 건, 계단만 이상한 게 아니라 맵 전체가 이상해진다는 점이었습니다. "분명 바닥 타일은 안 건드렸는데 왜 올라가 있지?" 하면서 엉뚱한 곳을 파고들기 십상이었습니다.

시행착오의 연속

시도 1: 스케일로 해결?

던전 모델 전체에 0.75 스케일을 적용하면서 "이러면 되겠지" 했지만, 스케일은 크기만 바꿀 뿐 원점 위치는 바꾸지 않습니다.

javascript
// placePiece에서 던전 모델 스케일 적용
const basePath = getModelBasePath(modelName);
if (basePath.includes('dungeon')) {
    instance.scaling.setAll(0.75);
}
// → 크기는 맞지만 위치는 여전히 어긋남

시도 2: Y 오프셋 하드코딩?

계단 모델에만 특별한 Y 오프셋을 적용해볼까 했습니다. 하지만 계단 종류가 여러 개(stairs_wide, stairs_narrow, stairs_spiral...)이고, 각각 피벗 위치가 달라서 하나하나 수동으로 맞추는 건 유지보수 지옥이었습니다.

"그러니까 모델만 왼쪽으로 당겨서 셀렉트박스에 들어가게 해달라고!!!!!"

시도 3: 바운딩 박스 기반 자동 보정

결국 모델의 바운딩 박스를 읽어서, 피벗이 어디에 있든 바닥면이 배치 좌표에 맞도록 자동 보정하는 방식을 적용했습니다.

javascript
// 바운딩 박스에서 실제 바닥 위치를 계산
const boundingInfo = mesh.getBoundingInfo();
const minY = boundingInfo.boundingBox.minimumWorld.y;
const pivotOffset = mesh.position.y - minY;

// 피벗이 어디에 있든, 바닥이 타일 표면에 닿도록 보정
mesh.position.y = targetTileY + pivotOffset;

게임 개발 중 디버깅하는 개발자의 모습

교훈: 피벗 포인트를 절대 무시하지 마세요

1. 외부 에셋의 원점을 먼저 확인하라

모델 팩을 프로젝트에 통합하기 전에, 각 모델의 피벗 포인트가 어디에 있는지 반드시 확인해야 합니다. Blender에서 열어보거나, 런타임에서 바운딩 박스를 찍어보거나. "당연히 바닥 중앙이겠지"라는 가정이 가장 위험합니다.

2. 스케일과 위치는 별개의 문제다

모델 크기가 안 맞는 것과 모델 원점이 안 맞는 것은 완전히 다른 문제입니다. 스케일을 아무리 조정해도 피벗 오프셋은 해결되지 않습니다.

3. 연쇄 반응을 경계하라

한 모델의 피벗 문제가 맵 전체의 레벨 시스템을 무너뜨릴 수 있습니다. "이 모델만 이상한 거겠지"라고 생각하면 큰 코 다칩니다. 특히 자동 보정 로직이 있는 시스템에서는 한 곳의 오류가 전체로 전파됩니다.

4. 사용자(아티스트)의 말을 믿어라

"좌우반전 된 것 같다"고 하면, 카메라 각도 탓으로 돌리기 전에 코드를 먼저 확인합시다. 눈으로 보고 있는 사람이 가장 정확합니다.

5. 서로 다른 에셋 팩의 컨벤션 차이를 문서화하라

code
| 에셋 팩   | 1타일 크기 | 피벗 위치   | 스케일 팩터 |
|-----------|-----------|------------|------------|
| Forest    | 3 유닛    | 바닥 중앙   | 1.0        |
| Dungeon   | 4 유닛    | 상단 또는 임의 | 0.75     |

이런 표 하나만 있었어도 삽질 시간이 절반은 줄었을 겁니다.

마무리

결국 getModelBasePath() 함수로 모델 경로를 자동 판별하고, 바운딩 박스 기반으로 피벗 오프셋을 보정하고, 원본 스케일 부호를 보존하는 것으로 문제를 해결했습니다. 에디터와 게임 양쪽 모두에서요.

3D 모델의 피벗 포인트는 눈에 보이지 않습니다. 그래서 더 무섭습니다. 바운딩 박스도 정상, 텍스처도 정상, 애니메이션도 정상 — 근데 위치만 이상한 그 기분. 겪어본 사람만 압니다.

다음에 외부 3D 에셋을 프로젝트에 통합할 일이 있다면, 제발 피벗 포인트부터 확인하세요. 미래의 당신이 감사할 겁니다.