커리어해커 알렉스
Second Brain프롬프트 만들기이미지 프롬프트강의자료블로그자주 묻는 질문

커리어해커 알렉스

커리어 · AI · 성장을 다루는 알렉스의 공식 허브

© 2026 커리어해커 알렉스

소개자주 묻는 질문개인정보처리방침이용약관
Blog

2026-09-15

브라우저 속 3D 작업실을 만든 방법

Blender에서 만든 가구와 미리 계산한 빛이 Three.js에서 화면이 되기까지. /studio의 모델링, 조명, 파일 구조, 실시간 렌더링을 실제 구현으로 풀어봅니다.

제 웹사이트에는 들어가서 걸어 다닐 수 있는 작업실이 있습니다. 바닥을 누르면 그곳으로 이동하고, 화면을 드래그하면 고개를 돌립니다. 책상과 소파, 커피 바 사이에는 제가 만든 콘텐츠와 Second Brain이 놓여 있습니다.

이 방을 이해하는 출발점은 계산의 시점을 나누는 것입니다. Blender에서 형상과 기본 조명을 준비하고, Three.js와 GPU가 방문자의 시점에 맞는 화면을 계속 만듭니다. 제작할 때 해 두는 계산과 방문자가 움직일 때 필요한 계산이 다릅니다.

소파 하나가 화면에 나타나는 과정을 따라가 보겠습니다. 기본 설명과 수치는 2026년 9월 13일의 fidelity-styling-20260913 구현을 기준으로 합니다. 9월 15일의 식물·발견 방식 변경과 최신 수치는 글 마지막에 덧붙였습니다.

곡면 천장 아래 책상과 의자, 촬영 장비, 식물, 포스터와 Second Brain이 놓인 3D 작업실

2026년 9월 13일 개발 브라우저에서 저장한 화면입니다. 가구는 3D 형상이고, 포스터와 모니터 내용은 표면 이미지이며, 중앙의 그래프는 실행 중 생성하는 요소입니다.

소파의 모양은 점과 면으로 저장됩니다

컴퓨터에 소파를 보여 주려면 먼저 형태를 정의해야 합니다. 공간 어디에 점을 놓을지, 어떤 점들을 연결해서 면을 만들지 정합니다. 이 점들을 정점(vertex), 정점과 면으로 이루어진 형상을 메시(mesh)라고 부릅니다. 화면을 그리는 GPU는 주로 삼각형 단위로 면을 처리합니다.

우리 작업실의 방과 주요 가구는 Python의 Blender API인 bpy로 구성했습니다. 벽은 상자를 만들고 크기를 정합니다. 둥근 천장은 여러 타원 단면을 연결하고, 금속 다리는 곡선을 따라 관을 만듭니다. 모서리를 둥글게 깎는 bevel과 표면을 매끄럽게 보이게 하는 설정도 적용합니다.

모델링 스크립트에는 다음과 같은 코드가 있습니다. points는 정점 좌표이고, faces는 연결할 정점들의 번호입니다. v()는 프로젝트에서 사용하는 좌표를 Blender 좌표로 바꾸는 함수입니다.

data = bpy.data.meshes.new(name)
data.from_pydata([v(p) for p in points], [], faces)
data.update()
ob = bpy.data.objects.new(name, data)

컨셉 이미지는 모양과 배치, 분위기를 정하는 기준이었습니다. 이미지에서 방 전체를 자동 복원한 과정은 없었습니다. 이미지를 참고해서 크기와 곡선, 배치를 코드로 정의하고, 브라우저에서 본 결과와 비교하며 다듬었습니다.

모든 물체를 처음부터 직접 만든 것은 아닙니다. 최근 식물 개선에서는 Poly Haven의 스캔 모델을 가져와 기존 화분과 공간에 맞게 배치했습니다. 일부 나무와 선반 식물은 직접 만든 형상을 유지합니다. 직접 만든 모델과 가져온 모델을 하나의 방으로 조립한 셈입니다.

형상 위에는 재질을 입힙니다. 나뭇결처럼 표면의 색과 무늬를 정하는 이미지가 있고, 반사가 얼마나 퍼지는지 정하는 roughness, 금속처럼 반응하는지 정하는 metallic 값이 있습니다. Normal map은 미세한 요철이 빛을 받는 방향을 표현합니다. 직물의 모든 실밥을 별도의 삼각형으로 만들지 않아도 표면의 결을 드러낼 수 있습니다.

방의 기본 조명은 미리 계산합니다

