# AI 꾸러미 — 은하수 띠 (먼지 줄 · 밝은 팽대부) — Milky Way band (galactic coordinates)
> 하늘 방향을 은하 좌표로 돌려 위도로 띠를 만들고, 미리 구운 잡음으로 별구름 · 가운데 먼지 줄(파랑을 더 먹어 붉게)을 그려 은하수를 띄운다.  
> 견본: https://ai-techstudio.web.app/#t/i523

이 문서를 코드 도우미(Claude Code · Cursor · ChatGPT 등)에 그대로 주면 돼요. 「## 주문서」 가 할 일, 나머지는 참고 자료예요.

## 주문서

### 만들어 줘: 은하수 띠 (먼지 줄 · 밝은 팽대부) — Milky Way band (galactic coordinates)

#### 1. 목표
밤하늘 배경에 은하수 띠를 그려 줘 — 별구름 · 가운데 먼지 줄 · 밝은 팽대부 · 지평선 대기광까지, 느낌은 사진 같은 은하수.

#### 2. 핵심 기술 용어
- **Milky Way band (galactic coordinates)** — 은하 좌표의 위도로 만든 은하수 띠
- **Dust lane extinction (wavelength dependent)** — 먼지가 파랑을 더 먹어 붉어지는 가림
- **Pre-baked 2D noise texture** — 셰이더에서 계산하지 않고 미리 구운 잡음 그림
- **Airglow** — 지평선 근처 대기광 (초록 · 주황)

#### 3. 환경
- 플랫폼: three.js r186 (ES 모듈 · TypeScript, `import * as THREE from "three"`), WebGL2, 외부 라이브러리 추가 없이
- 화면: 3D · 브라우저 — PC 와 폰(가로 844×390 · 세로 390×844) 모두, 60fps 목표

#### 4. 조건
- 잡음은 셰이더에서 계산하지 말고 미리 구운 2D 잡음 텍스처(512², 반복 이음)에서 읽는다
- 하늘 구는 BackSide · depthWrite false · renderOrder -2, 반지름은 별보다 바깥(450)
- 먼지 가림은 색마다 다르게 exp(-dust × (1.9, 2.4, 3.2)) — 회색으로 어둡게 하지 않는다
- 별 분포도 같은 은하 회전 행렬로 (은하면에 55%, 폭 0.07)
- ACESFilmic 톤 매핑, 은하 밝기는 uGlow × 0.16 정도로 옅게

#### 5. 완성 기준 (이게 보이면 성공)
- 하늘을 가로지르는 은하수 띠가 보이고, 가운데쯤 밝은 노란 팽대부가 있다
- 띠 한가운데를 어두운 먼지 줄이 가르고, 그 둘레는 붉은빛이 돈다
- 지평선 가까이 옅은 초록 · 주황 대기광과 산 그림자가 있다
- 「먼지 줄」 · 「별구름 밝기」 · 「대기광」 슬라이더가 바로 바뀐다

#### 6. 진행 방식
- 핵심 코드 위주로, 설명은 짧게. 내 프로젝트에 끼워 넣기 쉬운 함수 · 클래스로 나눠 줘.
- 처음 화면에 바로 결과가 보이게, 그리고 켬/끔(또는 전/후) 비교를 할 수 있게 만들어 줘.
- 그림 · 소리 · 모델 파일이 필요하면 코드로 만든 임시 대체물로 먼저 돌아가게 하고, 진짜 파일로 바꿀 자리를 표시해 줘.
- 마지막에 「확인 방법」(무엇을 보면 성공인지)과 「조절할 값」 목록을 짧게 정리해 줘.
- 답변과 코드 주석은 한국어로 해 줘.

## 원리
- 은하 중심 방향 · 기울기로 회전 행렬(galFrame)을 만든다. 하늘 방향 d 를 그 행렬로 돌리면 은하 위도 b · 경도 l 이 나온다.
- 띠 = exp(-b² / 폭²), 폭은 중심(l = 0)에서 넓게 (0.05 + 0.075·exp(-1.3 l²)). 팽대부는 중심 둘레 밝은 덩어리.
- 미리 구운 512² 잡음 세 크기로 별구름 · 낟알을 만든다 — 셰이더에서 잡음을 계산하지 않아 가볍다.
- 먼지 줄은 띠 가운데를 가르는 어두운 줄. 색마다 다르게 가린다: col *= exp(-dust × (1.9, 2.4, 3.2)) — 파랑이 더 먹혀 붉어진다.
- 별도 같은 회전 행렬로 은하면에 55% 를 몰아 뿌리고, 지평선에 대기광과 산 그림자를 둔다.

