Inside

Three.js에서 3D 모델을 최적화하기 위한 여정

Three.js에서 3D 모델을 최적화하기 위한 여정
임상아 뉴스룸 디벨로퍼|동아일보 디프런티어센터 2025-02-12 10:00:02
Three.js는 지금까지 여러 번 기사에 활용했지만, 사용할 때마다 매번 새로운 문제에 직면한다. 여태까지 사용했던 3D 모델 중 가장 규모가 컸던 만큼 최적화하는 데에 긴 시간이 걸렸다.
3D mesh가 제대로 보이지 않는 현상
카메라 각도에 따라 빨간 천막이 제대로 보이지 않는 현상카메라 각도에 따라 빨간 천막이 제대로 보이지 않는 현상
타이틀 화면에 스크롤에 따라 카메라 시점이 이동하는 애니메이션을 넣어야 했다. 3D 모델을 위에서 아래로 내려다보는 카메라 각도에서는 모델이 정상적으로 보였다. 하지만 아래에서 위로 올려다볼 경우 빨간 천막이 카메라 각도에 따라 제대로 보이지 않는 경우가 있었다.

3D 객체의 투명도 때문이었다. 문제가 발생했던 빨간 천막은 다른 3D 객체들과 달리 투명도가 설정돼 있었다. Three.js에서 불투명 객체는 카메라 거리와 관계없이 정해진 순서대로 렌더링 되지만, 투명 객체는 카메라와의 거리를 계산해 먼 객체에서 가까운 객체 순서대로 렌더링된다.

빨간 천막 객체는 3D 모델에서 가장 바깥에 위치해 있다. 가장 마지막에 렌더링 되어야 건물 바깥을 감싸는 형태로 보인다. 하지만 바깥에 위치해있기 때문에 카메라가 움직이면서 카메라와의 거리가 가까워져서 다른 객체들보다 먼저 렌더링될 때가 있었다. 이 때문에 천막 객체가 내부의 불투명 객체에 가려지거나, 카메라 각도에 따라 빨간 천막이 보였다가 사라지는 문제가 발생했다.

이를 해결하기 위해 콘솔에 3D 모델 객체들을 하나씩 찍어봤다. 사전에 모델을 압축했기 때문에 객체의 이름이 직관적이지 않고 ‘mesh_숫자’ 형태로 바뀌었기 때문에 일일이 확인해야 했다. 빨간 천막을 가리키는 객체의 이름이 ‘mesh_0_12’인 것을 찾아내고, js 코드에서 아래와 같은 코드를 추가했다.
렌더링 순서를 조정하는 코드렌더링 순서를 조정하는 코드
renderOrder를 999로 설정해 빨간 천막 객체가 항상 제일 마지막에 렌더링되도록 설정했다.
렌더링 순서 지정 후 카메라 각도와 상관없이 잘 보이는 3D 모델렌더링 순서 지정 후 카메라 각도와 상관없이 잘 보이는 3D 모델
웹 호환성 이슈
QA를 진행하던 중 팀에서 테스트용으로 갖고 있던 아이폰12 미니 기종 모든 브라우저에서 오류를 발견했다. 이용자가 기사 페이지에 랜딩하면 로딩 화면을 보여주고, 3D 모델이 다 불러와지면 로딩 화면이 사라지도록 제작했는데 아이폰 12 미니에서는 3D 모델 로딩이 끝난 이후에도 로딩 화면이 사라지지 않았다. 원인을 찾기 위해 Three.js 관련 스크립트가 정상적으로 로드될 경우 경고창이 뜨도록 해봤다. 경고창은 나오지 않았다. 페이지에서 Three.js 관련 스크립트가 아예 불러와지지 않고 있었다.

Three.js 버전을 낮춰 다시 테스트해봤다. 이번에 사용한 3D 모델에는 디자이너가 직접 작업한 텍스쳐가 들어가 Three.js 최신 버전인 0.162.0 버전을 적용해뒀었다. 이전 기사에서 사용했던 0.126.1 버전으로 낮춰보니 Three.js 스크립트가 로드되긴 했지만 3D 모델의 퀄리티가 현저히 낮아졌다.

어쨌든 Three.js의 버전에서 문제가 발생했다는 걸 확인할 수 있었다. 버전과 웹 호환성 문제에 대해 조금 더 살펴봤고, 그 결과 최신 버전을 사용하며 같이 썼던 ‘importmap’이라는 script의 문제였다는 것을 알아냈다.

