히어로 3D 를 파이프라인 게이트로 — 형태에 이유를 준다
'3D 라고 그냥 큐브만 돌면 안 된다'는 지적에서 시작해 조사부터 했다.
research/three/06~08 에 사례 32건, GLB 소스 17곳, 방향 제안 3개가 있다.
조사 결론: 추상이다. GLB 는 쓰지 않는다.
- 구체 오브젝트의 성립 조건 3개(물리적으로 존재 / 각도가 정보 /
오브젝트가 하나로 정해짐)를 우리 브리프는 하나도 충족하지 않는다.
규칙 묶음은 돌려봐도 새 정보가 없다
- 실측: DamagedHelmet.glb 3,685KB 중 텍스처가 87%라 Draco 를 걸어도 -13%.
게다가 디코더만 200KB 로 예산의 67%를 먹는다
- Stripe Globe 는 모델 파일이 0바이트다. 6만 점을 코드로 생성한다.
지오메트리가 파일이 아니라 코드일 때 추상이 이긴다
- 2026 Awwwards Developer Award 수상작 중에는 3D 를 아예 안 쓴 것도 있다
채택안: 파이프라인 게이트
- 유리 판 여섯 장이 깊이 축에 정면으로 선다. 이 스킬의 여섯 단계다
- 스크롤하면 카메라가 문을 하나씩 통과하고 지나온 판은 뒤에 겹친다
- 판이 여섯인 이유가 생겼다. 아래 Pipeline 섹션과 같은 수다
- 순서는 이 제품의 본체다('순서를 바꾸지 마라'가 절대 규칙). 순서를
깊이로 옮기는 것은 은유가 아니라 직역이다
스크롤 연동은 이미 도는 렌더 루프에서 위치를 한 번 읽는다.
addEventListener('scroll') 을 쓰지 않는다(하드 게이트 10). 핀도 없다.
This commit is contained in:
parent
ed1f335a89
commit
17d04ee2bb
4 changed files with 572 additions and 18 deletions
|
|
@ -1,7 +1,15 @@
|
||||||
/**
|
/**
|
||||||
* 히어로 3D 씬.
|
* 히어로 3D 씬 — 파이프라인 게이트.
|
||||||
*
|
*
|
||||||
* 유리 판 몇 장이 공간에 떠 있고, 그 뒤의 빛을 실제로 굴절시킨다.
|
* 유리 판 여섯 장이 깊이 축에 차례로 서 있다. 이 스킬의 여섯 단계다.
|
||||||
|
* 스크롤하면 카메라가 판을 하나씩 통과하고, 지나온 판은 뒤에 남아 겹친다.
|
||||||
|
*
|
||||||
|
* 형태에 이유가 있다. 이 제품이 파는 것은 순서이고("순서를 바꾸지 마라"가
|
||||||
|
* 절대 규칙이다), 순서를 깊이로 옮기는 것은 은유가 아니라 직역이다.
|
||||||
|
* 판이 여섯인 이유도 여기서 나온다. 아래 Pipeline 섹션의 여섯 단계와 같은 수다.
|
||||||
|
*
|
||||||
|
* 조사 결론(research/three/08-proposal.md): 우리가 파는 것은 무형이라
|
||||||
|
* 구체 오브젝트가 성립하지 않는다. GLB 는 쓰지 않는다. 지오메트리는 코드다.
|
||||||
*
|
*
|
||||||
* 구현 방식과 그 이유:
|
* 구현 방식과 그 이유:
|
||||||
* 배경 그라디언트를 렌더 타겟에 한 번 그리고, 유리 판이 그 텍스처를
|
* 배경 그라디언트를 렌더 타겟에 한 번 그리고, 유리 판이 그 텍스처를
|
||||||
|
|
@ -96,6 +104,7 @@ async function boot({ canvas, accentVar, glowVar }: HeroSceneOptions): Promise<v
|
||||||
uTime: { value: 0 },
|
uTime: { value: 0 },
|
||||||
uAspect: { value: 1 },
|
uAspect: { value: 1 },
|
||||||
uPointer: { value: new THREE.Vector2(0.5, 0.5) },
|
uPointer: { value: new THREE.Vector2(0.5, 0.5) },
|
||||||
|
uProgress: { value: 0 },
|
||||||
uA: { value: accent },
|
uA: { value: accent },
|
||||||
uB: { value: glow },
|
uB: { value: glow },
|
||||||
};
|
};
|
||||||
|
|
@ -113,7 +122,8 @@ async function boot({ canvas, accentVar, glowVar }: HeroSceneOptions): Promise<v
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
uniform float uTime; uniform float uAspect;
|
uniform float uTime; uniform float uAspect;
|
||||||
uniform vec2 uPointer; uniform vec3 uA; uniform vec3 uB;
|
uniform vec2 uPointer; uniform float uProgress;
|
||||||
|
uniform vec3 uA; uniform vec3 uB;
|
||||||
${NOISE}
|
${NOISE}
|
||||||
void main() {
|
void main() {
|
||||||
vec2 p = vUv; p.x *= uAspect;
|
vec2 p = vUv; p.x *= uAspect;
|
||||||
|
|
@ -123,8 +133,9 @@ async function boot({ canvas, accentVar, glowVar }: HeroSceneOptions): Promise<v
|
||||||
float f1 = fbm(p * 1.8 + vec2(t, -t * 0.6));
|
float f1 = fbm(p * 1.8 + vec2(t, -t * 0.6));
|
||||||
float f2 = fbm(p * 2.3 - vec2(t * 0.8, t * 1.1) + f1 * 0.55);
|
float f2 = fbm(p * 2.3 - vec2(t * 0.8, t * 1.1) + f1 * 0.55);
|
||||||
|
|
||||||
vec2 c1 = mix(vec2(0.30 * uAspect, 0.44), m, 0.20);
|
// 통과할수록 빛이 뒤로 물러난다
|
||||||
vec2 c2 = vec2(0.82 * uAspect, 0.28);
|
vec2 c1 = mix(vec2(0.30 * uAspect, 0.44), m, 0.20) + vec2(0.0, uProgress * 0.18);
|
||||||
|
vec2 c2 = vec2(0.82 * uAspect, 0.28) - vec2(uProgress * 0.12, 0.0);
|
||||||
|
|
||||||
float a = smoothstep(0.72, 0.08, distance(p, c1) + f1 * 0.36);
|
float a = smoothstep(0.72, 0.08, distance(p, c1) + f1 * 0.36);
|
||||||
float b = smoothstep(0.66, 0.06, distance(p, c2) + f2 * 0.32);
|
float b = smoothstep(0.66, 0.06, distance(p, c2) + f2 * 0.32);
|
||||||
|
|
@ -193,14 +204,18 @@ async function boot({ canvas, accentVar, glowVar }: HeroSceneOptions): Promise<v
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 판은 깊이 축에 정면으로 선다. 떠다니는 사각형이 아니라 통과하는 문이다.
|
||||||
|
const GATE_GAP = 5.2;
|
||||||
const panes: import("three").Mesh[] = [];
|
const panes: import("three").Mesh[] = [];
|
||||||
const geo = new THREE.PlaneGeometry(3.1, 4.3, 1, 1);
|
const geo = new THREE.PlaneGeometry(7.4, 5.2, 1, 1);
|
||||||
for (let i = 0; i < PANE_COUNT; i++) {
|
for (let i = 0; i < PANE_COUNT; i++) {
|
||||||
const m = new THREE.Mesh(geo, paneMat);
|
const m = new THREE.Mesh(geo, paneMat);
|
||||||
const a = (i / PANE_COUNT) * Math.PI * 2;
|
// 좌우로 조금씩 어긋나게 세워 통과할 때 옆면이 스친다
|
||||||
m.position.set(Math.cos(a) * 3.6, Math.sin(a * 1.7) * 1.5, -i * 0.9);
|
const sway = (i % 2 === 0 ? 1 : -1) * (0.5 + i * 0.12);
|
||||||
m.rotation.set(0.1 + i * 0.05, a * 0.5, a * 0.22);
|
m.position.set(sway, Math.sin(i * 1.3) * 0.32, -i * GATE_GAP);
|
||||||
m.userData["seed"] = a;
|
m.rotation.set(0, sway * 0.05, sway * 0.018);
|
||||||
|
m.userData["index"] = i;
|
||||||
|
m.userData["sway"] = sway;
|
||||||
scene.add(m);
|
scene.add(m);
|
||||||
panes.push(m);
|
panes.push(m);
|
||||||
}
|
}
|
||||||
|
|
@ -246,20 +261,36 @@ async function boot({ canvas, accentVar, glowVar }: HeroSceneOptions): Promise<v
|
||||||
const t = ms * 0.001;
|
const t = ms * 0.001;
|
||||||
|
|
||||||
pointer.lerp(pointerTarget, 0.04);
|
pointer.lerp(pointerTarget, 0.04);
|
||||||
|
|
||||||
|
const rect0 = canvas.getBoundingClientRect();
|
||||||
|
const progress0 = Math.min(Math.max(-rect0.top / (rect0.height * 1.6), 0), 1);
|
||||||
|
bgUniforms.uProgress.value = progress0;
|
||||||
bgUniforms.uTime.value = t;
|
bgUniforms.uTime.value = t;
|
||||||
bgUniforms.uPointer.value.copy(pointer);
|
bgUniforms.uPointer.value.copy(pointer);
|
||||||
paneMat.uniforms["uTime"]!.value = t;
|
paneMat.uniforms["uTime"]!.value = t;
|
||||||
|
|
||||||
|
// 스크롤 진행도. addEventListener("scroll") 을 쓰지 않는다(하드 게이트 10).
|
||||||
|
// 이미 도는 렌더 루프 안에서 위치를 한 번 읽을 뿐이라 추가 비용이 없다.
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const travel = rect.height * 1.6;
|
||||||
|
const progress = Math.min(Math.max(-rect.top / travel, 0), 1);
|
||||||
|
|
||||||
|
// 카메라가 여섯 개의 문을 차례로 통과한다
|
||||||
|
const depth = (PANE_COUNT - 1) * GATE_GAP;
|
||||||
|
camera.position.z = 8.5 - progress * (depth + 6);
|
||||||
|
camera.position.x = (pointer.x - 0.5) * 1.1;
|
||||||
|
camera.position.y = (pointer.y - 0.5) * -0.5;
|
||||||
|
camera.lookAt(camera.position.x * 0.3, 0, camera.position.z - 10);
|
||||||
|
|
||||||
for (const m of panes) {
|
for (const m of panes) {
|
||||||
const seed = m.userData["seed"] as number;
|
const i = m.userData["index"] as number;
|
||||||
m.rotation.y += 0.0016;
|
const sway = m.userData["sway"] as number;
|
||||||
m.rotation.z = Math.sin(t * 0.18 + seed) * 0.24;
|
// 통과 직전의 판만 미세하게 숨쉰다. 전부 흔들면 다시 부유물이 된다
|
||||||
m.position.y = Math.sin(t * 0.22 + seed * 1.7) * 1.6;
|
const dist = Math.abs(m.position.z - camera.position.z);
|
||||||
// 포인터가 판을 아주 조금 기울인다
|
const near = Math.max(0, 1 - dist / (GATE_GAP * 1.6));
|
||||||
m.rotation.x = 0.1 + (pointer.y - 0.5) * 0.24;
|
m.rotation.z = sway * 0.018 + Math.sin(t * 0.3 + i) * 0.012 * near;
|
||||||
|
m.position.y = Math.sin(i * 1.3) * 0.32 + Math.sin(t * 0.24 + i) * 0.06;
|
||||||
}
|
}
|
||||||
camera.position.x = (pointer.x - 0.5) * 1.5;
|
|
||||||
camera.lookAt(0, 0, 0);
|
|
||||||
|
|
||||||
// 배경을 먼저 타겟에 굽고, 그 텍스처를 유리가 읽는다
|
// 배경을 먼저 타겟에 굽고, 그 텍스처를 유리가 읽는다
|
||||||
renderer.setRenderTarget(rt);
|
renderer.setRenderTarget(rt);
|
||||||
|
|
|
||||||
127
research/three/06-webgl-showcase.md
Normal file
127
research/three/06-webgl-showcase.md
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
# 06 — 웹 3D 사례집: 형태가 의미를 나르는 경우
|
||||||
|
|
||||||
|
> 조사일 2026-08-20. 판정 기준 하나: **3D를 끄면 무엇을 잃는가.** 아무것도 안 잃으면 장식이다.
|
||||||
|
> 무게는 가능한 곳에서 실측했고, 아닌 것은 `추정`으로 표시했다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 먼저 — 3D 없이 이긴 사례들
|
||||||
|
|
||||||
|
사례집을 3D 사례로 시작하면 편향된다. 2026 Awwwards Developer Award 수상작 중 **3D를 전혀 쓰지 않은 것들**이 있다.
|
||||||
|
|
||||||
|
| 사이트 | 무엇으로 이겼나 |
|
||||||
|
|---|---|
|
||||||
|
| **By-Kin** https://by-kin.com | GSAP + 스무스 스크롤. "전환이 스스로를 드러내지 않는데 전체가 하나의 연속된 표면으로 느껴진다" |
|
||||||
|
| **Mat Voyce** https://matvoyce.tv | 키네틱 타이포만. 작가가 키네틱 타입 아티스트라 **사이트가 그 작업처럼 움직여야** 했다 |
|
||||||
|
| **Uncommon Studio** https://uncommonstudio.com.au | GSAP 전환을 카메라 무브처럼. 그리드 규율 |
|
||||||
|
|
||||||
|
**교훈**: "3D가 있어야 상을 받는다"는 전제가 틀렸다. 형태와 메시지가 맞을 때만 3D가 이긴다.
|
||||||
|
Mat Voyce의 논리 — *"작업이 움직임이니 사이트도 움직여야 한다"* — 가 이 문서 전체의 판정식이다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 구체 오브젝트 (제품·기계·캐릭터)
|
||||||
|
|
||||||
|
파는 물건이 **물리적으로 존재할 때**의 문법이다.
|
||||||
|
|
||||||
|
| # | 사례 | 3D가 무엇 | 왜 그 형태여야 했나 | 지오/모델 | 무게·전략 | 훔칠 것 |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| 1 | **Oryzo** oryzo.ai (Lusion, Awwwards SOTM 2026-04) | 코르크 코스터 하나 | 제품이 하나뿐이라 **하나만 제대로** 보여준다. 여러 각도 = 구매 확신 | GLB | 추정 1~3MB | **관성.** 물리를 흉내낸 이징 + 스크롤이 2D 레이어가 아니라 **진짜 Z축**으로 카메라를 민다 |
|
||||||
|
| 2 | **Cartier Watches & Wonders** cartier.com/watchesandwonders | 여섯 개의 독립된 3D 방(alcove) | 시계 여섯 종을 각각의 세계에 둔다. 그리드에 늘어놓으면 럭셔리가 죽는다 | GLB | 추정 수 MB/방, 방 단위 지연 로드 | **씬을 쪼개서 필요할 때만 로드.** GLSL+GSAP+Lenis+Web Audio를 서사 레이어로 |
|
||||||
|
| 3 | **Lacoste Ace Breaker** members-play.lacoste.com/ace-breaker-rg | 플레이 가능한 벽돌깨기 | 캠페인이 "참여"라서 **볼 것이 아니라 할 것**이어야 했다 | 프로시저럴 | — | 게임 메커닉 하나를 브랜드 월드로 감싼다. 기능이 곧 메시지 |
|
||||||
|
| 4 | **Porsche: Dream Machine** (Lusion) | 차 | 차는 형태 자체가 상품이다 | GLB | — | 3D 일러스트+모션그래픽을 웹이 아니라 **영상으로** 뺐다. 웹에 다 넣지 않는 선택 |
|
||||||
|
| 5 | **Bruno Simon portfolio** bruno-simon.com | 운전 가능한 차 + 물리 월드 | 포트폴리오 주인이 **three.js 교사**다. 사이트가 곧 실력 증명 | GLB + Cannon.js | 무겁다(수 MB). 로딩 화면 명시 | 어려운 아이디어 **하나**를 끝까지 판다. 여러 효과를 쌓지 않는다 |
|
||||||
|
| 6 | **Adventure Time: BMO** (Active Theory) | 캐릭터 | IP 자체가 캐릭터다 | GLB, 리깅 | — | 캐릭터는 **표정과 반응**이 전부. 폴리곤 수가 아니다 |
|
||||||
|
| 7 | **NASA JPL Open Source Rover** (Active Theory) | 실제 로버 | 오픈소스 하드웨어를 **분해해서 보여주는 것**이 목적 | GLB, 부품 분리 | — | 폭발도(exploded view). 조립 순서가 정보다 |
|
||||||
|
| 8 | **KODE Sports Club** (Merci Michel, SOTM) | 건축 공간 | 아직 안 지어진 건물을 판다 | GLB | — | 미완성 실물의 대체재로서의 3D — **가장 정당한 용도** |
|
||||||
|
| 9 | **Hubtown** hubtown.co.in | 풍경 위 빛나는 모놀리스 | 부동산. 랜드마크성이 상품 | 혼합 | — | 마우스로 **가려진 디테일을 벗겨내는** 인터랙션 |
|
||||||
|
| 10 | **Gunner Kennels** (AR 컨피규레이터) | 켄넬 실물 크기 | 개 옆에 놓아봐야 **사이즈 확신**이 생긴다 | GLB | — | **반품률 5% 감소**. 3D의 ROI가 숫자로 나온 드문 사례 |
|
||||||
|
| 11 | **Lundia** 모듈 선반 컨피규레이터 | 조립 결과물 | 조합 수가 많아 사진으로 불가능 | 프로시저럴 조립 | — | 실시간 가격 연동. 3D가 **견적서**가 된다 |
|
||||||
|
| 12 | **Explore Primland** explore.ownprimland.com | 블루리지 산맥 지형 | 리조트가 파는 것이 **입지**다 | 하이트맵(프로시저럴) | — | 스크롤=카메라 활공 + 대기 안개. 지형은 모델이 아니라 하이트맵으로 |
|
||||||
|
| 13 | **DeepSee Commerce** deepseecommerce.com | 빙산을 따라 하강 | "수면 아래를 본다"는 은유가 제품명 그 자체 | 혼합 | DPR 클램프, 에셋 압축, **보일 때만 렌더** | 은유가 **제품 이름과 일치**할 때만 은유를 쓴다 |
|
||||||
|
|
||||||
|
### 구체 오브젝트의 공통 조건
|
||||||
|
1. **파는 것이 물리적으로 존재한다** (또는 아직 존재하지 않아 대체가 필요하다)
|
||||||
|
2. **각도가 정보다** — 돌려봐야 알 수 있는 것이 있다
|
||||||
|
3. **오브젝트는 하나** — Oryzo는 코스터 하나, Bruno Simon은 차 하나. 여러 개를 띄운 사례는 상위권에 없다
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 추상 (데이터·재질·현상)
|
||||||
|
|
||||||
|
파는 것이 **무형일 때**의 문법이다. designpaca가 여기 속한다.
|
||||||
|
|
||||||
|
| # | 사례 | 3D가 무엇 | 왜 그 형태여야 했나 | 지오/모델 | 무게·전략 | 훔칠 것 |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| 14 | **Stripe Globe** stripe.com/blog/globe | 지구본 위 결제 흐름 | 평면 지도 대비 **화면 면적 20% 미만**으로 같은 정보. 국가 크기 왜곡도 적다 | **완전 프로시저럴** — 해바라기 나선으로 6만 점 생성, 실제 그리는 건 2만 점 | 모델 파일 **0바이트**. 국가 마스킹은 색으로 인코딩한 PNG 1장 | **이 문서 최고의 참조.** 점 하나하나가 데이터고, 지오메트리가 파일이 아니라 **코드**다. `antialias: false`가 전 기기에서 성능을 살렸다 |
|
||||||
|
| 15 | **Stripe Connect** stripe.com/blog/connect-front-end-experience | 결제 연결 구조 | 추상적 인프라를 공간으로 | 프로시저럴 | — | 무형 제품의 3D는 **구조도**여야 한다 |
|
||||||
|
| 16 | **fromanother** (FWA 수상) | 아이덴티티가 유리처럼 굴절·변형 | 스튜디오가 파는 것이 **아이덴티티 시스템**이다. 시스템이 변형되는 걸 보여준다 | 커스텀 GLSL | 모델 없음 | **"로고를 3D로"가 아니라 "정체성이 굴절한다"** — 우리와 가장 가까운 사례 |
|
||||||
|
| 17 | **Iventions** iventions.com | 프로젝트마다 스포트라이트 설치물 | 공간·이벤트를 만드는 회사라 사이트가 **공간처럼 느껴져야** 했다 | 조명 중심 | Next.js + Three + GSAP | **볼거리가 아니라 분위기로** WebGL을 쓴 가장 명확한 사례. 조명이 주인공 |
|
||||||
|
| 18 | **IVRESS** brand.ivress.co.jp | 브랜드 월드 | — | 프로시저럴 | **WebGPURenderer + WebGL 폴백**, TSL로 양쪽 컴파일 | 2026년 WebGPU 프로덕션 투입의 실사례 |
|
||||||
|
| 19 | **The Harmonic State** (Active Theory, SOTM) | 소리의 시각화 | 오디오가 주제라 **소리에 반응하는 형태**가 필수 | 프로시저럴 파티클 | — | 오디오 리액티브는 데이터 바인딩의 한 종류다 |
|
||||||
|
| 20 | **Spotify: Listening Together** (Active Theory) | 동시 청취 데이터 | 실제 재생 데이터가 형태를 만든다 | 프로시저럴 | — | **데이터가 없으면 형태가 없다** — 형태의 정당성이 데이터에서 나온다 |
|
||||||
|
| 21 | **Thorne: The Frontier Within** (Active Theory) | 체내 생물학 | 눈에 안 보이는 것을 보게 한다 | 혼합 | — | 비가시적 대상의 시각화 = 추상 3D의 정당한 자리 |
|
||||||
|
| 22 | **Worldcoin Globe** (Lusion) | 전 세계 분포 | Stripe Globe와 같은 논리 | 프로시저럴 | API 연동 | 지구본은 **분포 데이터가 있을 때만** 쓴다 |
|
||||||
|
| 23 | **Lusion Labs** lusion.co/projects/lusion_labs | 실험 모음 | 스튜디오의 R&D 자체가 상품 | 프로시저럴 | — | 기술 실험을 콘텐츠로 만드는 구조 |
|
||||||
|
| 24 | **Aave** aave.com | 금융 프로토콜 | 무형 금융의 추상 표현 | 추상 | — | 다크+연보라. **우리는 이 팔레트를 피한다**(슬롭 지문) |
|
||||||
|
| 25 | **Audion / Tavus / SISA / Tracebit** | AI·보안 SaaS의 추상 형태 | — | 추상 | — | 대부분 **의미 없는 추상**의 사례. 반면교사로 본다 |
|
||||||
|
| 26 | **Shopify Editions** shopify.com/editions/spring2026 | 기능 체인지로그를 제품 서사로 | 릴리스 노트를 읽게 만들어야 했다 | 씬 분할 | 섹션마다 자체 무대 | **스크롤 시퀀스 = 각 섹션이 독립된 무대.** 하나의 긴 씬이 아니다 |
|
||||||
|
| 27 | **Bellwoods / Audiograph** (Matt DesLauriers) | 제너러티브 지형/음악 | 작품 자체가 코드다 | 100% 프로시저럴 | 극도로 가벼움 | 모델 없이 **시드 하나**로 세계를 만든다 |
|
||||||
|
| 28 | **Mapbox 바람 지도** | 풍속 데이터 | 벡터장은 정지 이미지로 못 읽는다 | GPGPU 파티클 | 데이터 텍스처 | **움직임이 정보인** 유일한 정당화 |
|
||||||
|
| 29 | **IBM Technology Garden** (Variable) | 기술을 정원으로 | 은유가 브랜드 서사와 붙어 있다 | 프로시저럴 생성 | — | 은유형 추상. 성공 조건은 **은유가 카피에도 있는 것** |
|
||||||
|
| 30 | **Minh Pham** minhpham.design | 작업물의 틀 | 3D가 작업을 **압도하지 않도록** 설계 | 혼합 | — | 3D를 **액자**로 쓴다. 주인공이 되지 않는다 |
|
||||||
|
| 31 | **Sleep Well Creative** sleep-well-creatives.com | 손그림+3D 혼합 | 수면 안내라 부드러움이 요구됐다 | 혼합 | — | 3D에 손그림을 섞어 "CG 티"를 지운다 |
|
||||||
|
| 32 | **Taotajima.jp** (Akella 해부) | 이미지 전환 | — | 셰이더만 | 모델 0 | 셰이더 전환은 **모델 없이** 인상을 만드는 가장 싼 길 |
|
||||||
|
|
||||||
|
### 추상이 성공하는 조건 — 셋 다 만족해야 한다
|
||||||
|
1. **형태가 데이터에 묶여 있다.** Stripe의 점은 국가고, Spotify의 파티클은 재생이고, Mapbox의 흐름은 풍속이다. 데이터가 없으면 형태의 근거가 없다
|
||||||
|
2. **은유가 카피에도 존재한다.** DeepSee는 제품명이 "수면 아래"고, fromanother는 "굴절하는 정체성"을 문장으로도 말한다
|
||||||
|
3. **3D를 끄면 정보를 잃는다.** 분위기만 잃으면 그건 장식이다
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 우리가 지금 실패한 이유 (진단)
|
||||||
|
|
||||||
|
현재 히어로: 유리 판 6장이 렌더타겟에 구운 배경 그라디언트를 UV를 밀어 샘플링한다.
|
||||||
|
|
||||||
|
| 성공 조건 | 현재 |
|
||||||
|
|---|---|
|
||||||
|
| 형태가 데이터에 묶여 있다 | ❌ **판이 6장인 이유가 없다.** 5장이나 8장이어도 똑같다 |
|
||||||
|
| 은유가 카피에 존재한다 | ❌ 페이지 어디에도 "유리"가 없다 |
|
||||||
|
| 3D를 끄면 정보를 잃는다 | ❌ CSS 그라디언트로 대체되고 아무도 모른다 |
|
||||||
|
|
||||||
|
기술은 옳았다(렌더타겟 1장으로 transmission 비용 회피, 예산 준수, 폴백 완비). **형태 선택만 틀렸다.**
|
||||||
|
사용자 평 "3D라고 그냥 큐브만 돌면 안 됩니다"는 정확히 이 진단이다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 기법 카탈로그 — 무게 순
|
||||||
|
|
||||||
|
| 기법 | 추가 비용 | 언제 정당한가 | 출처 사례 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **프로시저럴 점/선 지오메트리** | **0KB** | 데이터가 점의 위치를 정할 때 | Stripe Globe (6만 점, 모델 0바이트) |
|
||||||
|
| 셰이더 전용 표면 (풀스크린) | 0KB | 재질이 메시지일 때 | Taotajima, fromanother |
|
||||||
|
| InstancedMesh 격자 | 0KB | 반복 단위가 데이터 항목일 때 | Codrops 큐브 그리드 |
|
||||||
|
| GPGPU 파티클 | 0KB | 움직임 자체가 정보일 때 | Mapbox 바람, Spotify |
|
||||||
|
| 하이트맵 지형 | 텍스처 1장(~50KB) | 지형이 상품일 때 | Explore Primland |
|
||||||
|
| 스크롤 카메라 리그 | +45KB (GSAP+Lenis) | 깊이가 서사일 때 | Oryzo, Shopify Editions |
|
||||||
|
| SDF 텍스트 (troika) | +55KB | WebGL 안에 대형 타이포가 필요할 때 | — |
|
||||||
|
| **GLB 모델 (무텍스처, Draco)** | **35~90KB** (실측 §07) | 오브젝트가 상품일 때 | Duck 34KB, Fox 90KB |
|
||||||
|
| **GLB 모델 (PBR 텍스처)** | **1~10MB** (실측 §07) | 사실감이 구매 결정에 필요할 때만 | DamagedHelmet 3.7MB |
|
||||||
|
| 포스트프로세싱 (bloom+CA) | +32KB | 발광이 형태의 일부일 때 | — |
|
||||||
|
| Spline 씬 임포트 | **800KB~2MB** | **거의 없음** | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 출처
|
||||||
|
|
||||||
|
- awesome-casestudy (110건 아카이브) https://github.com/luruke/awesome-casestudy
|
||||||
|
- Stripe Globe 기술 해설 https://stripe.com/blog/globe
|
||||||
|
- Stripe Connect https://stripe.com/blog/connect-front-end-experience
|
||||||
|
- Best Three.js Websites 2026 https://www.utsubo.com/blog/best-threejs-websites-2026
|
||||||
|
- Award-Winning Websites 2026 https://www.hontran.dev/blog/best-award-winning-websites-2026
|
||||||
|
- WebGL Website Examples https://www.hontran.dev/blog/webgl-website-examples
|
||||||
|
- 3D Landing 갤러리 (24건) https://www.a1.gallery/websites/3d-landing
|
||||||
|
- Lusion 프로젝트 https://lusion.co/projects/
|
||||||
|
- Active Theory 케이스 스터디 https://medium.com/active-theory
|
||||||
|
- 3D 컨피규레이터 ROI https://www.designrush.com/agency/ecommerce/trends/3d-product-configurators
|
||||||
206
research/three/07-glb-sources.md
Normal file
206
research/three/07-glb-sources.md
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
# 07 — GLB 모델 소스 · 라이선스 · 실측 무게
|
||||||
|
|
||||||
|
> 조사일 2026-08-20. **`실측` 표기는 이 문서를 쓰면서 직접 다운로드/압축해 잰 값**이다. 나머지는 문서 기재값.
|
||||||
|
> 압축 실험은 `@gltf-transform/cli@4.4.2`로 수행했다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 먼저 알아야 할 것 — 텍스처가 예산이다
|
||||||
|
|
||||||
|
`DamagedHelmet.glb`(Khronos 표준 샘플)를 분해한 **실측**이다.
|
||||||
|
|
||||||
|
| 구성 | 크기 | 비중 |
|
||||||
|
|---|---|---|
|
||||||
|
| 지오메트리 (14,556 vtx / 15,452 tri) | **558.5 KB** (GPU) | 13% |
|
||||||
|
| baseColor 2048² JPEG | 935.6 KB | |
|
||||||
|
| metallicRoughness 2048² JPEG | 1,300 KB | |
|
||||||
|
| normal 2048² JPEG | 517.8 KB | |
|
||||||
|
| occlusion 2048² JPEG | 361.7 KB | |
|
||||||
|
| emissive 2048² JPEG | 97.5 KB | |
|
||||||
|
| **텍스처 합계** | **≈ 3.2 MB** | **87%** |
|
||||||
|
| **VRAM** | 텍스처 5장 × 22.37 MB = **111.9 MB** | |
|
||||||
|
|
||||||
|
압축 **실측**:
|
||||||
|
|
||||||
|
| 처리 | 결과 | 감소 |
|
||||||
|
|---|---|---|
|
||||||
|
| 원본 | 3,685.5 KB | — |
|
||||||
|
| **Draco** | 3,216.3 KB | **−12.7%** |
|
||||||
|
| **meshopt** | 3,320.8 KB | −9.9% |
|
||||||
|
| gzip(원본) | 3,297.0 KB | −10.5% |
|
||||||
|
|
||||||
|
> **Draco가 13%밖에 못 줄인다.** 지오메트리만 압축하는데 지오메트리가 파일의 13%뿐이기 때문이다.
|
||||||
|
> **"Draco 쓰면 90% 준다"는 말은 지오메트리가 payload일 때만 참이다.**
|
||||||
|
|
||||||
|
지오메트리가 payload인 모델의 **실측**:
|
||||||
|
|
||||||
|
| 모델 | 원본 | Draco | Draco+gzip | 감소 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Duck.glb** (텍스처 1장, 저폴리) | 117.7 KB | **34.4 KB** | **33.1 KB** | **−71%** |
|
||||||
|
| **Fox.glb** (텍스처 없음, 애니메이션 3종) | 159.0 KB | **89.8 KB** | **68.1 KB** | **−44%** |
|
||||||
|
| Box.glb (최소 큐브) | 1.6 KB | — | — | — |
|
||||||
|
|
||||||
|
### 결론 — 300KB 예산에 들어오는 것
|
||||||
|
|
||||||
|
| 시나리오 | 실현 가능? |
|
||||||
|
|---|---|
|
||||||
|
| 무텍스처 저폴리 GLB 1~3개 (Draco) | ✅ 35~90KB × 3 = **105~270KB** |
|
||||||
|
| 텍스처 1장(512² WebP ~40KB) + Draco 지오메트리 | ✅ **~120KB** |
|
||||||
|
| 애니메이션 캐릭터 1체 (무텍스처, Fox급) | ✅ **90KB** |
|
||||||
|
| PBR 텍스처 세트(2048²) 모델 1개 | ❌ **3.7MB — 예산의 12배** |
|
||||||
|
| PBR 세트를 512²로 줄이고 WebP | ⚠️ ~400~600KB **추정** — 여전히 초과 |
|
||||||
|
| Sketchfab/Poly Haven 쇼케이스 모델 그대로 | ❌ 대부분 2~24MB |
|
||||||
|
|
||||||
|
**규칙: 300KB 예산에서는 텍스처를 포기하거나 모델을 포기해야 한다. 둘 다는 안 된다.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Khronos 표준 샘플 (실측 완료)
|
||||||
|
|
||||||
|
`https://github.com/KhronosGroup/glTF-Sample-Assets` · GitHub raw 직링크 = **자동화 가능**
|
||||||
|
라이선스는 **모델마다 다르다**(CC0 / CC-BY 4.0 / Khronos 전용). `Models/<name>/README.md`에 명시. 반드시 개별 확인.
|
||||||
|
|
||||||
|
| 모델 | **실측 크기** | 성격 |
|
||||||
|
|---|---|---|
|
||||||
|
| Box.glb | **1.6 KB** | 최소 |
|
||||||
|
| Duck.glb | **117.7 KB** | 저폴리 + 512 텍스처 1장 |
|
||||||
|
| Fox.glb | **159.0 KB** | 무텍스처 + 애니 3종 |
|
||||||
|
| BrainStem.glb | **3,120 KB** | 리깅 애니메이션 |
|
||||||
|
| DamagedHelmet.glb | **3,685 KB** | PBR 표준 레퍼런스 |
|
||||||
|
| SheenChair.glb | **4,029 KB** | 패브릭(sheen) |
|
||||||
|
| ToyCar.glb | **5,295 KB** | 클리어코트 |
|
||||||
|
| IridescentDishWithOlives.glb | **5,608 KB** | 이리데선스 |
|
||||||
|
| DragonAttenuation.glb | **6,252 KB** | 투과·감쇠 |
|
||||||
|
| ChronographWatch.glb | **7,272 KB** | 정밀 제품 |
|
||||||
|
| Avocado.glb | **7,920 KB** | 소품 + 4K 텍스처 |
|
||||||
|
| WaterBottle.glb | **8,757 KB** | 제품 |
|
||||||
|
| Lantern.glb | **9,340 KB** | 발광 |
|
||||||
|
| BoomBox.glb | **10,365 KB** | 제품 |
|
||||||
|
| AntiqueCamera.glb | **17,129 KB** | 고해상 |
|
||||||
|
| MosquitoInAmber.glb | **23,662 KB** | 투과 극한 |
|
||||||
|
| GlassHurricaneCandleHolder.glb | **2,642 KB** | **유리 — 우리 미학과 가장 근접** |
|
||||||
|
|
||||||
|
**용도**: 렌더러/머티리얼 검증용이지 프로덕션 에셋이 아니다. 예산 안에 드는 것은 Box·Duck·Fox뿐.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. CC0 (상업 이용 자유 · 크레딧 불요) — 안전 등급 A
|
||||||
|
|
||||||
|
| # | 소스 | URL | 라이선스 | 폴리곤 규모 | 크기대 | 자동화 |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| 1 | **Poly Haven** | polyhaven.com/models | **CC0** 전부 | 중~고 (스캔 기반 소품) | gltf 1k/2k/4k 분리 제공. **실측** `potted_plant_02_1k.gltf` 6.5KB(+ 별도 bin/텍스처) | ✅ 공개 API + `dl.polyhaven.org` 직링크 |
|
||||||
|
| 2 | **Quaternius** | quaternius.com | **CC0** 전부 | **저폴리** (수백~수천 tri) | 팩 단위 수 MB, 개별은 수십 KB | ⚠️ 팩 zip 다운로드 |
|
||||||
|
| 3 | **Kenney** | kenney.nl/assets | **CC0** 전부 | **극저폴리** | 개별 수 KB~수십 KB | ⚠️ 팩 zip |
|
||||||
|
| 4 | **KayKit** (Kay Lousberg) | kaylousberg.itch.io | **CC0** (대부분) | 저폴리, 캐릭터 리깅 포함 | 팩 단위 | ⚠️ itch.io zip |
|
||||||
|
| 5 | **ambientCG** | ambientcg.com | **CC0** | 텍스처·HDRI 중심, 모델 소수 | — | ✅ API |
|
||||||
|
| 6 | **Smithsonian 3D** | 3d.si.edu | **CC0** (2,000+) | **고폴리 스캔** | 수 MB~수십 MB | ⚠️ 개별 다운로드. glTF/OBJ 제공 |
|
||||||
|
| 7 | **Threedscans** | threedscans.com | **퍼블릭 도메인** | **초고폴리** 조각 스캔 | 수십 MB (STL/OBJ) | ⚠️ 수동. **GLB 변환 + 데시메이션 필수** |
|
||||||
|
| 8 | **NASA 3D Resources** | nasa3d.arc.nasa.gov | **퍼블릭 도메인** (일부 CC-BY, 개별 확인) | 중~고 | 수 MB | ⚠️ 수동 |
|
||||||
|
| 9 | **Blender Studio / demo files** | studio.blender.org, blender.org/download/demo-files | **CC0 / CC-BY** 혼재 | 고 | 수십 MB (.blend) | ⚠️ 변환 필요 |
|
||||||
|
| 10 | **awesome-cc0** (메타 목록) | github.com/madjin/awesome-cc0 | 링크 모음 | — | — | — |
|
||||||
|
| 11 | **Open Source 3D Assets** | opensource3dassets.com | CC0 중심 GLB 집계 | 저~중 | — | ⚠️ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 크레딧 필요 또는 제한 있음 — 안전 등급 B/C
|
||||||
|
|
||||||
|
| # | 소스 | URL | 라이선스 | 주의 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 12 | **Sketchfab (CC0 필터)** | sketchfab.com/3d-models?licenses=cc0 | 필터로 CC0만 | ✅ 자동 glTF 변환 제공. **CC-BY/NC/ND 모델은 Sketchfab 밖으로 못 나가는 경우가 있다** — 필터 없이 쓰지 마라 |
|
||||||
|
| 13 | **Poly Pizza** (Google Poly 후속) | poly.pizza | **대부분 CC-BY** (크레딧 필수) | 로그인 불요, 저폴리 대량. **CC0가 아니다** |
|
||||||
|
| 14 | **Scan the World** (MyMiniFactory) | myminifactory.com/scantheworld | **혼재 — CC-BY-NC 다수** | ⛔ **상업 사이트에 그대로 쓰면 위반.** 개별 확인 필수 |
|
||||||
|
| 15 | **Sketchfab / Smithsonian 컬렉션** | sketchfab.com/Smithsonian | CC0 | ✅ 안전 |
|
||||||
|
| 16 | **Mixamo** | mixamo.com | Adobe 계정 필요, 사용 조건 있음 | 캐릭터 리깅·애니메이션. **재배포 제한** |
|
||||||
|
| 17 | **three.js examples 모델** | threejs.org/examples/models | 개별 상이 | **실측**: Soldier.glb 2,110KB · LittlestTokyo.glb 4,036KB · Flower.glb 4.7KB |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 300KB 안에서 실제로 가능한 파이프라인
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1) 지오메트리만 남긴다 — 텍스처를 버리고 정점 색/머티리얼 상수로 간다
|
||||||
|
npx @gltf-transform/cli prune in.glb s1.glb
|
||||||
|
npx @gltf-transform/cli dedup s1.glb s2.glb
|
||||||
|
npx @gltf-transform/cli weld s2.glb s3.glb
|
||||||
|
|
||||||
|
# 2) 폴리곤을 깎는다 (error 0.001~0.01 사이에서 육안 확인하며 조정)
|
||||||
|
npx @gltf-transform/cli simplify s3.glb s4.glb --ratio 0.5 --error 0.005
|
||||||
|
|
||||||
|
# 3) 지오메트리 압축 — 여기서 비로소 Draco 가 값을 한다
|
||||||
|
npx @gltf-transform/cli draco s4.glb final.glb
|
||||||
|
# 또는 디코드가 5~10배 빠른 meshopt
|
||||||
|
npx @gltf-transform/cli meshopt s4.glb final.glb
|
||||||
|
|
||||||
|
# 4) 텍스처를 꼭 써야 한다면 딱 한 장, 512 이하, WebP
|
||||||
|
npx @gltf-transform/cli resize final.glb r.glb --width 512 --height 512
|
||||||
|
npx @gltf-transform/cli webp r.glb out.glb
|
||||||
|
|
||||||
|
# 5) 검증 — 텍스처 비중과 VRAM 을 눈으로 본다
|
||||||
|
npx @gltf-transform/cli inspect out.glb
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ 이 환경에서 `--texture-compress` / `resize` 는 `sharp` 의 `colourspace: parameter space not set` 오류로 실패했다.
|
||||||
|
> 텍스처 처리는 별도 환경(또는 squoosh-cli)에서 하고 GLB에 되넣는 편이 안전하다.
|
||||||
|
|
||||||
|
### 디코더 비용도 예산이다
|
||||||
|
|
||||||
|
| 디코더 | 크기 | 비고 |
|
||||||
|
|---|---|---|
|
||||||
|
| DRACO WASM | **~200 KB** | 자체 호스팅 필수. 한 번 받으면 캐시 |
|
||||||
|
| KTX2/basis 트랜스코더 | **~250 KB** | |
|
||||||
|
| meshopt 디코더 | **~25 KB** | **훨씬 작다.** 300KB 예산에서는 meshopt이 유일하게 현실적 |
|
||||||
|
|
||||||
|
> **300KB 예산에서 Draco를 쓰면 디코더만으로 200KB(67%)가 날아간다.**
|
||||||
|
> 모델 하나를 위해 Draco를 도입하지 마라. **meshopt(25KB) 또는 무압축 + gzip**이 맞다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 압축 판단표
|
||||||
|
|
||||||
|
| 상황 | 선택 |
|
||||||
|
|---|---|
|
||||||
|
| 지오메트리 > 텍스처 (저폴리·무텍스처) | **meshopt** (디코더 25KB). Draco는 디코더가 더 크다 |
|
||||||
|
| 텍스처 > 지오메트리 (PBR) | **텍스처를 먼저 줄여라.** 지오메트리 압축은 무의미 |
|
||||||
|
| 다운로드 크기가 문제 | WebP/AVIF (VRAM은 안 줄어든다) |
|
||||||
|
| **VRAM**이 문제 (모바일) | **KTX2만이 답** (ETC1S ≈ 1/8). 단 이미 JPEG인 텍스처는 **파일이 커질 수 있다**(1.51MB → 1.98MB 사례) |
|
||||||
|
| 모델 1개, 예산 300KB | 압축하지 말고 **모델을 다시 만들어라**. 저폴리로 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 우리 브리프에 대한 판정
|
||||||
|
|
||||||
|
| 후보 | 예산 적합 | 컨셉 적합 |
|
||||||
|
|---|---|---|
|
||||||
|
| Khronos `GlassHurricaneCandleHolder.glb` (유리, 2.6MB) | ❌ 8배 초과 | ⚠️ 미학은 맞지만 촛대가 designpaca와 무관 |
|
||||||
|
| Khronos `Duck.glb` (34KB Draco) | ✅ | ❌ 오리가 왜 |
|
||||||
|
| Quaternius/KayKit 저폴리 팩 | ✅ (수십 KB) | ❌ 게임 에셋 톤 |
|
||||||
|
| Poly Haven 스캔 소품 | ⚠️ 1k도 수백 KB | ❌ 사실적 소품이 컨셉과 무관 |
|
||||||
|
| Threedscans 조각 (데시메이션 후) | ⚠️ 가능하나 수작업 | ⚠️ "고전 조각" 은유는 카피에 없다 |
|
||||||
|
|
||||||
|
**어느 것도 컨셉과 연결되지 않는다.** 상세 판단은 `08-proposal.md`.
|
||||||
|
|
||||||
|
**GLB가 정당해지는 유일한 경로**: designpaca가 **브랜드 캐릭터(알파카)**를 아이덴티티로 채택하는 경우.
|
||||||
|
그때는 CC0 팩에서 가져오지 말고 저폴리로 **직접 만들어야** 한다 — 남의 알파카는 우리 아이덴티티가 아니다.
|
||||||
|
비용 추정: 무텍스처 저폴리 + 정점 색 + meshopt = **40~80KB**, 디코더 25KB, 합 **65~105KB**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 출처
|
||||||
|
|
||||||
|
- Khronos glTF-Sample-Assets https://github.com/KhronosGroup/glTF-Sample-Assets
|
||||||
|
- Models 목록 https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/Models/Models.md
|
||||||
|
- glTF Transform https://gltf-transform.dev/
|
||||||
|
- Draco 압축률 논의 https://github.com/donmccurdy/glTF-Transform/discussions/347
|
||||||
|
- KTX2 크기 논의 https://github.com/donmccurdy/glTF-Transform/discussions/443
|
||||||
|
- Poly Haven https://polyhaven.com/models
|
||||||
|
- Quaternius https://quaternius.com
|
||||||
|
- Kenney https://kenney.nl/assets
|
||||||
|
- KayKit https://kaylousberg.itch.io
|
||||||
|
- ambientCG https://ambientcg.com
|
||||||
|
- Smithsonian 3D https://3d.si.edu
|
||||||
|
- Threedscans https://threedscans.com
|
||||||
|
- NASA 3D Resources https://nasa3d.arc.nasa.gov
|
||||||
|
- Sketchfab CC0 https://sketchfab.com/3d-models?licenses=cc0
|
||||||
|
- Poly Pizza https://poly.pizza
|
||||||
|
- Scan the World 라이선스 https://www.myminifactory.com/object-licensing
|
||||||
|
- awesome-cc0 https://github.com/madjin/awesome-cc0
|
||||||
190
research/three/08-proposal.md
Normal file
190
research/three/08-proposal.md
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
# 08 — designpaca 소개 페이지 3D 방향 제안
|
||||||
|
|
||||||
|
> 근거: `06-webgl-showcase.md`(사례 32건), `07-glb-sources.md`(실측 압축), 현재 코드 `apps/site/src/lib/hero-scene.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 결론 먼저 — 추상이다. GLB는 쓰지 않는다.
|
||||||
|
|
||||||
|
`06` §2에서 뽑은 **추상 3D 성공 조건 3개**를 브리프에 대입한 결과다.
|
||||||
|
|
||||||
|
| 조건 | designpaca |
|
||||||
|
|---|---|
|
||||||
|
| 형태가 **데이터**에 묶여 있다 | ✅ 가능. 페이지가 이미 파이프라인 6단계·토큰 값·자기 성능 수치를 **텍스트로** 공개하고 있다. 그 숫자를 형태로 옮기면 된다 |
|
||||||
|
| 은유가 **카피에도** 존재한다 | ✅ 가능. "게이트", "통과 조건", "예산" 은 이미 카피의 언어다 |
|
||||||
|
| 3D를 끄면 **정보를 잃는다** | ⚠️ 설계하기 나름. 아래 세 안은 전부 이 조건을 만족하도록 짰다 |
|
||||||
|
|
||||||
|
**구체 오브젝트를 배제하는 이유** — `06` §1의 공통 조건 3개 중 하나도 만족하지 않는다.
|
||||||
|
1. 파는 것이 물리적으로 존재하지 않는다. 규칙 묶음이다
|
||||||
|
2. 각도가 정보가 아니다. 규칙은 돌려봐도 새 정보가 없다
|
||||||
|
3. 오브젝트가 하나로 정해지지 않는다 — 무엇을 놓아도 **은유**고, 은유가 카피에 없으면 장식이다
|
||||||
|
|
||||||
|
**예산이 이 판단을 다시 확인해준다** (`07` 실측): PBR 텍스처 GLB는 3.7MB로 300KB 예산의 12배다.
|
||||||
|
Draco는 텍스처를 못 줄여서 −13%에 그치고, Draco 디코더 자체가 **200KB(예산의 67%)** 다.
|
||||||
|
GLB를 쓰려면 무텍스처 저폴리(35~90KB) + meshopt(디코더 25KB)뿐인데, 그 급의 모델로는 의미를 나를 수 없다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 제안 A — 파이프라인 게이트 (권장)
|
||||||
|
|
||||||
|
### 무엇을 보여주나
|
||||||
|
현재 떠 있는 **유리 판 여섯 장에 이름을 준다.** Z축을 따라 순서대로 세우고, 각 판이 파이프라인 한 단계가 된다.
|
||||||
|
|
||||||
|
```
|
||||||
|
카메라 ──→ [0 브리프 게이트] [1 레퍼런스] [2 방향] [3 토큰] [4 구현] [5 프리플라이트] ──→ 페이지 본문
|
||||||
|
```
|
||||||
|
|
||||||
|
- 스크롤하면 카메라가 판을 **하나씩 통과**한다. 통과한 판은 뒤에 남아 겹쳐 굴절된다
|
||||||
|
- 각 판 위에는 그 단계의 **통과 조건 한 줄**이 DOM 텍스트로 겹쳐 있다 (WebGL 안에 텍스트를 넣지 않는다 — 검색·선택·스크린리더)
|
||||||
|
- 아직 통과하지 않은 판은 흐리고, 통과한 판은 선명하다. **깊이 = 진행도**
|
||||||
|
|
||||||
|
### 왜 이 브리프에 맞나
|
||||||
|
1. **개수 문제가 이미 풀려 있다.** 히어로는 지금도 판이 6장이고 `Pipeline.astro`는 정확히 6단계다. "왜 6장인가"에 답이 생긴다 — 지금은 답이 없다
|
||||||
|
2. **순서가 이 제품의 본체다.** SKILL.md의 절대 규칙이 "순서를 바꾸지 마라"다. 순서를 **공간의 깊이**로 만드는 것은 은유가 아니라 직역이다
|
||||||
|
3. `06` §1의 Oryzo 교훈("스크롤이 2D 레이어가 아니라 진짜 Z축으로 카메라를 민다")과 Shopify Editions 교훈("각 섹션이 독립된 무대")을 동시에 쓴다
|
||||||
|
4. **기존 코드를 버리지 않는다.** 렌더타겟 굴절 기법, 예산 준수, 폴백이 그대로 산다. 판의 배치와 라벨만 바뀐다
|
||||||
|
|
||||||
|
### GLB 필요 여부
|
||||||
|
**불필요.** `PlaneGeometry` 6장 + 기존 굴절 셰이더. 새 에셋 0개.
|
||||||
|
|
||||||
|
### 예산 추정
|
||||||
|
| 항목 | 추가 |
|
||||||
|
|---|---|
|
||||||
|
| three (이미 있음) | ±0 |
|
||||||
|
| 판 배치·카메라 리그 코드 | **+2~3KB** |
|
||||||
|
| 스크롤 연동 | **+0KB** — `scroll` 리스너 대신 rAF에서 `scrollY`를 읽는다 (하드게이트 #10) |
|
||||||
|
| 라벨 | **+0KB** — DOM 오버레이 |
|
||||||
|
| **합계** | **+3KB 이내** |
|
||||||
|
|
||||||
|
### 실패 위험
|
||||||
|
| 위험 | 대응 |
|
||||||
|
|---|---|
|
||||||
|
| **스크롤 하이재킹.** 게이트 통과를 강제하면 이탈한다 | 전체 스크롤 길이를 **2뷰포트 이내**로 묶고, 핀 고정을 쓰지 않는다. 스크롤을 멈춰도 페이지가 진행된다 |
|
||||||
|
| 판이 텍스트 가독성을 해친다 | 라벨은 판 **옆**에 두고 판 위에 겹치지 않는다. 대비는 별도 측정 |
|
||||||
|
| 여전히 "떠다니는 사각형"으로 읽힌다 | 판을 **정면으로 세우고 정렬**한다. 지금의 자유 부유를 버린다. 계기판은 정렬돼 있다 |
|
||||||
|
| reduced-motion에서 의미가 사라진다 | 판 6장을 **정지 상태로 한 장 렌더**해서 순서가 보이게 한다. 현재처럼 아예 로드하지 않으면 정보를 잃는다 → 이 안은 폴백 정책을 바꿔야 한다 |
|
||||||
|
|
||||||
|
> ⚠️ **이 안의 유일한 구조적 대가**: 3D가 정보를 나르게 되는 순간, "reduced-motion이면 아예 로드하지 않는다"는 현재 정책이 **정보 손실**이 된다.
|
||||||
|
> 해결: reduced-motion에서는 CSS만으로 6단계를 **세로 목록**으로 보여준다(이미 `Pipeline.astro`가 한다). 3D는 같은 정보의 다른 표현이 된다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 제안 B — 토큰 격자
|
||||||
|
|
||||||
|
### 무엇을 보여주나
|
||||||
|
페이지가 실제로 쓰는 토큰을 **3차원 좌표계**에 세운다. 축이 곧 토큰 종류다.
|
||||||
|
|
||||||
|
- **X축** = 간격 스케일 `--space-1`~`--space-8` (8단계)
|
||||||
|
- **Y축** = 타입 스케일 (본문 단계 + 디스플레이)
|
||||||
|
- **Z축** = 색 역할 (`--surface` / `--surface-raised` / `--ink` / `--ink-muted` / `--line` / `--accent`)
|
||||||
|
- 각 노드의 **위치와 크기가 실제 CSS 변수 값에서 읽힌다**. `getComputedStyle`로 뽑아 그대로 스케일에 넣는다
|
||||||
|
- 다크/라이트를 토글하면 **격자가 실제로 재배치된다** — 색 축의 좌표가 바뀌기 때문
|
||||||
|
- 노드에 마우스를 올리면 그 토큰이 페이지 어디에 쓰였는지 DOM 요소가 하이라이트된다
|
||||||
|
|
||||||
|
### 왜 이 브리프에 맞나
|
||||||
|
1. 페이지 컨셉이 *"그 절차와 **형태 수치**를 전부 공개한다"* 이다. **수치가 그대로 형태가 된다.** 은유가 0단계다
|
||||||
|
2. `06` §2의 Stripe Globe와 같은 논리 — 점 하나하나가 데이터고, 지오메트리가 파일이 아니라 **코드**다
|
||||||
|
3. **3D를 끄면 진짜로 정보를 잃는다.** 세 축을 2D로 접으면 축 하나가 사라진다. 이 조건을 가장 확실히 만족하는 안이다
|
||||||
|
4. 토큰이 바뀌면 격자가 따라 바뀐다 — **하드코딩이 불가능한 구조**라 스킬의 "토큰은 계약이다" 원칙을 코드가 증명한다
|
||||||
|
|
||||||
|
### GLB 필요 여부
|
||||||
|
**불필요.** `InstancedMesh` 노드 + `LineSegments` 축. Stripe가 6만 점을 모델 없이 만든 것과 같다.
|
||||||
|
|
||||||
|
### 예산 추정
|
||||||
|
| 항목 | 추가 |
|
||||||
|
|---|---|
|
||||||
|
| InstancedMesh + 라인 축 | **+3KB** |
|
||||||
|
| `getComputedStyle` 토큰 리더 | +1KB |
|
||||||
|
| 호버 하이라이트 연동 | +2KB |
|
||||||
|
| **합계** | **+6KB 이내** |
|
||||||
|
|
||||||
|
### 실패 위험
|
||||||
|
| 위험 | 대응 |
|
||||||
|
|---|---|
|
||||||
|
| **"도표를 왜 3D로?"** — 가장 큰 위험. 2D 표가 더 읽기 쉬우면 3D는 진 것이다 | 축 세 개가 **동시에** 필요하다는 걸 보여줘야 한다. 축 하나를 접었을 때 잃는 것이 눈에 보여야 정당화된다 |
|
||||||
|
| 격자가 차갑고 예쁘지 않다 | Ethereal Glass 미학과 충돌할 수 있다. 노드를 유리 재질로 하고 축을 시안 틸로 — 미학은 유지 가능 |
|
||||||
|
| 정보 과다 | 축당 표시 개수를 제한한다. 전부 보여주려 하지 마라 |
|
||||||
|
| 히어로에 두기엔 무겁다 | **히어로가 아니라 `Metrics`/`Typography` 섹션 옆**에 두는 것이 맞을 수 있다. 히어로는 A안, 본문은 B안 조합이 가능하다 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 제안 C — 실측 계기판
|
||||||
|
|
||||||
|
### 무엇을 보여주나
|
||||||
|
페이지가 **지금 이 순간의 자기 수치**를 3D로 그린다. 값은 전부 실제 API에서 읽는다.
|
||||||
|
|
||||||
|
| 축 | 데이터 소스 |
|
||||||
|
|---|---|
|
||||||
|
| 번들 전송량 | `performance.getEntriesByType('resource')` |
|
||||||
|
| LCP | `PerformanceObserver` |
|
||||||
|
| 드로우콜 / 삼각형 | `renderer.info.render` |
|
||||||
|
| 현재 DPR | `renderer.getPixelRatio()` |
|
||||||
|
| 프레임 시간 분포 | rAF 델타 히스토그램 |
|
||||||
|
|
||||||
|
- 막대그래프가 아니라 **공간에 뜬 눈금과 축**. 값이 예산선을 넘으면 그 축만 색이 바뀐다
|
||||||
|
- **관찰자 효과를 숨기지 않는다**: 3D를 켜면 드로우콜과 프레임 시간이 올라가고, 그게 화면에 그대로 보인다
|
||||||
|
|
||||||
|
### 왜 이 브리프에 맞나
|
||||||
|
1. `Metrics.astro`가 이미 *"이 페이지의 수치"*를 **텍스트로** 공개하고 있다(JS 0.6%, three 추가분 185KB, 슬롭 지문 0건). 이 안은 그 섹션의 3D 판이다 — **새 컨셉이 아니라 기존 컨셉의 연장**
|
||||||
|
2. 스킬의 5단계가 *"측정하지 않았으면 통과가 아니다"* 라고 말한다. 페이지가 스스로를 측정하는 화면은 **제품 주장의 증명**이다
|
||||||
|
3. 3D가 자기 비용을 스스로 고백하는 구조라, "3D를 왜 넣었나"는 질문에 페이지가 직접 답한다
|
||||||
|
|
||||||
|
### GLB 필요 여부
|
||||||
|
**불필요.** 라인 + 인스턴스 + 텍스트는 DOM.
|
||||||
|
|
||||||
|
### 예산 추정
|
||||||
|
| 항목 | 추가 |
|
||||||
|
|---|---|
|
||||||
|
| 계측 코드 (PerformanceObserver 등) | **+2KB** |
|
||||||
|
| 축·눈금 지오메트리 | +2KB |
|
||||||
|
| **합계** | **+4KB 이내** |
|
||||||
|
|
||||||
|
### 실패 위험
|
||||||
|
| 위험 | 대응 |
|
||||||
|
|---|---|
|
||||||
|
| **수치가 나쁘면 자폭한다.** 저사양 기기에서 프레임이 무너지면 그 화면이 증거로 남는다 | 이게 정직함이라 판단하면 그대로 두고, 아니면 이 안을 버려라. **어중간하게 감추면 최악** |
|
||||||
|
| 계기판 자체가 진부하다 | `antipatterns.md` §4 「펄스 애니메이션 상태 점」에 인접하다. 값이 **실제로 변할 때만** 움직여야 한다 |
|
||||||
|
| 개발자 아닌 방문자에게 무의미하다 | 청중이 에이전트·개발자라 큰 문제는 아니지만, 히어로에 두면 첫인상이 좁아진다. **본문 섹션용** |
|
||||||
|
| 측정 자체가 프레임을 먹는다 | 샘플링 주기를 1초로. 매 프레임 계측하지 않는다 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 조합 권장안
|
||||||
|
|
||||||
|
셋은 배타적이지 않다. **A를 히어로에, C를 Metrics 섹션에** 두는 조합이 가장 안전하다.
|
||||||
|
|
||||||
|
| 위치 | 안 | 이유 |
|
||||||
|
|---|---|---|
|
||||||
|
| 히어로 | **A 파이프라인 게이트** | 첫 화면에서 제품이 무엇인지(6단계 순서) 말한다. 기존 코드 재사용 |
|
||||||
|
| Metrics 섹션 | **C 실측 계기판** | 이미 그 자리에 있는 텍스트 수치의 3D 판 |
|
||||||
|
| (선택) Typography/Tokens 섹션 | B 토큰 격자 | 여력이 있을 때만. 셋 다 넣으면 페이지가 3D 전시장이 된다 |
|
||||||
|
|
||||||
|
**셋 다 하지 마라.** `06` §1이 반복해서 말하는 것은 *"어려운 아이디어 하나를 끝까지 판다"* 다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 하지 말아야 할 것 (진부해진 웹 3D)
|
||||||
|
|
||||||
|
| 금지 | 왜 |
|
||||||
|
|---|---|
|
||||||
|
| **회전하는 큐브 / 토러스넛** | three.js "hello world" 그 자체. 18줄 튜토리얼의 결과물이 그대로 프로덕션에 올라간 신호 |
|
||||||
|
| **이유 없는 파티클 구(sphere)** | 파티클이 데이터에 묶이지 않으면 스크린세이버다. `06` §2 조건 1 위반 |
|
||||||
|
| **마우스를 따라다니는 구체 / 커서 오브** | 인터랙션을 설계하지 않았다는 신호. 커서는 이미 커서다 |
|
||||||
|
| **Spline 기본 씬 임포트** | 800KB~2MB. 예산의 3~7배. 게다가 Spline 특유의 매끄러운 블롭은 즉시 식별된다 |
|
||||||
|
| **떠다니는 3D 추상 블롭·기하 도형** | `antipatterns.md` §7이 명시적으로 금지 — "콘텐츠 부재를 덮는 장식" |
|
||||||
|
| **히어로 뒤 방사형 그라디언트 오브·헤일로** | `antipatterns.md` §1 — "구성을 못 잡았을 때의 회피 수단" |
|
||||||
|
| **네온 온 다크 발광 와이어프레임** | `antipatterns.md` §1 「네온 온 다크」. 개발자 툴 브리프라도 관성이다 |
|
||||||
|
| **아무 데나 떠 있는 유리 판** | **지금 우리다.** 형태에 근거가 없으면 유리도 블롭과 같다 |
|
||||||
|
| **3D 텍스트로 만든 로고 회전** | 텍스트는 DOM에 있어야 한다. 3D 로고는 검색·선택·스크린리더를 전부 잃고 얻는 게 없다 |
|
||||||
|
| **모든 섹션에 3D** | 스크롤할 때마다 캔버스가 뜨면 컨텍스트 상한(8~16)과 VRAM을 동시에 친다 |
|
||||||
|
| **로딩할 게 없는데 3D 프리로더** | `antipatterns.md` §5 — 없는 대기를 만든다 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 어느 안을 택하든 지켜야 할 것
|
||||||
|
|
||||||
|
- **텍스트는 DOM에.** 라벨·수치·통과 조건 전부. WebGL 안에 넣지 않는다
|
||||||
|
- **판정 질문 하나**: *"3D를 끄면 무엇을 잃는가?"* 답이 "분위기"면 그 안은 실패다
|
||||||
|
- **형태의 개수에 근거가 있어야 한다.** 6장인 이유, 8개인 이유를 한 줄로 못 대면 개수를 바꿔라
|
||||||
|
- **예산**: 세 안 모두 **+6KB 이내**다. 300KB 상한은 GLB를 전제한 값이고, 여기서는 쓸 일이 없다
|
||||||
|
- **폴백 재설계**: 3D가 정보를 나르게 되면 "reduced-motion이면 로드하지 않는다"가 정보 손실이 된다. 같은 정보를 CSS/DOM으로도 제공해야 한다
|
||||||
Loading…
Add table
Add a link
Reference in a new issue