## 핵심 코드 — 은하 좌표 → 띠 · 팽대부 · 먼지 줄 (미리 구운 잡음)
(발췌: demos/demosSpace.ts MW_FS 를 정리)
```glsl
uniform sampler2D uN2;   // 미리 구운 2D 잡음 (R G B A 서로 다른 fbm)
uniform mat3 uGal;       // 세계 → 은하 좌표
uniform float uDust, uGlow, uAir;
varying vec3 vDir;
void main(){
  vec3 d = normalize(vDir);
  vec3 g = uGal * d;
  float b = asin(clamp(g.y, -1.0, 1.0));    // 은하 위도
  float l = atan(g.z, g.x);                 // 은하 경도 (0 = 중심)
  vec2 uv = vec2(l, b) / 6.2831853;
  vec4 n1 = texture(uN2, uv * 2.0), n2 = texture(uN2, uv * 5.0 + 0.37), n3 = texture(uN2, uv * 13.0 + 0.71);
  float l2 = l * l;
  float width = 0.05 + 0.075 * exp(-l2 * 1.3);
  float bb = b + (n1.r - 0.5) * 0.05;
  float band = exp(-bb * bb / (width * width));
  float bulge = exp(-(l2 * 4.5 + bb * bb * 45.0));
  float clouds = smoothstep(0.3, 0.85, n2.g * 0.55 + n1.r * 0.5); clouds *= clouds;
  float grain = 0.55 + 0.9 * n3.a * n3.a;
  float light = band * (0.08 + 1.1 * clouds) * grain + bulge * 1.5 * (0.6 + 0.7 * n2.g) * grain;
  vec3 col = light * mix(vec3(0.6, 0.66, 0.9), vec3(1.0, 0.78, 0.52), clamp(bulge * 1.6 + 0.15 * band, 0.0, 1.0));
  // 먼지 줄 — 파랑을 더 먹어 붉어진다
  float laneC = 0.008 * sin(l * 3.0) + (n1.g - 0.5) * 0.035;
  float laneW = 0.016 + 0.022 * exp(-l2 * 2.0);
  float lane = exp(-pow((b - laneC) / laneW, 2.0)) * smoothstep(2.4, 0.2, abs(l));
  float dust = (lane * smoothstep(0.35, 0.65, n2.r) * 1.6 + band * smoothstep(0.55, 0.8, n3.g) * 0.9) * uDust;
  col *= exp(-dust * vec3(1.9, 2.4, 3.2));
  col *= uGlow * 0.16;
  float alt = max(d.y, 0.0);                // 지평선 대기광
  col += uAir * (vec3(0.03, 0.07, 0.04) * exp(-alt * 9.0) + vec3(0.06, 0.035, 0.015) * exp(-alt * 30.0));
  gl_FragColor = vec4(col, 1.0);
  #include <tonemapping_fragment>
  #include <colorspace_fragment>
}
```

## 흔한 실수 · 확인 목록
- [ ] **잡음을 셰이더 반복문으로 계산하면 첫 컴파일이 오래 멈춘다** — 이 견본은 잡음을 미리 구운 텍스처로 읽어 피했다 (보물 동굴에서 겪은 멈춤).
- [ ] **먼지를 회색으로 곱하면 구멍 난 듯 보인다** — 색마다 다른 가림(파랑을 더)으로 붉어지게 해야 진짜 먼지 같다.
- [ ] **은하수를 화면 가로로 곧게 그리면 가짜 같다** — 은하 회전 행렬로 하늘에 비스듬히 걸리게, 별도 같은 행렬로 몰아 뿌린다.