importmap은 브라우저에서 직접 모듈을 불러올 때 경로를 지정해 주는 역할을 한다. 이것을 추가하지 않으면 모듈을 import할 때 브라우저가 종종 경로를 제대로 이해하지 못해 ‘Error: Relative references must start with either "/", "./", or ".. ‘ 오류가 발생한다.
웹 기술의 브라우저 지원 여부를 확인할 수 있는 caniuse에 ‘importmap’ 검색시 나오는 결과웹 기술의 브라우저 지원 여부를 확인할 수 있는 caniuse에 ‘importmap’ 검색시 나오는 결과
하지만 caniuse에 importmap을 검색해 보면 많은 구버전 브라우저에서 이를 지원하지 않고 있는 것을 확인할 수 있다. 팀에서 갖고 있던 아이폰12 미니의 운영체제 버전은 iOS15였다. 이 역시 importmap을 지원하지 않았다. 이를 해결하기 위해 es-module-shims 스크립트를 importmap 위에 추가했다.
importmap 위에 추가된 es-module-shims 스크립트importmap 위에 추가된 es-module-shims 스크립트
es-module-shims는 importmap 및 ES6 모듈을 지원해 importmap을 지원하지 않는 환경에서도 동작할 수 있도록 해준다.
3D 모델 압축 방법
3D 모델이 첫 화면에서 바로 보여야 하기 때문에, 3D 모델은 페이지에서 1순위로 빠르게 로드되어야 했다. 3D 모델 용량 압축이 로딩 속도를 줄이는 데 가장 큰 도움이 되었다. 3D 모델은 아파트 2개, 크레인 1개로 구성되어 있었다. 아파트 모델 원본은 개당 약 145MB였고, 크레인은 7MB였다. 모델은 다양한 방법을 사용해 압축했다.

1. gltfpack
제임스웹 등 Three.js를 사용한 기존 기사에서 사용했던 방법이다. -tc -cc -si를 사용해 145MB를 4.5MB까지 압축했다.
2. 블렌더
크레인은 gltfpack으로 압축할 경우 퀄리티 손실이 너무 심했다. 블렌더에서 glb 형식으로 내보낼 때 옵션에 있는 ‘압축’ 기능을 사용해 7MB에서 1.5MB로 압축했다. 압축 옵션을 사용하면 자동으로 Draco 압축이 적용되기 때문에, 3D 모델을 Three.js에서 불러올 때 반드시 DracoLoader를 추가해야 한다.
3. gltf report
3D 모델을 올리면 Draco, Meshopt 두 가지 설정 중에 골라 압축해 저장할 수 있다. 코드로 압축하는 것과 비슷하지만 페이지에서 몇 번의 클릭으로 압축이 가능해 간편하다. 이 방법은 작업 막바지에 발견해서 이번에 사용하진 않았지만, 빠르게 압축 작업이 필요할 때 좋을 것 같다.

이번 작업을 통해 Three.js를 활용한 3D 모델 최적화가 렌더링 순서와 웹 호환성, 로딩 속도 등 다양한 요소를 고려해야 하는 과정임을 다시 한번 실감했다. 예상보다 많은 시행착오를 겪었지만, 문제를 해결할수록 3D 웹 개발이라는 분야에 더욱 흥미를 느끼고, 도전의식이 생겼다. 이번 경험을 바탕으로 앞으로는 더 정교하고 최적화된 방식으로 3D 인터랙티브 콘텐츠를 제작할 수 있을 것 같다.
관련 콘텐츠 더보기
누락: 당신의 아파트는 안녕하신가요 아파트 건설 현장은 '보이지 않는 세계'입니다. 아파트가 올라간 뒤에는 제대로 지어졌는지 누구도 알기 어렵습니다. '안전'을 단지 '운'에 맡겨야 하는 현실. 기자들이 직접 탐사장비를 들고 아파트의 철근 누락을 7개월 간 추적했습니다.
2025.01.23~01.27·히어로콘텐츠 9기·
임상아 뉴스룸 디벨로퍼
임상아 뉴스룸 디벨로퍼|동아일보 디프런티어센터

디오리지널의 기사를 기술적으로 구현하는 일을 하고 있습니다. 이야기와 기술이 만날 때 이야기가 전달할 수 있는 가치는 더욱 극대화될 수 있다고 생각합니다. 많은 사람들에게 새로움과 감동을 줄 수 있는 기사들을 만들어가고 싶습니다.