BUILD NOTES · 06 NOVEMBER 2026
고양이와 걷는 미술관을
웹으로 만드는 과정
이 문서는 실제 구현 과정을 기록한 기술 노트입니다. 실제 내셔널갤러리를 정밀 스캔한 디지털 트윈이 아니라, Room 32의 공간 언어를 참고해 35점의 작품을 다시 배치한 개인 미술관입니다.
1. 경험을 먼저 정하기
핵심 경험을 ‘고양이를 움직여 작품 앞에 멈추는 일’로 정했습니다. 첫 화면은 조용한 전시실과 짧은 초대 문구로 구성하고, 입장한 뒤에는 이동·작품 감상·산책 안내만 남겼습니다. 제작 도중 추상 임시 이미지에서 실제 Room 32 작품 35점으로 범위를 확장했습니다.
재현용 작업 설명
아래 문장은 결과를 다시 만들 때 쓸 수 있도록 정리한 예시이며, 대화의 원문을 그대로 옮긴 것은 아닙니다.
런던 내셔널갤러리 Room 32의 긴 비례, 붉은 벽, 금빛 장식에서 영감을 얻은 개인 웹 미술관을 만든다. 고양이를 직접 움직여 작품을 볼 수 있게 한다. 데스크톱과 모바일을 지원하고, 공식 목록의 작품을 이미지별 이용 조건에 맞게 전시한다. 실제 배치의 정밀 복원이나 공식 서비스를 가장하지 않는다.
2. 자료와 권리를 분리해서 조사하기
공간은 공식 자료의 길이 34m, 너비 12m, 붉은 벽, 금박과 녹색 상부 장식, 20개 루네트를 참고했습니다. 공식 Room 32 페이지에서 35점 목록을 확인하고, 개별 작품 페이지의 제목·작가·제작연도·핵심 설명을 기록했습니다.
오래된 그림이라고 해서 웹에 있는 모든 사진을 자유롭게 사용할 수 있는 것은 아닙니다. Wikimedia Commons의 개별 파일 페이지, 원 제공자 이미지 메타데이터, Flickr 사진가가 표시한 라이선스를 각각 확인했습니다. 제작연도는 파일명보다 공식 작품 페이지의 현재 정보를 우선했습니다.
- 공공영역으로 표시된 작품 복제물은 해당 표시와 출처를 기록했습니다.
- CC BY 사진은 사진가와 라이선스 링크를 표시했습니다.
- Reni의 Lot 이미지는 원 제공자의 CC BY-NC-ND 4.0을 적용해 전체 비율을 유지했습니다.
- Rosa의 Pascariello는 독립 방문자 사진의 CC BY-NC-SA 2.0을 확인했으며 비상업 제한을 표시했습니다.
출처와 조건은 각 작품의 감상 창과 전체 출처 페이지에서 확인할 수 있습니다.
3. 기술 구성
| 역할 | 실제 사용 기술 |
|---|---|
| 화면·인터페이스 | HTML, CSS, JavaScript ES modules |
| 입체 공간·카메라·광선 선택 | Three.js 0.183.2 |
| WebGL 미지원 대체 화면 | Three.js CSS3DRenderer + CSS 평면 + 자체 SVG 고양이 |
| 이미지 준비 | Python, Pillow, 최대 1,400px 비율 유지 축소·JPEG 압축 |
| 기록 저장 | 브라우저 localStorage에 본 작품 번호만 저장 |
| 호스팅 | 정적 사이트, 기본 비공개 배포 |
회원 가입 없이 감상할 수 있습니다. 선택한 경우에만 가상 위치·별명을 공유하는 함께 관람과 공개 방명록이 서버·데이터베이스를 사용합니다. 함께 관람은 약 5초 간격, 최대 15분 세션이며 방명록은 최대 7일 표시됩니다. Three.js 파일을 사이트에 함께 넣어 외부 JavaScript CDN에 의존하지 않도록 했습니다. 글꼴은 Google Fonts와 시스템 대체 글꼴을 사용합니다.
4. 방을 만들기
Three.js의 좌표 1을 대략 1m로 보고 12×34 크기의 바닥을 놓았습니다. 벽·몰딩·천장 격자·문·벤치는 기본 기하 도형의 조합입니다. 벽의 섬유 질감과 나무 바닥은 Canvas로 만든 절차적 텍스처입니다. 건물 사진을 텍스처로 붙이거나 실제 전시실을 스캔하지 않았습니다.
양쪽 벽에 2단으로 작품을 배치하고, 앞쪽 중앙에는 Caravaggio의 The Supper at Emmaus를 두었습니다. 작품의 가로세로 비율을 유지하면서 각각의 걸 수 있는 영역 안에 들어오도록 크기를 계산했습니다. 실제 작품 크기·실제 벽 위치·세부 장식은 복원하지 않았습니다.
const ratio = imageWidth / imageHeight;
let width = maxWidth;
let height = width / ratio;
if (height > maxHeight) {
height = maxHeight;
width = height * ratio;
}
5. 고양이와 이동
WebGL 화면의 고양이는 구·원뿔·곡선 튜브로 직접 만든 모델입니다. 몸, 머리, 귀, 눈, 다리, 꼬리를 별도 그룹으로 구성했습니다. 걷는 동안 대각선 다리가 반대 위상으로 움직이고 꼬리와 머리가 조금씩 흔들립니다. 외부 고양이 모델이나 AI 생성 이미지는 사용하지 않았습니다.
WASD와 방향키의 입력을 카메라 방향에 맞게 변환합니다. 바닥을 누르면 광선과 바닥의 교차점으로 이동하고, 모바일에서는 포인터 기반 조이스틱을 씁니다. 벽과 벤치 주변에서는 간단한 충돌 판정을 하고 자동 안내 중에는 벤치 옆으로 우회합니다.
worldX = -Math.sin(cameraYaw) * forward
+ Math.cos(cameraYaw) * right;
worldZ = -Math.cos(cameraYaw) * forward
- Math.sin(cameraYaw) * right;
6. 작품을 감상하는 UI
액자를 누르거나 가까이에서 E를 누르면 감상 창이 열립니다. 같은 벽에 위아래로 걸린 작품은 R 또는 ‘다른 작품’ 버튼으로 선택할 수 있습니다. 목록에서도 모든 작품을 열 수 있으며, 이전·다음 버튼과 Esc 닫기를 지원합니다.
감상 창에는 원본 비율의 이미지, 작가, 제작연도, 소장 번호, 공식 설명을 바탕으로 한 한 문장 소개, 출처와 라이선스 링크를 넣었습니다. 작은 화면에서는 이미지와 설명을 위아래로 쌓습니다.
7. 실제로 만난 문제와 수정
- WebGL 미지원: 테스트용 클라우드 브라우저가 WebGL 문맥을 만들 수 없었습니다. 빈 오류 화면 대신 같은 카메라·이동 좌표를 사용하는 CSS 3D 호환 모드를 추가했습니다. 호환 모드의 고양이는 입체 모델 대신 직접 그린 SVG 빌보드입니다.
- 작품 선택 좌표: CSS 렌더러는 원래 Three 장면의 world matrix를 자동 갱신하지 않아 광선 선택이 어긋났습니다. 렌더링 전에 scene.updateMatrixWorld(true)를 호출해 수정했습니다.
- 2단 전시의 선택 충돌: 위아래 작품의 바닥 좌표가 같아 아래 작품만 선택되던 문제를 수정했습니다. 가까운 작품을 전환하는 동작과 자동 안내의 현재 작품 우선 선택을 추가했습니다.
- 초기 로딩 중 입장: 이미지 준비 전에 버튼을 눌러도 반응이 없는 상태를 막기 위해 준비가 끝날 때까지 버튼을 비활성화했습니다.
- 키보드 버튼: 전역 이동 처리에서 Space를 가로채던 문제를 수정해 버튼의 기본 키보드 활성화를 유지했습니다.
- 이미지 서버의 속도 제한: 일부 Wikimedia 다운로드가 HTTP 429를 반환했습니다. 서버가 안내한 재시도 간격을 지키며 나머지 구현을 계속했습니다. 재시도 후 35점 전부 다운로드했고, 전체 연락판으로 파일 내용과 원본 비율을 확인했습니다.
8. 확인한 것과 남은 한계
JavaScript 구문 검사, 35개의 고유 소장번호·메타데이터·이미지 파일 검사, 이미지 전체 연락판 검수를 통과했습니다. 실제 브라우저에서는 CSS 3D 화면, 입장, WASD 이동, 산책 안내, 액자 선택, 위아래 작품 전환, 작품 목록·감상 창, 이전·다음, Esc 닫기, 입구 복귀를 확인했습니다. 390×844px iframe에서는 모바일 레이아웃과 포인터 조이스틱 이동, 감상 창을 확인했습니다.
검증 한계: 테스트용 브라우저의 GPU/WebGL 비활성화 때문에 실제 WebGL 화면을 이 환경에서 시각 검증하지 못했습니다. WebGL 지원 기기의 입체 고양이, GPU 조명·그림자, 실제 휴대폰의 터치 동작과 성능은 별도 기기 확인이 필요합니다.
구현하지 않은 것: 실제 작품 크기와 벽 위치의 정밀 복원, 실측 카메라 이동, 미술관 공식 가상 투어, VR, 다중 사용자, 음성 도슨트, 개인 작품 업로드, 편집 관리자 화면은 포함하지 않았습니다.
이 기록은 실제 결과를 과장하지 않기 위한 제작 노트입니다. 시각적 재구성과 공식 소장품 정보, 이미지 이용 조건을 구분해서 읽어 주세요.