Scale — 플랑크 길이에서 관측 가능한 우주까지
62자릿수에 걸친 우주를 하나의 캔버스에 담는 과정 — 로그 스케일 균등 배치, 오브젝트별 커스텀 드로잉, 상대 크기 렌더링의 고민
시작 — 62자릿수의 문제
플랑크 길이는 10⁻³⁵m, 관측 가능한 우주는 10²⁷m. 그 사이 62자릿수. 선형 스크롤로는 양자 영역에서 원자까지 도달하는 데만 화면을 수만 번 스크롤해야 합니다.
"Scale of the Universe" 류 콘텐츠의 핵심 문제는 이 극단적 범위를 어떻게 사용자가 자연스럽게 탐험하게 만드느냐입니다. 로그 스케일을 쓰면 해결될까요? 단순 로그 스케일에는 함정이 있습니다.
균등 배치 — scaleWarp
로그 스케일의 문제는 오브젝트 분포가 균일하지 않다는 점입니다. 양자 영역(10⁻³⁵10⁻¹⁴)에는 4개 오브젝트가 20자릿수에 걸쳐 흩어져 있고, 일상 영역(10⁻³10¹)에는 4개가 4자릿수에 밀집해 있습니다. 단순 로그 매핑을 적용하면 양자 영역은 텅 비고 일상 영역은 겹치는 문제가 생깁니다.
해결책으로 "visual warp"를 도입했습니다. 모든 인접 오브젝트 쌍을 동일한 시각 간격(1 visual unit)으로 배치합니다.
// 모든 인접 오브젝트 쌍을 동일 시각 간격으로 배치
const UNIFORM_GAP = 1;
function buildWarpTable(): WarpPoint[] {
const positions = [...objectLogPositions].sort((a, b) => a - b);
const points: WarpPoint[] = [{ log: positions[0], visual: 0 }];
let visual = 0;
for (let i = 1; i < positions.length; i++) {
visual += UNIFORM_GAP;
points.push({ log: positions[i], visual });
}
return points;
}
이 테이블을 기반으로 logToVisual/visualToLog 이진 탐색 변환을 제공합니다. 양자 영역의 20자릿수 빈 구간은 압축되고, 밀집 영역은 적절히 펼쳐집니다. 스크롤 한 번에 다음 오브젝트까지 일정한 거리를 이동하게 되어, 사용자는 "빈 공간을 한참 스크롤하는" 경험을 하지 않습니다.
상대 크기 — 보이지만 비교 가능하게
38개 오브젝트를 같은 크기로 그리면 비교 체험이 되지 않습니다. 하지만 실제 물리 비율 그대로 적용하면 양성자(10⁻¹⁵m)는 지구(10⁷m) 옆에서 완전히 사라집니다.
타협점으로 "80% 로그 비율"을 적용했습니다.
const delta = obj.logSize - logScale; // 현재 중앙 스케일과의 차이
const RATIO_POWER = 0.8;
const sizeRatio = Math.pow(10, delta * RATIO_POWER);
const radius = Math.max(3, Math.min(maxDraw, baseR * sizeRatio));
delta=0(화면 중앙)인 오브젝트는 기본 크기(300px)로 표시되고, 스케일이 1자릿수 벗어날 때마다 크기가 10⁰·⁸≈6.3배 변합니다. 순수 물리 비율(10배)보다 완만하지만, 크기 차이가 명확히 느껴집니다.
여기에 앵커 방식이 중요했습니다. 중앙 정렬을 쓰면 크기 변화 시 오브젝트가 위아래로 튀는 느낌이 납니다. "왼쪽 아래 모서리 앵커"를 적용해, 오브젝트가 바닥에 붙은 채 오른쪽+위로 자연스럽게 확장됩니다.
38개 오브젝트, 14종 드로잉
각 오브젝트를 단순 원으로 그리면 시각적 재미가 없습니다. 14종의 커스텀 shape을 만들었습니다.
| Shape | 대상 | 핵심 표현 |
|---|---|---|
glow | 플랑크, 쿼크, 양성자, 전자 | 3겹 레이디얼 글로우 + 맥동 애니메이션 |
orbit | 수소, 탄소 | 핵 + 전자 궤도 (타원 + 공전 점) |
helix | DNA | 이중나선 — Catmull-Rom 스플라인 + 가로대 |
blob | 바이러스 | 스파이크 돌기 + 캡시드 패턴 |
bacteria | 대장균 | 편모 4개 + 선모 22개 + 리보솜 30개 |
cell | 적혈구 | 이중 오목 디스크 + 그라디언트 |
silhouette | 개미, 사람, 고래 | 타원 조합 실루엣 |
circle | 모래알, 동전, 축구공 | 기본 원 + 세부 디테일 (무늬, 질감) |
mountain | 에베레스트 | 삼각형 조합 + 설선 + 정상 깃발 |
rect | ISS, 카르만선 | 태양 전지판, 대기 경계선 |
planet | 달, 지구, 목성, 토성 | 대기 글로우 + 표면 디테일 (크레이터, 대륙, 줄무늬) |
star | 태양, 프록시마 | 코로나 레이어 + 채층 + 흑점 |
galaxy | 은하수, 안드로메다 | 나선팔 (다층 그라디언트 + 별 파티클) |
cluster | 초은하단, 필라멘트, 관측 우주 | 점 군집 + 연결선 (코스믹 웹) |
가장 고민이 많았던 건 DNA 이중나선입니다. 단순 사인파 두 줄은 밋밋하고, 3D 느낌을 줘야 했습니다. 전면/후면 가닥을 분리하고 Catmull-Rom 보간으로 매끄러운 곡선을 그린 뒤, 후면은 어둡게, 전면은 밝게 처리해 깊이감을 만들었습니다.
박테리아도 까다로운 대상이었습니다. 현미경 사진을 참고해 편모(flagella)의 물결치는 움직임, 짧은 선모(pili), 내부 리보솜과 플라스미드까지 표현했습니다. 프레임당 150+ 캔버스 콜이 들어가지만, 화면에 보이는 박테리아는 한 개뿐이므로 성능에 문제가 없습니다.
사용성 — 발견의 경험
캔버스 인터랙티브 콘텐츠의 가장 큰 UX 위험은 "뭘 해야 하지?"입니다. DOM 요소가 아니라 캔버스이기 때문에 버튼도 스크롤바도 없습니다.
세 가지로 해결했습니다.
첫째, 초기 가이드 텍스트. 첫 방문 시 "← 스크롤하여 우주를 탐험하세요 →" 메시지가 화면 중앙에 표시되고, 약 3초 후 자연스럽게 사라집니다. 사용자가 스크롤을 시작하면 즉시 비표시됩니다.
둘째, 양쪽 쉐브론 화살표. 더 작은/큰 스케일로 이동 가능할 때만 표시되는 보라색 화살표가 좌우 끝에서 바운스합니다.
셋째, 하단 게이지 바. 전체 스케일 범위에서 현재 위치를 시각적으로 보여주고, 현재 스케일의 카테고리(양자 세계, 태양계 등)와 10ᴺ m 수치를 함께 표시합니다.
조작은 마우스 휠, 터치 스와이프, 화살표 키 세 가지를 모두 지원합니다. 모든 입력은 visual-space 단위로 변환되므로, 어떤 입력이든 "다음 오브젝트까지의 거리"가 일정합니다.
모바일 — 같은 콘텐츠, 다른 밀도
PC에서 잘 동작하는 레이아웃을 모바일에 그대로 적용하면 오브젝트가 겹치거나, 라벨이 잘리거나, 빈 공간이 너무 넓어집니다.
모바일에서는 visual unit당 화면 비율을 2.5배 확대합니다.
const VIS_RATIO_PC = 0.18; // 1 visual unit = 화면 너비의 18%
const VIS_RATIO_MOBILE = 0.44; // 1 visual unit = 화면 너비의 44%
이렇게 하면 모바일에서 인접 오브젝트 간 간격이 넓어져 겹침이 방지됩니다. 동시에 오브젝트 기본 반지름도 PC 150px → 모바일 90px로 줄이고, 오브젝트+라벨 그룹을 수직 중앙에 배치해 헤더(64px)와 하단 게이지 사이의 공간을 최대한 활용합니다.
마치며
우주의 크기를 직관적으로 이해하는 건 불가능합니다. 인간의 뇌는 10²²배 차이를 체감할 수 있도록 설계되지 않았습니다.
하지만 DNA에서 시작해 스크롤 한 번에 적혈구로, 다시 한 번에 개미로, 사람으로, 에베레스트로 이어지는 경험은 숫자가 줄 수 없는 감각을 전달합니다. 플랑크 길이에서 관측 가능한 우주까지 62자릿수를 스크롤하는 동안, 우리가 서 있는 위치가 얼마나 좁고 동시에 얼마나 넓은지를 느낄 수 있다면 — 이 페이지는 그것만으로 충분합니다.