소파 아래가 어두운 이유는 빛이 충분히 들어가지 않기 때문입니다. 창 가까운 바닥이 밝고, 빛을 직접 받지 않는 벽에도 은은한 색이 남는 모습까지 표현하려면 주변 물체와 빛의 관계를 계산해야 합니다.

Blender의 Cycles가 이 계산을 맡습니다. 조명에서 직접 도달하는 빛과 다른 표면에 반사된 뒤 도달하는 빛을 계산하고, 결과를 이미지로 저장합니다. 이 작업이 baking이며, 조명 결과를 담은 이미지가 lightmap입니다.

Lightmap은 물체의 표면을 펼쳐 놓은 지도에 가깝습니다. 상자를 펼치면 여섯 면의 전개도가 생기듯, 방의 여러 표면을 이미지 안에 배치합니다. 각 표면에는 “이 이미지의 어느 위치를 읽어야 하는가”를 나타내는 UV 좌표가 붙습니다.

카메라로 찍은 방 사진과는 다릅니다. 한 시점에서 보이는 모양을 저장한 사진은 고개를 돌리면 사용할 수 없습니다. Lightmap은 물체 표면에 연결되어 있으므로 카메라가 움직여도 같은 표면의 조명을 읽습니다.

우리 메시는 재질용 UV와 조명용 UV를 따로 사용합니다. 나뭇결 이미지를 찾는 좌표와 조명 이미지를 찾는 좌표를 분리한 것입니다. 여러 표면의 조명 구역을 모은 atlas는 방용 4096×4096, 식물용 2048×2048로 나뉩니다. 각각 낮과 밤 두 벌을 준비합니다.

현재 bake는 표면의 기본 색을 제외하고 diffuse direct·indirect 조명을 저장합니다. Three.js에서 표면의 색과 미리 계산한 빛을 결합하도록 구성했습니다. 밝은 값을 보존하는 HDR 데이터로 계산한 뒤 배포용 KTX2 형식으로 압축합니다.

기록된 bake 실행 시간은 약 24.5분입니다. 방을 제작할 때 한 번 수행한 작업 시간입니다. 방문자는 그 계산을 기다리는 대신 이미 저장한 결과를 내려받습니다. 구현 수치와 출처

Blender와 Three.js 사이에는 파일이 있습니다

Blender의 .blend는 편집용 원본입니다. 물체와 modifier, 재질 노드, 조명 등 제작에 필요한 정보를 보관합니다. 웹에는 그중 실행에 필요한 결과를 내보냅니다.

형상과 재질은 studio.glb에 담깁니다. GLB는 glTF의 바이너리 형식입니다. 우리 파일에는 정점과 면, 재질과 이미지, 오브젝트 이름과 기준점 등이 들어 있습니다. 조명은 별도의 KTX2 파일로 전달합니다.

Blender 제작 원본
  ├─ 형상·재질·이미지 → studio.glb
  └─ Cycles bake → 낮·밤 조명 KTX2

Three.js가 파일을 읽음
  → 3D 장면과 GPU 데이터를 준비
  → 방문자의 시점에 맞춰 렌더링

Three.js의 GLTFLoader가 모델을 불러오는 과정은 다음처럼 요약됩니다. 실제 구현에는 Draco 압축 해제, KTX2 로딩, 조명 연결과 오류 처리가 더 붙습니다.

const model = await loader.loadAsync("studio.glb");
scene.add(model.scene);

Blender의 Python 코드를 JavaScript로 번역하는 단계는 없습니다. Three.js는 내보낸 데이터를 읽어 자신의 장면 객체로 구성합니다. 방문자가 방을 보고 있는 동안에는 Blender가 실행되지 않습니다. GLTFLoader 공식 문서

2026년 9월 13일 에셋의 최종 GLB는 약 39.2MB입니다. 조명 파일 네 개를 합치면 약 70.4MB이며, 이 수치에는 JavaScript와 추가 콘텐츠가 포함되지 않습니다. MB는 파일 바이트 수를 1,000,000으로 나눠 계산했습니다.

GLB에는 내보내기 메시 13개가 있습니다. 방에 물건이 13개라는 뜻은 아닙니다. 제작 중에는 나뉘어 있던 고정 물체를 내보낼 때 합쳤기 때문입니다. 재질별 그리기 구간은 남아 있으며, 방과 식물의 정적 형상은 약 112만 삼각형입니다. 에셋 인코딩 기록

카메라가 움직일 때마다 화면은 새로 만들어집니다

