이 블로그의 입구는 작은 방이다. 책장에 있는 책을 누르면 글을 읽을 수 있고, 모니터를 누르면 컴퓨터 화면으로 들어간다. 한쪽에는 베이스와 심레이싱 장비도 놓여 있다.

방 안의 에셋과 애니메이션은 Blender에서 직접 만들었다. 그렇게 만든 장면을 React Three Fiber로 웹에 옮기고, React로 글을 읽는 화면을 붙였다.

첫 글에는 그 과정을 남겨보려고 한다. 모델을 화면에 띄우는 것에서 출발했지만, 작업이 이어질수록 신경 쓴 부분은 장면 사이를 어떻게 이동하고, 그 안에서 어떻게 편하게 글을 읽게 할 것인가였다.

책장, 컴퓨터 책상, 베이스와 심레이싱 장비가 놓인 웹 블로그의 3D 방
현재 웹에서 볼 수 있는 방. 왼쪽 메뉴로도 각 공간에 바로 이동할 수 있다.

방 안의 물건을 블로그의 입구로

책은 글로, 컴퓨터는 프로젝트로 이어지게 했다. 화면에 익숙한 물건이 보이면, 그 물건을 눌렀을 때 어떤 내용이 나올지도 조금은 예상할 수 있을 것 같았다.

처음에는 화면 위의 메뉴와 방 안의 오브젝트가 함께 눈에 들어왔다. 같은 목적지로 가는 입구가 여러 곳에 흩어져 있으니 무엇을 눌러야 할지 오히려 복잡해졌다. 가운데 메뉴를 걷어내고, 바로 이동하는 기능과 화면 설정은 왼쪽의 작은 메뉴에 모았다.

방을 둘러보고 싶으면 물건을 눌러도 되고, 글을 바로 읽고 싶으면 메뉴를 사용하면 된다. 공간을 탐색하는 재미를 남기면서도, 매번 같은 경로를 거치게 하고 싶지는 않았다.

Blender에서 만든 장면을 웹으로 가져오기

방을 구성하는 에셋뿐 아니라 물건이 반응하고 화면이 이동하는 애니메이션도 Blender에서 직접 만들었다. 아무것도 누르지 않았을 때의 움직임, 포인터를 올렸을 때의 반응, 클릭한 뒤 이어지는 동작이 각각 필요했다.

책을 예로 들면, 단순히 책 한 권을 만드는 것으로 끝나지 않는다. 책장에 놓여 있다가 반응하고, 앞으로 나와 펼쳐진 뒤, 다시 돌아가는 흐름까지 있어야 한다. 웹에서는 이 흐름의 어느 지점에서 글을 보여줄지도 정해야 했다.

Blender에서 렌더링한 방 전체 모습
Blender의 원본 조명으로 렌더링한 장면. 웹에서는 별도로 조명과 재질의 보이는 모습을 맞췄다.

장면은 GLB 형식으로 내보냈다. 웹에서는 Three.js를 React 안에서 다룰 수 있는 React Three Fiber를 사용한다. 방과 물건, 카메라의 움직임은 3D 장면이 담당하고, 메뉴와 글 본문은 React 화면으로 구성했다.

여기서 Blender에서 보이는 결과가 파일 하나만 가져오면 그대로 재현되는 것은 아니었다. 일반적인 glTF 애니메이션으로 전달하는 위치·회전·크기 외에도, 이 프로젝트에서 사용하는 조명의 밝기나 재질의 발광, 카메라 렌즈 변화는 별도 데이터로 내보내 웹에서 재생하도록 했다.

웹의 조명과 그림자도 따로 설정했다. 원본 렌더와 브라우저 화면을 비교하면서 가져와야 할 것은 장면의 형태뿐 아니라 분위기라는 점도 확인할 수 있었다.

책이 열리는 순간, 글을 읽는 화면으로

글 화면은 펼친 책처럼 양쪽으로 나눴다. 왼쪽에는 글 목록을 두고, 오른쪽에는 선택한 글을 보여준다. 목록을 오가더라도 읽는 공간의 위치가 크게 바뀌지 않도록 했다.

