← DEVLOG
인터랙티브2025.12.205 min read

Cosmic Breathing — 숨 쉬는 성운, 코드로 만든 명상

성운이 호흡에 맞춰 팽창·수축하는 인터랙티브 명상 — 5가지 호흡 패턴, Web Audio 동기 앰비언트, Canvas 성운 렌더링

web-audiocanvasmeditationbreathingambient

시작 — 쉬고 싶었습니다

코드를 짜다 보면 모니터 너머의 시간 감각이 사라집니다. 새벽 3시에 고개를 들면 어깨는 돌처럼 굳어 있고, 호흡이 얕아져 있다는 걸 그때야 알게 됩니다.

우주 시뮬레이션과 게임을 만들면서, 한편으로는 정반대의 것이 필요했습니다. 아무것도 조작하지 않아도 되는, 그냥 숨만 쉬면 되는 공간. 성운이 내 호흡에 맞춰 부풀었다 줄어드는 것을 바라보기만 하면 되는 곳.


호흡 패턴 설계

호흡법은 단순한 타이머가 아닙니다. 각 기법마다 들숨·참기·날숨·쉬기의 비율이 다르고, 그 비율이 신체에 미치는 영향도 다릅니다.

const PATTERNS: Record<PatternId, PatternDef> = {
  relax: { phases: [inhale(4), hold_in(4), exhale(6)] }, // 이완
  box: { phases: [inhale(4), hold_in(4), exhale(4), hold_out(4)] }, // 균형
  sleep: { phases: [inhale(4), hold_in(7), exhale(8)] }, // 4-7-8 수면
  coherent: { phases: [inhale(5.5), exhale(5.5)] }, // 공명
  sigh: { phases: [inhale(2.5), inhale(1.5), exhale(7)] }, // 한숨
};

4-7-8 수면 패턴은 앤드류 와일 박사의 호흡법에서 가져왔습니다. 긴 날숨이 부교감신경을 활성화합니다. 공명 호흡(5.5초-5.5초)은 심박 변이도(HRV)를 최적화한다고 알려져 있습니다. 한숨 호흡은 스탠퍼드 연구에서 효과가 확인된 cyclic sigh — 이중 들숨 후 긴 날숨 — 패턴입니다.


성운이 숨을 쉽니다

호흡 진행도(progress: 0~1)를 easeInOut으로 변환해 성운 반지름에 매핑합니다. 들숨이면 팽창하고, 날숨이면 수축합니다.

function getBreathScale(state: BreathDrawState): number {
  const ep = easeInOut(progress);
  if (phase === 'inhale') return ep; // 0 → 1 팽창
  if (phase === 'hold_in') return 1; // 최대
  if (phase === 'exhale') return 1 - ep; // 1 → 0 수축
  return 0; // 최소
}

성운의 색상도 호흡에 반응합니다. 들숨이 깊어지면 차가운 보라(80,60,180)에서 따뜻한 분홍(200,100,160)으로 선형 보간됩니다. 숨을 들이마실수록 성운이 따뜻해지는 것입니다.

성운 렌더링은 OffscreenCanvas에 먼저 그린 뒤 blur 필터를 한 번에 적용하는 배칭 기법을 사용합니다. blur 비용은 pixel_count × radius²에 비례하므로, 개별 요소마다 blur를 거는 것보다 훨씬 효율적입니다.


소리가 호흡을 따라갑니다

배경 패드는 3~4개의 근접 주파수 사인파를 겹쳐 자연스러운 맥놀이를 만듭니다. 110Hz와 111.5Hz — 1.5Hz 차이가 느린 파동을 만들어냅니다.

// 호흡 동기 드론: 들숨 시 볼륨과 주파수가 함께 올라갑니다
if (phase === 'inhale') {
  targetGain = 0.06 + progress * 0.06; // 0.06 → 0.12
  targetFreq = 55 + progress * 10; // A1(55Hz) → C#2(65Hz)
}

들숨에서 드론 오실레이터가 55Hz에서 65Hz로 올라가며 음의 밀도가 높아집니다. 날숨에서 다시 55Hz로 내려오면서 공간이 넓어지는 느낌을 줍니다. 이 10Hz의 미세한 변화가 호흡에 물리적인 감각을 더합니다.

각 호흡 패턴마다 패드 주파수가 다릅니다. 수면 패턴은 82.41Hz(E2) 기반으로 더 깊고 낮은 음을, 균형 패턴은 130.81Hz(C3)로 좀 더 명료한 음색을 사용합니다.


스트릭과 통계

명상은 꾸준히 하는 것이 중요합니다. 매 세션의 시간을 누적하고, 연속 일수를 추적합니다.

// 완료 시 업데이트
const today = new Date().toISOString().slice(0, 10);
const lastDate = storageGet<string>('breathing:lastDate', '');
const prevStreak = Number(storageGet<string>('breathing:streak', '0'));

const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
const newStreak = lastDate === yesterday ? prevStreak + 1 : lastDate === today ? prevStreak : 1;

며칠째 매일 숨을 쉬고 있는지, 총 몇 분이 쌓였는지. 큰 의미는 없지만, 내일도 3분을 열어보게 만드는 작은 동기가 됩니다.


마치며

우주 시뮬레이터를 만들다가 명상 앱을 만들 줄은 몰랐습니다.

그런데 성운의 팽창과 수축을 바라보며 호흡을 맞추고 있으면, 시뮬레이션과 명상 사이의 경계가 생각보다 얇다는 걸 느낍니다. 둘 다 결국 리듬을 만드는 것이니까요 — 하나는 물리 법칙의 리듬이고, 하나는 몸의 리듬입니다.

모니터 앞에서 어깨가 굳어 있을 때, 3분만 열어보시기 바랍니다.

이 포스트와 연결된 콘텐츠

직접 체험하기