소파 옆으로 걸어가면 앞에서 보이지 않던 옆면이 드러납니다. 소파의 형상 데이터는 그대로지만 화면에서 차지하는 위치와 모양은 달라집니다. Three.js와 GPU는 이 변화를 계속 계산합니다.

한 장의 화면을 프레임이라고 합니다. 프레임을 만드는 과정은 다음과 같습니다.

  1. JavaScript가 입력을 읽고 카메라의 위치와 방향을 바꿉니다.
  2. GPU가 물체의 3D 좌표를 카메라 기준으로 변환하고, 원근 투영으로 화면 좌표를 구합니다. Vertex shader가 이 계산을 담당합니다.
  3. Rasterization이 삼각형이 덮는 화면 샘플을 정합니다. 깊이 검사로 가까운 불투명 표면이 뒤쪽을 가립니다.
  4. Fragment shader가 표면 무늬와 lightmap을 읽고, 재질과 반사, 추가 조명을 반영해 색을 계산합니다.
  5. 후처리에서 계단 현상과 빛의 번짐을 다루고, tone mapping과 색공간 변환을 거쳐 화면에 표시합니다.

이 과정에서 고정된 정점 데이터를 매번 새로 만들지는 않습니다. GPU에 준비한 데이터를 재사용하면서 카메라와 필요한 조명 값을 갱신합니다. 현재 /studio는 Three.js의 WebGLRenderer를 사용하는 WebGL2 경로로 동작합니다.

카메라를 옮기는 계산과 간접광을 계산하는 작업의 비용은 다릅니다. 시점이 바뀔 때 필요한 계산은 계속 수행하고, 고정된 방의 기본 빛은 미리 계산한 결과를 읽습니다. 이 구분 덕분에 움직이는 화면과 실내 조명의 분위기를 함께 유지합니다.

반사와 램프는 실행 중에도 반응합니다

기본 diffuse 조명은 lightmap에 담겨 있지만, 방문자의 시점이나 조작에 반응해야 하는 효과도 있습니다.

요소현재 구현
벽과 가구의 기본 조명Blender에서 bake한 lightmap을 읽습니다.
낮과 밤UV가 같은 낮·밤 lightmap을 셰이더에서 섞습니다.
금속과 목재의 환경 반사방 안에서 한 번 캡처한 큐브 환경을 시점과 재질에 맞춰 읽습니다.
바닥 반사반사용 시점으로 장면을 추가 렌더링합니다.
켜고 끄는 램프Three.js의 조명을 조절하고, 초기화 때 만든 고정 그림자 지도를 재사용합니다.
Second Brain공개 그래프의 점과 선을 생성하고 선택과 인용 상태에 따라 강조합니다.

낮과 밤 전환은 두 조명 이미지의 혼합 비율을 바꾸는 방식입니다. 자동 모드에서는 방문자 기기의 시간에 맞춰 하늘색과 외부 풍경도 변합니다. 실제 태양의 위치를 계산하는 천문 시뮬레이션은 아니며, 시간대별로 설계한 연출입니다.

스튜디오 라운지의 낮 조명과 창에서 들어오는 빛

같은 라운지의 낮입니다. 2026년 9월 13일 개발 브라우저 캡처.

같은 스튜디오 라운지에서 램프와 실내등이 강조되는 밤 조명

같은 라운지의 밤입니다. 형태를 다시 만드는 과정 없이 준비한 조명과 실행 중 효과를 조합합니다.

화면을 누르는 행동도 별도 코드가 처리합니다. 카메라에서 클릭 지점을 향해 가상의 선을 쏘고, 무엇과 만나는지 검사합니다. 이 교차 검사가 raycasting입니다. 가림 검사에는 삼각형을 공간별로 묶어 검색하는 BVH를 사용합니다.

바닥 이동은 가구의 사각형·원형 장애물 범위를 검사합니다. 바로 갈 수 없으면 0.3m 간격의 격자에서 경로를 찾고 중간 지점을 줄입니다. 물건을 열었을 때 나오는 글과 영상은 React 화면입니다. 발견 기록과 읽던 페이지, 조명 선택은 브라우저의 localStorage에 보관합니다.

소파를 옮기면 저장해 둔 그림자도 달라져야 합니다

이 구조에서 손이 더 가는 부분은 수정입니다. 고개를 돌리는 행동은 소파의 형상이나 방의 빛을 바꾸지 않습니다. 소파를 다른 위치로 옮기면 주변 그림자와 간접광이 달라집니다.

