도트 지구본

Globe dots

위도·경도 격자를 따라 점을 구 표면에 배치하고 천천히 돌려 지구본처럼 보이게 만드는 기법.

다른 이름: Dotted spherePoint-cloud globe
···
html
<div class="globe-wrap"><canvas id="gl"></canvas></div>
css
.globe-wrap{position:relative;width:min(260px,82%);aspect-ratio:1;display:grid;place-items:center}
.globe-wrap::before{content:"";position:absolute;inset:-8%;border-radius:50%;filter:blur(10px);
  background:radial-gradient(circle,color-mix(in srgb,var(--accent) 32%,transparent),transparent 70%)}
canvas{position:relative;width:100%;height:100%}
js
const canvas = document.getElementById('gl');
const ctx = canvas.getContext('2d');
const accent = getComputedStyle(document.documentElement).getPropertyValue('--accent').trim() || '#5b5bf7';
let W, H, R;
function resize() {
  const r = canvas.getBoundingClientRect();
  W = canvas.width = r.width * devicePixelRatio;
  H = canvas.height = r.height * devicePixelRatio;
  R = Math.min(W, H) * 0.42;
}
resize();
addEventListener('resize', resize);

const dots = [];
const LAT_STEPS = 14, LON_STEPS = 22;
for (let i = 1; i < LAT_STEPS; i++) {
  const lat = (Math.PI * i) / LAT_STEPS - Math.PI / 2;
  const ringR = Math.cos(lat);
  const count = Math.max(4, Math.round(LON_STEPS * ringR));
  for (let j = 0; j < count; j++) {
    const lon = (Math.PI * 2 * j) / count;
    dots.push([ringR * Math.cos(lon), Math.sin(lat), ringR * Math.sin(lon)]);
  }
}

let angle = 0;
function frame() {
  angle += 0.006;
  ctx.clearRect(0, 0, W, H);
  const cx = W / 2, cy = H / 2;
  const cosA = Math.cos(angle), sinA = Math.sin(angle);
  const projected = dots
    .map(([x, y, z]) => [x * cosA - z * sinA, y, x * sinA + z * cosA])
    .sort((a, b) => a[2] - b[2]);
  for (const [x, y, z] of projected) {
    const depth = (z + 1) / 2;
    const px = cx + x * R, py = cy - y * R;
    const size = (1.1 + depth * 2.1) * devicePixelRatio;
    ctx.globalAlpha = 0.2 + depth * 0.8;
    ctx.fillStyle = accent;
    ctx.beginPath();
    ctx.arc(px, py, size, 0, Math.PI * 2);
    ctx.fill();
  }
  requestAnimationFrame(frame);
}
frame();

각 점의 3D 좌표(x, y, z)는 위도·경도를 구면 좌표 공식으로 변환해서 얻습니다. 매 프레임 y축 회전 행렬을 곱해 점들의 좌표를 돌리면 공 전체가 회전하는 것처럼 보입니다.

입체감은 두 가지로 만듭니다. z값(카메라 쪽으로 얼마나 가까운지)이 클수록 점을 더 크고 진하게, 작을수록 더 작고 흐리게 그리는 게 "가까운 건 크게, 먼 건 흐리게"라는 원근 규칙입니다. 그리고 매 프레임 z값 기준으로 점들을 정렬해서 뒤쪽 점을 먼저, 앞쪽 점을 나중에 그리면 앞쪽 점이 뒤쪽 점을 자연스럽게 가립니다(화가의 알고리즘).

three.js로 실제 Points 지오메트리를 써서 만들 수도 있지만, 점 몇 백 개 수준에서는 이 데모처럼 2D canvas에 수학만으로 그리는 편이 훨씬 가볍습니다. 위도별 점 개수를 위도의 코사인에 비례해 줄이는 것도 중요한 디테일입니다 — 그렇지 않으면 극 지방에 점이 몰려 보입니다.

언제 쓰나

글로벌 서비스 소개, "전 세계에서 접속 중" 같은 통계 섹션의 배경 그래픽에 씁니다. 점 개수를 늘릴수록 느려지니 카드처럼 작은 영역에서는 수백 개 이하로 유지하세요.