라이브 체험 설명
Bluestone Lane은 브라우저에서 실행되는 3~4분 길이의 실시간 작품입니다. 철거를 기다리는 옛 거리에 해 질 녘이 내려앉았습니다. 길 건너편 가게들은 하나같이 셔터를 내렸고, 벽에는 페인트로 그린 동그라미 속에 拆(“철거”) 자가 적혀 있으며, 건물 한 채는 이미 반쯤 헐렸습니다. 셔터가 아직 반쯤 열려 있는 곳은 작은 가전 수리점 한 곳뿐입니다. 그 안에는 나이 든 수리공이 라디오 케이스와 카메라 렌즈, 배관 파이프로 조립한 로봇이 작업대 옆 의자에 여전히 앉아 있습니다.
클릭해서 시작하면 카메라가 거리에서 가게 안으로 걸어 들어가 로봇의 얼굴 앞으로 돌아갑니다. 마우스 왼쪽 버튼이나 스페이스 바를 누르고 있으면 로봇 렌즈의 조리개가 열립니다. 첫 시도는 언제나 실패하므로, 손을 뗐다가 다시 누르면 됩니다. 계속 누른 채 마우스를 움직이면 로봇의 머리가 따라 움직이고, 그 빛이 닿는 곳마다 과거가 되살아납니다.
1막의 무대는 가게입니다. 빛줄기 바깥은 현재입니다. 모든 것이 먼지를 뒤집어쓴 채 빛이 바랬고, 선반에는 거미줄이, 바닥에는 떨어진 회벽 조각과 깨진 유리가 널려 있으며, 종이 상자 위의 TV와 벽시계, 탁상 선풍기, 진공관 라디오는 모두 고장 나 있습니다. 빛이 닿는 곳에서는 모든 것이 예전 그대로입니다. TV에서는 방송이 나오고, 라디오는 다이얼에 불이 들어오며 곡을 들려주고, 벽시계의 추가 흔들립니다.
잠시 후 길 건너편에서 같은 곡이 희미하게 흘러옵니다. 로봇이 고개를 돌려 귀를 기울이는 사이 시점은 문간까지 물러납니다. 로봇의 빛이 흔들리고, 노래에 잡음이 스며들고, 화면은 자꾸 동기가 어긋나다가, 마지막으로 한 번 찢어지면서 2막으로 넘어갑니다.
2막의 무대는 거리입니다. 길 건너편 다섯 가게 가운데 빛줄기가 닿은 가게는 1990년대 중반의 어느 저녁으로 돌아갑니다. 가게 안에는 불이 켜지고, 발코니에는 빨래가 널려 있고, 이웃들은 문 앞 낮은 의자에 앉아 있으며, 그 가게의 소리가 들려옵니다. 찾아낸 가게는 계속 불이 켜져 있고, 아직 찾지 못한 가게는 간판과 소리로 이쪽을 부릅니다. 바라보는 동안에도 전력은 계속 줄어듭니다. 로봇의 마지막 시선이 그 곡이 흘러나오는 전자제품 가게로 돌아가면 거리 전체가 잠시 환하게 밝아집니다. 이윽고 전력이 다하면 빛줄기는 몇 번 깜박이다 꺼지고, 노래는 소절 중간에서 끊기며, 거리는 지금의 모습으로 돌아옵니다. 엔딩에서는 가게 안에서 밖을 내다봅니다. 로봇은 여전히 어두운 거리를 향해 있고, 길 건너편에 등불 하나가 켜지며, 마지막 한 줄은 이렇습니다. “로봇은 불 켜진 거리를 기억합니다.”
엔딩 카드가 나온 뒤 클릭하면 다시 볼 수 있습니다. 인터페이스는 기본적으로 영어이며, 타이틀 화면의 링크로 중국어로 바꿀 수 있습니다. 휴대폰에서는 화면을 길게 누른 채 손가락을 밀어 조작합니다. 헤드폰을 쓰고, 최신 Chrome이나 Edge처럼 WebGPU를 지원하는 브라우저로 보는 것을 권장합니다. WebGPU를 지원하지 않는 브라우저에서는 자동으로 WebGL2로 전환됩니다. 처음 불러올 때는 모델을 내려받고 셰이더를 미리 컴파일하느라 시간이 조금 걸립니다.
Tripo 활용 방식
Tripo로 생성한 수리점 안의 오브젝트는 20개가 넘습니다. 로봇, 진공관 라디오 두 대(하나는 수리하려고 뜯어 놓은 것, 하나는 닫혀 있는 것), CRT TV 두 대, 탁상 선풍기, 벽시계, 자명종, 카세트 녹음기, 탁상 스탠드, 납땜인두, 돋보기, 공구함, 부품 수납장, 작업대, 28인치 로드스터 자전거, 보온병, 법랑 머그, 그리고 예비 팔·다리·몸통이며, 여기에 따로 만든 망가진 버전 네 개가 더해집니다. 방의 벽과 바닥, 선반과 그 위의 종이 상자, 케이블, 공구 벽, 작업대 위의 자잘한 물건은 코드로 만든 지오메트리이고, 길 건너편에 늘어선 가게들은 모델이 아니라 생성한 사진 두 장(“지금”과 “그때”)입니다.
- 레퍼런스에서 모델까지: 모든 오브젝트는 무늬 없는 연회색 배경의 제품 렌더링 이미지에서 출발해 Tripo로 넘어갑니다. 모델링에 들어가기 전, 뜯어 놓은 라디오로 세 가지 방식을 비교했습니다. 이미지 투 3D를 바로 쓰는 방식, 이미지에서 멀티뷰를 거쳐 3D로 가는 방식, 직접 조합한 4방향 뷰 시트로 3D를 만드는 방식입니다. 대부분의 모델은 이미지 → 멀티뷰 → 3D 경로를 택했고, 정밀한 지오메트리와 텍스처, PBR 맵을 갖추었으며, 텍스처에 구워진 조명은 걷어 내 탁상 스탠드와 문밖의 땅거미, 로봇의 빛줄기가 조명을 맡도록 했습니다.
- 로봇: Tripo가 만든 로봇은 머리와 몸통, 의자가 하나로 붙은 단일 메시였습니다. 제작자는 머리를 잘라 내는 대신 정점 셰이더로 “부드러운 목”을 만들었습니다. 위치로 계산한 가중치가 라디오 케이스로 된 머리에서는 1, 몸통과 어깨에서는 0이고, 목과 그 주위의 케이블을 지나며 점차 바뀌므로, 머리를 돌리면 각 정점이 제 몫의 각도만큼 목을 축으로 비틀립니다. 아무것도 자르지 않았으니 이음매가 벌어질 일이 없고, 머리가 돌면 케이블도 목을 감으며 따라갑니다. 의자는 Blender 스크립트로 분리해 두었기 때문에, 2막에서는 로봇 전체가 의자 위에서 돌아 거리를 마주합니다. 얼굴의 크롬 “上海”(상하이) 배지와 가슴의 “人民电器”(인민전기) 명판은 모델 자체에 들어 있는 지오메트리이며, 벽 고리에 걸려 있거나 작업대 옆에 기대 세워 둔 예비 팔·다리·몸통도 같은 디자인으로 생성했습니다.
- 망가진 버전: 빛줄기 바깥의 현재와 빛줄기 안의 과거가 서로 맞아떨어지도록, 종이 상자 위의 TV와 벽시계, 탁상 선풍기, 진공관 라디오에는 각각 낡고 망가진 버전을 하나씩 더 만들었습니다. 레퍼런스 이미지를 손상된 모습으로 고친 뒤 같은 Tripo 설정으로 모델링한 것입니다. 두 버전은 같은 자리에 놓여 있으며, 빛줄기가 닿는 픽셀에는 멀쩡한 쪽이, 나머지 픽셀에는 망가진 쪽이 보입니다.
- 원본 메시를 웹으로: Tripo 원본 모델은 개당 약 190만 개의 삼각형과 4K 텍스처(로봇은 8K)로 이루어져 있습니다. Blender에서 디시메이션하면 텍스처 이음매가 찢어져 가까이서 흰 균열이 보이기 때문에, 제작자는 그 대신 이음매를 유지한 채 meshopt로 단순화해 여러 단계의 디테일 수준을 만들고, 단계마다 실측 오차를 기록해 두었습니다. 런타임에는 오브젝트마다 오차가 화면 1픽셀 미만이고 텍스처가 화면 해상도보다 거칠지 않은 단계만 표시합니다. 멀리서는 단순한 단계를, 가까이 다가가면 더 정밀한 단계를 씁니다. 공개 버전에서 가장 정밀한 단계는 원본 메시와의 차이가 1밀리미터에도 못 미칩니다. 거친 단계부터 먼저 불러오고 나머지는 필요할 때 스트리밍하며, 비디오 메모리가 예산을 넘으면 다시 해제합니다.
- 모델 뷰어: 주소에 ?review를 붙이면 밝은 검수용 조명이 켜진 검수 모드로 들어갑니다. 드래그하면 궤도를 따라 회전하고, 스크롤하면 확대·축소되며, 1–8을 누르면 미리 설정된 시점으로 바뀝니다. 모델을 더블클릭하면 카메라가 그 모델 앞으로 미끄러지듯 다가가고, 왼쪽 아래에 모델 이름과 Tripo 원본 삼각형 수가 표시됩니다.
브라우저에서 실행되는 방식
게임 엔진은 쓰지 않았습니다. Vite로 three.js의 WebGPU 렌더러 위에 직접 만들었고, WebGPU를 지원하지 않는 브라우저에서는 자동으로 WebGL2로 전환됩니다.
과거는 픽셀 단위로 계산됩니다. 로봇의 눈은 원뿔 모양의 빛을 비추고, 렌더링 과정에서 모든 픽셀이 이 원뿔 안에 드는지 판정합니다. 원뿔 안에서는 멀쩡한 모델과 켜진 TV, 한층 따뜻한 색감이 나타나고, 바깥에는 먼지 쌓이고 빛바랜 현재가 남습니다. 망가진 버전과 멀쩡한 버전도 같은 마스크에 따라 바뀝니다. 길 건너편의 사진 두 장도 같은 방식으로 지금과 그때를 오가며, 전자제품 가게의 쇼윈도는 실제로 작은 3D 가게로 만들어져 그 안의 TV에서도 방송이 나옵니다.
빠르게 열리고 매끄럽게 재생되는 비결은 세 가지입니다. 모든 셰이더는 비동기로 병렬 컴파일되며, 셰이더가 아직 준비되지 않은 오브젝트는 프레임을 붙잡아 두지 않고 그 프레임에서만 건너뜁니다. 로딩 화면에서는 모든 오브젝트를 모든 패스(깊이 프리패스, 메인 패스, 각 섀도 맵)에서 한 번씩 그려 두므로, 클릭해서 시작한 뒤에는 이야기 도중에 컴파일이 일어나지 않습니다. 또 렌더링은 약 210만 픽셀(대략 1080p)로 상한을 두고, 프레임 시간이 길어지면 해상도를 자동으로 낮춥니다. 모델의 단계가 바뀔 때는 두 단계를 서로 상보적인 무작위 픽셀 집합에 동시에 그려 0.35초에 걸쳐 크로스페이드하고, 템포럴 안티에일리어싱으로 이를 매끄럽게 다듬기 때문에 가까이서 봐도 갑자기 “튀는” 일이 없습니다. 주소에 ?q=low를 붙이면 저화질 모드(앰비언트 오클루전과 화면 공간 반사 없음, 섀도 맵 크기 절반)로 실행됩니다.
사운드는 CC0 필드 레코딩에 Web Audio 합성음을 겹쳐 만들었습니다. 해 질 녘 문밖의 거리 소리, 형광등과 전원의 웅웅거리는 소리, 시계추의 똑딱 소리, TV 테스트 톤, 로봇의 서보 소리와 눈이 열리는 소리가 있으며, 라디오에서 흐르는 곡은 5음 음계로 만든 오리지널 멜로디입니다. 2막에서는 길 건너편 가게마다 저마다의 소리가 납니다.
작업 과정에서 제작자는 three.js r186 WebGPU 렌더러의 버그도 하나 고쳤습니다. 두 모델이 노멀 저장 방식(Int8 대 Int16)만 다를 경우 렌더러가 둘에게 파이프라인 하나를 함께 쓰게 하므로, 두 번째로 그려지는 쪽이 정점을 잘못된 형식으로 읽어 표면 조명이 위장 무늬처럼 엉망이 됩니다. WebGL2는 영향을 받지 않습니다.