현재 고정 가구는 합쳐서 내보냈습니다. 가구의 형태나 배치를 바꾸려면 Blender 원본을 수정하고, 영향받는 조명을 다시 bake해서 내보냅니다. 이동 장애물과 클릭 기준점도 점검해야 합니다. 특히 이동 장애물은 모델에서 자동으로 생성하지 않으므로 가구를 옮겼다고 경로 규칙까지 함께 바뀌지 않습니다.

모니터의 내용이나 설명 글, 이동 속도, 낮과 밤의 전환 시각은 웹 코드에서 바꿉니다. 형상이나 저장된 조명을 바꾸지 않는 수정에는 bake가 필요하지 않습니다.

Blender에서 본 화면과 웹의 결과가 다른 이유도 여기에 있습니다. Cycles는 시간을 들여 빛을 추적하고, 웹은 저장된 조명과 실시간 효과를 조합합니다. 텍스처 해상도, 압축, 유리 표현, 반사 방식, 색공간과 tone mapping에 따라 인상이 달라집니다.

그래서 모델을 내보낸 뒤에도 브라우저에서 확인했습니다. 조명 atlas를 방과 식물로 나누고, 표면 경계의 번짐을 다듬고, 셰이더를 미리 준비하고, 기기 속도에 따라 렌더 해상도와 효과를 조절합니다. 현재 파일 크기는 여전히 큰 편이며 모든 기기에서 60fps를 보장한 결과는 아닙니다.

3D 웹을 설계할 때는 무엇을 움직이게 할지 먼저 정해야 합니다. 고정된 방을 둘러보는 경험과 가구를 자유롭게 재배치하는 경험은 필요한 형상 구성과 조명, 충돌 처리가 다릅니다. 어떤 계산을 미리 해 둘 수 있는지는 사용자가 바꿀 수 있는 것의 범위가 결정합니다.

9월 15일에는 식물과 발견 방식을 다듬었습니다

갤러리의 식물이 화분에서 떨어져 보이는 문제가 있었습니다. 스캔 모델의 변종을 나란히 놓아 둔 원본 오프셋이 회전·축소 과정에 남아 있었고, 화분을 찾는 코드도 일부 재질을 놓쳤습니다. 뿌리와 화분 바닥을 기준으로 배치하도록 고친 뒤 여덟 식물 그룹을 다시 구성했습니다.

갤러리에는 아몬드 트리와 몬스테라를 직접 만들었습니다. 아몬드는 작은 잎 270장과 가늘어지는 가지를 갖고, 몬스테라의 갈라진 잎과 구멍은 실제 형상입니다. 줄기와 흙, 화분이 이어지는지 확인하고 낮·밤 그림자를 다시 계산했습니다. 이번 베이크는 약 25.6분이 걸렸습니다.

갤러리에 나란히 놓인 아몬드 트리와 몬스테라. 줄기가 화분 안의 흙으로 이어집니다.

2026년 9월 15일 개발 브라우저에서 확인한 압축 모델입니다.

물건 위의 + 마커도 없앴습니다. 보이는 물건에 가까이 가서 잠깐 머물면 오른쪽 위에 안내 하나가 나타납니다. 안내가 떠도 카메라와 포커스가 움직이지 않고, 글이나 영상은 방문자가 선택했을 때 열립니다. 닫으면 그 구역을 떠날 때까지 다시 재촉하지 않습니다.

현재 모델은 40.08MB이고 낮·밤 조명 네 파일을 합하면 주요 자산은 71.39MB입니다. 첫 진입이 기다리는 모델과 낮 조명만 약 55.86MB이므로 다음 과제는 실제 로딩 단계를 측정하고 입구에 필요한 자산을 나누는 일입니다. 밤 전용 환경 반사와 가까이 보는 재질, 세로 화면의 조작도 비교할 계획입니다. WebGPU는 기능을 같은 상태로 맞춘 실험에서 이득을 확인한 뒤 판단합니다.

최신 자산은 botanical-refinement-20260915입니다. 위의 9월 13일 캡처와 수치는 이전 제작 단계를 설명합니다. 현재 제작 문서와 다음 개선 계획에 재제작 절차와 아직 검증하지 않은 과제를 기록했습니다.

구현과 참고 자료

  • 작업실 둘러보기
  • 이 글의 구현 수치와 출처. 2026년 9월 13일 에셋과 9월 15일 코드 확인을 기준으로 합니다.
  • 배포 에셋의 인코딩 기록
  • Three.js GLTFLoader. 모델과 압축 데이터 로딩을 설명합니다.
  • Blender Cycles Render Baking. 표면에 조명 결과를 저장하는 설정을 설명합니다.