## 완성 기준 체크리스트
- [ ] 하늘을 가로지르는 은하수 띠가 보이고, 가운데쯤 밝은 노란 팽대부가 있다
- [ ] 띠 한가운데를 어두운 먼지 줄이 가르고, 그 둘레는 붉은빛이 돈다
- [ ] 지평선 가까이 옅은 초록 · 주황 대기광과 산 그림자가 있다
- [ ] 「먼지 줄」 · 「별구름 밝기」 · 「대기광」 슬라이더가 바로 바뀐다

## 이 기술 정보
- id: `i523` · 분류: 그래픽 · 셰이더 › 우주 표현 · 3D · 난이도 보통 · 폰 부담 가벼움 (폰 OK) — 하늘 구 픽셀마다 잡음 텍스처 3번 읽기 + 식 몇 줄. 별 6만 점(그리기 1번). 폰도 가볍다.
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i523
- 쓰면 좋을 때: 맑은 밤하늘 배경 / 별 하늘(i522) 위에 함께 / 천체 · 은하 학습
- 쓰지 말 때: 도시 밤 장면 — 빛 공해로 은하수가 안 보이는 게 맞다 / 은하를 밖에서 보는 장면 — 대신 나선 은하(i525)

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### i523 견본 항목 — `src/demos/demosSpace.ts:1863`
```ts
  i523: {
    kind: '3d',
    caption: '은하수 띠 — 잡음 별구름 + 가운데를 가르는 먼지 줄(파랑을 더 먹어 붉게) + 밝은 팽대부, 지평선 대기광과 산 그림자',
    make(): Scene3D {
      const bin = new Bin();
      const scene = new THREE.Scene();
      const cam = new THREE.PerspectiveCamera(68, 1.6, 0.5, 1000);
      cam.rotation.order = 'YXZ';
      const frame = galFrame(0.42, 0.15, 1.05);
      const U = { uN2: { value: noise2D() }, uGal: { value: new THREE.Matrix3().setFromMatrix4(frame).transpose() }, uDust: { value: 1 }, uGlow: { value: 1 }, uAir: { value: 1 } };
      const dome = new THREE.Mesh(new THREE.SphereGeometry(450, 64, 32), new THREE.ShaderMaterial({ uniforms: U, vertexShader: DIR_VS, fragmentShader: MW_FS, side: THREE.BackSide, depthWrite: false }));
      dome.renderOrder = -2;
      scene.add(dome);
      const S = makeStars(60000, 400, 8, { magMax: 8.2, band: 0.55, bandW: 0.07, frame, tw: 0.5, gain: 1.2 });
      scene.add(S.pts);
      const hills = mountainRing(300, 4, 0.06, 0x010204);
      scene.add(hills);
      bin.scene(scene);
      return {
        scene,
        camera: cam,
        tone: THREE.ACESFilmicToneMapping,
        update(t) {
          S.U.uTime.value = t;
          cam.rotation.set(0.42, 0.12 + Math.sin(t * 0.06) * 0.42, 0);
        },
        resize: (_w, h) => (S.U.uScale.value = h / 900),
        controls: [
          range('먼지 줄 (가림)', 0, 2, 0.05, 1, (v) => (U.uDust.value = v)),
          range('별구름 밝기', 0.2, 2.5, 0.05, 1, (v) => (U.uGlow.value = v)),
          range('대기광', 0, 3, 0.05, 1, (v) => (U.uAir.value = v)),
          range('별 밝기', 0.2, 3, 0.05, 1, (v) => (S.U.uGain.value = v)),
        ],
        dispose: () => {
          bin.dispose();
          S.dispose();
        },
      };
    },
  }
```

## 관련 기술
- 먼저 알면 좋은 기술: [별 하늘 (실제 등급 · B-V 색 · 반짝임)](https://ai-techstudio.web.app/ai/t/i522.md) `i522`
- 다음에 해 볼 기술: [부피 성운 (레이마칭 · 별빛 산란)](https://ai-techstudio.web.app/ai/t/i524.md) `i524` · [오로라 (커튼 · 호수 반사)](https://ai-techstudio.web.app/ai/t/i533.md) `i533`
- 참고 문서: [Wikipedia — Galactic coordinate system](https://en.wikipedia.org/wiki/Galactic_coordinate_system)
