# AI 꾸러미 — 별 하늘 (실제 등급 · B-V 색 · 반짝임) — Starfield (magnitude distribution)
> 별 6만 개를 점 하나씩 — 어두운 별일수록 많은 등급 분포로 크기 · 밝기를, B-V 색지수 → 온도 → 흑체 색으로 색을, 별마다 다른 사인으로 반짝임을 준다.  
> 견본: https://ai-techstudio.web.app/#t/i522

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

## 주문서

### 만들어 줘: 별 하늘 (실제 등급 · B-V 색 · 반짝임) — Starfield (magnitude distribution)

#### 1. 목표
우주 게임 배경에 별 6만 개 하늘을 그려 줘 — 실제 등급 분포 · 온도 색 · 반짝임까지, 느낌은 진짜 밤하늘처럼.

#### 2. 핵심 기술 용어
- **Starfield (magnitude distribution)** — 실제 별 밝기 등급 분포를 흉내 낸 별 하늘
- **B-V color index → blackbody color** — 색지수로 표면 온도를 구해 별빛 색 정하기
- **Scintillation (twinkle)** — 공기 흔들림으로 별이 반짝이는 것
- **THREE.Points + custom ShaderMaterial** — 점 하나 = 별 하나, 크기 · 빛 모양은 셰이더로

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

#### 4. 조건
- 별은 THREE.Points 하나에 모두 — 속성 aMag(등급) · aCol(색) · aPh(위상)
- 재질은 AdditiveBlending · depthWrite false · toneMapped false, frustumCulled false
- 점 크기는 2.2 + 4.2 × √(흐름) 에 밝은 별 배율, 1.6px 보다 작으면 크기 대신 밝기를 줄인다
- 한계 등급(uLimit) · 반짝임(uTw) · 색 세기(uColK) · 밝기(uGain) 를 uniform 으로 조절
- 화면 높이에 맞춰 크기 배율(uScale = 높이 / 900)

#### 5. 완성 기준 (이게 보이면 성공)
- 어두운 작은 별이 아주 많고 밝은 별은 드물게 섞여 진짜 하늘처럼 보인다
- 별 색이 파랑 · 하양 · 노랑 · 주황 · 빨강으로 섞여 있다
- 밝은 별에만 십자 빛살이 있고, 별마다 다른 빠르기로 반짝인다
- 「한계 등급」을 낮추면 어두운 별부터 사라지고, 폰에서도 60fps

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

## 원리
- 실제 별은 어두운 것이 훨씬 많다. 등급을 log10(A + 무작위 × (B - A)) / 0.36 으로 뽑으면 그 분포가 된다 (-1.4 ~ 8.4 등급).
- 밝기(빛 흐름)는 10^(-0.4 × (등급 - 1)). 아주 작게 그려지는 별은 크기 대신 밝기로 줄여 깜빡임을 막는다.
- 색: 실제 분포를 흉내 낸 B-V 값을 뽑아 Ballesteros 식으로 온도(T = 4600 × (1/(0.92·BV+1.7) + 1/(0.92·BV+0.62)))를 구하고 흑체 색으로.
- 반짝임: 별마다 위상이 다른 사인 두 개를 곱해 밝기를 흔든다. 밝은 별(등급 1.4 아래)만 십자 빛살을 그린다.

## 핵심 코드 — 등급 · B-V 색 · 위상을 넣은 별 6만 개
(발췌: demos/demosSpace.ts makeStars() · bvRgb() · kelvinRgb() · STAR_VS 를 정리)
```ts
import * as THREE from 'three';

// B-V 색지수 → 온도 (Ballesteros) → 흑체 색 (kelvinRgb 는 견본의 온도 → RGB 근사)
const bvRgb = (bv: number) => kelvinRgb(4600 * (1 / (0.92 * bv + 1.7) + 1 / (0.92 * bv + 0.62)));
function sampleBV(r: () => number): number { // 뜨거운 파랑 조금 · 노랑 · 주황 많이 · 빨강 조금
  const u = r();
  if (u < 0.13) return -0.3 + r() * 0.35;
  if (u < 0.32) return 0.05 + r() * 0.3;
  if (u < 0.62) return 0.35 + r() * 0.45;
  if (u < 0.9) return 0.8 + r() * 0.6;
  return 1.4 + r() * 0.5;
}
const n = 62000, R = 400, mMin = -1.4, mMax = 8.4;
const A = Math.pow(10, 0.36 * mMin), B = Math.pow(10, 0.36 * mMax);
const pos = new Float32Array(n * 3), mag = new Float32Array(n), col = new Float32Array(n * 3), ph = new Float32Array(n);
for (let i = 0; i < n; i++) {
  const u = Math.random() * 2 - 1, a = Math.random() * Math.PI * 2, s = Math.sqrt(1 - u * u);
  pos.set([s * Math.cos(a) * R, u * R, s * Math.sin(a) * R], i * 3);
  mag[i] = Math.log10(A + Math.random() * (B - A)) / 0.36; // 어두운 별일수록 많다
  col.set(bvRgb(sampleBV(Math.random)), i * 3);
  ph[i] = Math.random();
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
geo.setAttribute('aMag', new THREE.BufferAttribute(mag, 1));
geo.setAttribute('aCol', new THREE.BufferAttribute(col, 3));
geo.setAttribute('aPh', new THREE.BufferAttribute(ph, 1));
// 정점 셰이더 핵심:
//   flux = pow(10.0, -0.4 * (aMag - 1.0));
//   tw = 1.0 + uTw * (0.3 * sin(uTime * (5.0 + aPh * 11.0) + aPh * 61.0) + 0.24 * sin(uTime * (13.0 + aPh * 7.0) + aPh * 23.0));
//   s = 2.2 + 4.2 * sqrt(min(flux, 6.0));  if (s < 1.6) { vB *= s / 1.6; s = 1.6; }
const mat = new THREE.ShaderMaterial({ uniforms: U, vertexShader: STAR_VS, fragmentShader: STAR_FS, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, toneMapped: false });
const stars = new THREE.Points(geo, mat);
stars.frustumCulled = false;
```