그렇다고 본문을 실제 3D 책 표면에 그리지는 않았다. 긴 글에는 텍스트 선택, 링크, 스크롤, 이미지 확대처럼 익숙하게 사용할 수 있는 기능이 필요하다. 책을 펼치는 장면과 읽는 화면의 역할을 나누고, 본문은 HTML로 표시했다.

책장에서 책이 앞으로 나오는 애니메이션 중간 장면
글 화면에 들어가기 전, Blender에서 만든 책 애니메이션이 재생된다.

전환은 책을 여는 부분까지만 재생한 뒤 멈춘다. 그 위에 글 화면을 띄우고, 사용자가 창을 닫으면 닫히는 구간으로 이어진다. 글을 읽고 있는 동안 애니메이션이 혼자 끝까지 재생되거나 처음부터 반복되지 않도록 한 것이다.

읽는 화면의 크기도 여러 번 조정했다. 펼친 책이라는 모양이 있어도 본문 영역이 좁으면 긴 글을 읽기 어렵다. 화면을 더 넓게 사용하고, 목록 위에 붙어 있던 소개 문구도 덜어냈다. 그 자리는 실제 글 목록과 본문에 쓰는 편이 나았다.

왼쪽 글 목록과 오른쪽 본문으로 나뉜 블로그 읽기 화면
책을 여는 동작 뒤에는 넓은 읽기 화면이 나온다. 본문은 React와 HTML로 표시한다.

애니메이션보다 어려웠던 시작과 끝

동작 하나만 재생할 때는 괜찮아 보여도, 방에서 그 동작으로 들어가는 순간에는 화면이 툭 튀었다. 메인 방을 보여주는 구도와 Blender 애니메이션의 첫 프레임이 서로 달랐기 때문이다.

방을 조금 더 크게 보여주려고 확대하면 문제가 더 잘 드러났다. 같은 배율을 애니메이션에도 적용하는 것만으로는 두 화면이 자연스럽게 이어지지 않았다. 쉬고 있는 방의 구도와 애니메이션이 시작되는 구도를 각각 다룰 필요가 있었다.

그래서 클릭하자마자 애니메이션의 첫 프레임으로 이동하는 대신, 현재 화면의 상태를 먼저 저장하도록 했다. 현재 카메라와 물건의 위치·회전 등을 시작 프레임의 상태까지 약 0.65초 동안 보간한 뒤, 원래 애니메이션을 재생한다. 보간은 두 상태 사이의 값을 조금씩 바꾸어 이동시키는 방식이다.

돌아올 때도 같은 처리가 필요했다. 동작이 끝났다고 곧바로 기본 방 상태로 바꾸면 마지막에 다시 끊긴다. 끝나는 지점에서 방의 기본 구도까지 이어 주어야, 클릭부터 복귀까지 하나의 움직임으로 보였다.

포인터를 올렸을 때의 반응도 비슷했다. 하나의 물건이 여러 메시로 구성되어 있으면, 그 안에서 포인터를 조금 움직이는 것만으로도 진입과 이탈이 여러 번 발생할 수 있다. 그러면 애니메이션이 계속 다시 시작되는 것처럼 보인다. 개별 메시 대신 상호작용할 물건을 기준으로 묶어 처리하면서 이 반복을 줄였다.

이 과정에서 애니메이션 클립 자체만큼이나 서로 다른 상태가 만나는 경계를 살피는 것이 중요했다.

방 밖으로 이어지는 장면

심레이싱 장비를 누르면 운전석의 1인칭 화면으로 이동한다. 방 안에서 장비를 바라보는 것과, 그 자리에 직접 앉은 구도를 보는 것은 느낌이 꽤 다르다.

심레이싱 장비에서 연결되는 서킷의 1인칭 운전석 화면
방의 심레이싱 장비에서 이어지는 운전석 시점. 현재는 장면을 둘러보는 화면이며, 주행 물리를 구현한 게임은 아니다.

방과 컴퓨터 화면, 서킷은 각각의 장면으로 나눴다. 사용자가 이동할 때 필요한 장면을 불러오도록 해서 첫 화면에서 모든 모델을 한꺼번에 준비하지 않게 했다.

