# AI 꾸러미 — 쉰(sheen)
> 벨벳 · 피부 · 과일 껍질의 부드러운 결 빛  
> 견본: https://ai-techstudio.web.app/#t/u05

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

## 주문서

### 만들어 줘: 쉰(sheen)

#### 1. 목표
3D 장면의 물체에 「쉰(sheen)」 기술을 깔끔한 기본 느낌으로 넣어 줘. — 벨벳 · 피부 · 과일 껍질의 부드러운 결 빛

#### 2. 핵심 기술 용어
- **sheen**

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

#### 4. 조건
- 한 프레임 16ms 안 (폰 포함) — 무거운 계산은 처음에 한 번 또는 여러 프레임에 나눠서
- 새 재질 · 셰이더는 첫 화면 전에 미리 컴파일 (renderer.compileAsync)
- 화면 가장자리 흐림 · 비네트로 얼버무리지 않기
- 외부 라이브러리 추가 없이 three.js 와 three/examples 만

#### 5. 완성 기준 (이게 보이면 성공)
- 견본처럼 「벨벳 · 피부 · 과일 껍질의 부드러운 결 빛」 이(가) 첫 화면에서 바로 보인다
- 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

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

## 원리
- 벨벳 · 피부 · 과일 껍질의 부드러운 결 빛


## 흔한 실수 · 확인 목록
- (아직 정리된 것이 없어요)

## 완성 기준 체크리스트
- [ ] 견본처럼 「벨벳 · 피부 · 과일 껍질의 부드러운 결 빛」 이(가) 첫 화면에서 바로 보인다
- [ ] 켬/끔(또는 전/후) 비교 단추로 이 기술이 만드는 차이를 확인할 수 있다
- [ ] 중요한 값 1~3개를 슬라이더로 바꿔 볼 수 있다
- [ ] 콘솔 오류 0, 폰 폭(390px)에서도 동작한다

## 이 기술 정보
- id: `u05` · 분류: 그래픽 · 셰이더 › 재질 · 그림체 · 3D · 난이도 보통 · 폰 부담 보통 (폰 주의)
- 라이브 견본 (브라우저에서 직접 조작): https://ai-techstudio.web.app/#t/u05

## 견본 실제 코드 (라이브 견본이 돌리는 코드 — three.js · TypeScript)
### u05 견본 항목 — `src/demos/demosMaterial.ts:468`
```ts
  u05: {
    kind: '3d',
    caption: '왼쪽 일반 · 오른쪽 쉰 — 벨벳 쿠션 · 복숭아 가장자리에 보송한 결 빛',
    make() {
      const B = base({ bg: gradBg('#1a1430', '#3a2850'), labels: ['일반 재질', '+ 쉰(sheen)'], fit: [2.5, 0.9], target: [0, 0.05, 0], dir: [0, 0.45, 1], env: studioEnv() });
      B.scene.add(new THREE.HemisphereLight(0xffffff, 0x302040, 0.4));
      const key = new THREE.DirectionalLight(0xffffff, 1.2);
      key.position.set(2, 3, 4);
      const back = new THREE.DirectionalLight(0xffe8f4, 6.0);
      back.position.set(-2, 2, -5);
      B.scene.add(key, back);
      const mk = (color: number, sheen: number, sc: number): THREE.MeshPhysicalMaterial => new THREE.MeshPhysicalMaterial({ color, roughness: 0.85, sheen, sheenColor: new THREE.Color(sc), sheenRoughness: 0.35 });
      const mats = [mk(0x4a0c2c, 0, 0xff9ad0), mk(0x4a0c2c, 1, 0xff9ad0), mk(0xd0603a, 0, 0xfff0e0), mk(0xd0603a, 1, 0xfff0e0), mk(0x2a6a2a, 0, 0xd8ffc0), mk(0x2a6a2a, 1, 0xd8ffc0)];
      const cg = new RoundedBoxGeometry(1.5, 0.5, 1.2, 6, 0.24);
      const sg = new THREE.SphereGeometry(0.42, 48, 32);
      const lg = new THREE.SphereGeometry(0.16, 24, 16);
      const set = (i: number): THREE.Group => {
        const g = new THREE.Group();
        const cushion = new THREE.Mesh(cg, mats[i]!);
        cushion.position.y = -0.45;
        const peach = new THREE.Mesh(sg, mats[2 + i]!);
        peach.position.set(0, 0.17, 0.05);
        peach.scale.set(1, 0.95, 1);
        const leaf = new THREE.Mesh(lg, mats[4 + i]!);
        leaf.scale.set(1.4, 0.35, 0.7);
        leaf.position.set(0.12, 0.6, 0);
        leaf.rotation.z = -0.4;
        g.add(cushion, peach, leaf);
        return g;
      };
      const L = set(0);
      const R = set(1);
      L.position.x = -1.2;
      R.position.x = 1.2;
      B.scene.add(L, R);
      return B.done({
        update(t) {
          for (const g of [L, R]) g.rotation.y = t * 0.5;
          back.position.set(Math.sin(t * 0.5) * 3, 2, -5);
        },
        controls: [
          { type: 'range', label: '쉰 세기', min: 0, max: 1, step: 0.05, value: 1, on: (v) => { for (let k = 1; k < 6; k += 2) mats[k]!.sheen = v; } },
          { type: 'range', label: '쉰 거칠기', min: 0, max: 1, step: 0.05, value: 0.35, on: (v) => { for (let k = 1; k < 6; k += 2) mats[k]!.sheenRoughness = v; } },
        ],
      });
    },
  }
```

## 관련 기술
- 이 효과를 만든 기술: [PBR + 클리어코트](https://ai-techstudio.web.app/ai/t/u04.md) `u04`