## 흔한 실수 · 확인 목록
- [ ] **등급을 고르게 뽑으면 밝은 별이 너무 많아 하늘이 하얗게 된다** — 어두운 별일수록 많게 log 분포로 뽑는다.
- [ ] **1px 보다 작은 점이 깜빡거린다** — 크기를 1.6px 아래로 줄이지 말고, 그만큼 밝기를 줄인다.
- [ ] **톤 매핑에 걸리면 별빛 색이 바랜다** — 별 재질은 toneMapped false, 밝기는 uGain 으로.

## 완성 기준 체크리스트
- [ ] 어두운 작은 별이 아주 많고 밝은 별은 드물게 섞여 진짜 하늘처럼 보인다
- [ ] 별 색이 파랑 · 하양 · 노랑 · 주황 · 빨강으로 섞여 있다
- [ ] 밝은 별에만 십자 빛살이 있고, 별마다 다른 빠르기로 반짝인다
- [ ] 「한계 등급」을 낮추면 어두운 별부터 사라지고, 폰에서도 60fps

## 이 기술 정보
- id: `i522` · 분류: 그래픽 · 셰이더 › 우주 표현 · 3D · 난이도 보통 · 폰 부담 가벼움 (폰 OK) — 점 6만 2천 개를 한 번에 그리기(그리기 호출 1). 점 크기는 대부분 1.6px 이라 픽셀 부담이 작다. 만들 때 계산은 한 번.
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/i522
- 쓰면 좋을 때: 우주 · 밤하늘 배경 (다른 우주 견본 모두의 바탕) / 별자리 · 천체 관측 체험 / 타이틀 화면 배경
- 쓰지 말 때: 2D 화면 — 대신 미리 그린 별 층(i535) / 별이 수십 개면 충분한 장면 — 스프라이트 몇 개가 낫다

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### i522 견본 항목 — `src/demos/demosSpace.ts:1832`
```ts
  i522: {
    kind: '3d',
    caption: '별 6만 개 — 어두운 별일수록 많은 실제 등급 분포, 색은 B-V 색지수 → 표면 온도 → 흑체 색, 대기 때문에 반짝인다',
    make(): Scene3D {
      const scene = new THREE.Scene();
      scene.background = new THREE.Color(0x000000);
      const cam = new THREE.PerspectiveCamera(62, 1.6, 0.1, 1000);
      cam.rotation.order = 'YXZ';
      const S = makeStars(62000, 400, 3, { magMax: 8.4, band: 0.42, bandW: 0.16, frame: new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0.9, 0.2, 0.5)), tw: 0.7 });
      S.U.uLimit.value = 7.6;
      scene.add(S.pts);
      return {
        scene,
        camera: cam,
        update(t) {
          S.U.uTime.value = t;
          cam.rotation.set(0.3 + 0.08 * Math.sin(t * 0.05), t * 0.02, 0);
        },
        resize: (_w, h) => (S.U.uScale.value = h / 900),
        controls: [
          range('한계 등급 (클수록 어두운 별까지)', 2, 8.4, 0.1, 7.6, (v) => (S.U.uLimit.value = v)),
          range('반짝임 (대기 흔들림)', 0, 2, 0.05, 0.7, (v) => (S.U.uTw.value = v)),
          range('색 (B-V 온도색)', 0, 1.5, 0.05, 1, (v) => (S.U.uColK.value = v)),
          range('밝기', 0.3, 3, 0.05, 1, (v) => (S.U.uGain.value = v)),
        ],
        dispose: () => S.dispose(),
      };
    },
  }
```

## 관련 기술
- 먼저 알면 좋은 기술: [인스턴싱 (InstancedMesh)](https://ai-techstudio.web.app/ai/t/u36.md) `u36`
- 다음에 해 볼 기술: [은하수 띠 (먼지 줄 · 밝은 팽대부)](https://ai-techstudio.web.app/ai/t/i523.md) `i523` · [시차 별층 (패럴랙스 스크롤 · 성간 먼지)](https://ai-techstudio.web.app/ai/t/i535.md) `i535`
- 참고 문서: [Wikipedia — Color index (B-V)](https://en.wikipedia.org/wiki/Color_index) · [Wikipedia — Apparent magnitude](https://en.wikipedia.org/wiki/Apparent_magnitude)