이런 이동이 늘어날수록 애니메이션의 시작과 끝을 자연스럽게 연결하는 처리가 더 중요해졌다. 각각의 화면이 잘 보여도 그 사이가 끊기면, 하나의 공간을 돌아다닌다는 느낌이 약해지기 때문이다.

3D를 거치지 않고 읽는 방법도 만들기

블로그를 방문한 모든 사람이 방을 구경하고 싶어 하지는 않을 것이다. 글만 빨리 읽고 싶은 사람도 있고, 기기나 상황에 따라 3D 화면이 부담스러울 수도 있다. 그래서 일반적인 목록에서 글을 찾아 읽는 2D 화면을 함께 만들었다.

두 화면의 콘텐츠는 같다. 별도의 글을 작성하는 대신, 같은 Markdown 파일을 서로 다른 입구에서 보여준다.

시계와 달력 소품, 글 목록이 배치된 일반 블로그 화면
3D 장면 없이도 같은 글을 읽을 수 있는 일반 블로그 화면.

2D 화면에는 방에서 사용한 녹색과 크림색, 작은 버튼의 느낌을 이어 갔다. 양옆의 여백에는 시계와 달력 같은 책상 소품을 놓았다. 정해진 칸에 나란히 쌓기보다는, 본문을 가리지 않는 남은 공간 안에서 위치가 달라지도록 했다.

여기서도 소개 문구나 장식을 계속 추가하기보다 실제로 필요한 것을 남기는 쪽으로 다듬었다. 글을 찾고 읽는 부분이 중심이고, 소품은 그 주변에 놓여 있으면 충분했다.

마지막으로 선택한 화면은 브라우저에 저장한다. 일반 블로그를 선택했다면 다음에 첫 주소로 들어왔을 때도 그 화면으로 시작한다. 이 경우에는 3D 장면을 먼저 불러올 필요도 없다.

부드러움은 파일과 화면 양쪽에서

장면이 이어지는 방식과 별개로, 모델을 불러오고 그리는 비용도 줄여야 했다. GLB 안의 중복 데이터를 정리하고 메시 데이터를 압축한 최적화 버전을 만들었다. 일반 모드와 최적화 모드 사이에서 렌더링 품질을 바꾸는 스위치도 두었다. 두 모드 모두 압축된 모델을 사용하고, 해상도와 그림자 품질을 조절하는 방식이 다르다.

읽기 화면이 열려 있을 때는 뒤에 가려진 방을 계속 렌더링하지 않도록 했다. 3D 화면에서는 프레임 처리 시간을 보고 해상도를 조절한다. 장면을 무조건 같은 해상도로 그리기보다, 움직임이 버거워질 때 부담을 낮추는 방식이다.

목표는 60fps에 가까운 움직임이다. 다만 모든 기기에서 60fps를 유지한다고 말할 수는 없다. 파일 크기, 화면 해상도, 그림자, 실행하는 기기의 성능이 함께 영향을 준다. 최적화 스위치가 생겼다는 것과 충분히 검증되었다는 것도 다른 일이다. 다양한 기기에서 실제로 확인하는 작업은 계속 필요하다.

이제 이 공간에 기록을 채우기

직접 만든 방을 웹에 띄우는 일에서 시작해, 책을 펼치고 글을 읽고 다시 돌아오는 흐름까지 만들었다. 그 사이에 가장 많이 고친 것은 화려한 효과보다 전환이 끊기는 순간과 읽기 불편한 화면이었다.

이 첫 글은 전체 흐름을 기록한 제작기다. Blender에서 에셋과 애니메이션을 구성한 방법, 웹에서 카메라 전환을 연결한 코드는 각각 별도의 글에서 더 자세히 풀어볼 수 있을 것 같다.

블로그의 공간은 준비됐다. 이제는 그 안에 만든 것과 막혔던 것, 다시 고친 과정을 하나씩 남겨보려고 한다.


글의 구성을 정리하면서 다음 제작기들을 참고했다. 완성된 모습을 먼저 보여주고 구현 선택과 시행착오를 이어 설명하는 방식이 도움이 됐다.