625 lines
11 KiB
Plaintext
625 lines
11 KiB
Plaintext
프로젝트명
|
|
Pet Glass / Pet Behind Glass
|
|
|
|
목표
|
|
사용자가 반려동물 사진 또는 동영상을 업로드하면,
|
|
Three.js 기반으로 "유리문 뒤에 반려동물이 있는 것처럼 보이는 시점 변화형 왜곡 효과"를 제공하는 웹 MVP를 구현한다.
|
|
|
|
중요
|
|
단순 이미지 필터가 아니라,
|
|
카메라/시점 이동에 따라 유리 굴절 결과가 동적으로 바뀌어야 한다.
|
|
|
|
기술 스택
|
|
- Vite
|
|
- TypeScript
|
|
- Three.js
|
|
- GLSL Shader
|
|
- Vanilla TS 기반
|
|
- React 사용하지 않음
|
|
- 별도 백엔드 없음
|
|
- 모든 처리는 브라우저 로컬에서 수행
|
|
|
|
핵심 컨셉
|
|
|
|
구조:
|
|
|
|
PerspectiveCamera
|
|
↓
|
|
Glass Plane
|
|
↓
|
|
Pet Plane
|
|
|
|
Pet Plane에는 사용자가 업로드한 이미지 또는 동영상을 Texture로 적용한다.
|
|
|
|
Glass Plane에는 ShaderMaterial을 적용하여
|
|
유리 종류에 따른 굴절/왜곡을 표현한다.
|
|
|
|
카메라 또는 view offset이 움직이면
|
|
굴절되는 이미지 위치도 같이 변화해야 한다.
|
|
|
|
|
|
========================
|
|
1. 프로젝트 초기 구성
|
|
========================
|
|
|
|
Vite + vanilla-ts 기반으로 프로젝트 생성.
|
|
|
|
필수 패키지:
|
|
- three
|
|
|
|
예상 구조:
|
|
|
|
src/
|
|
├─ main.ts
|
|
├─ style.css
|
|
├─ scene/
|
|
│ ├─ SceneManager.ts
|
|
│ ├─ CameraController.ts
|
|
│ └─ RendererManager.ts
|
|
├─ objects/
|
|
│ ├─ PetPlane.ts
|
|
│ └─ GlassPlane.ts
|
|
├─ shaders/
|
|
│ ├─ common.vert
|
|
│ ├─ mosaic.frag
|
|
│ ├─ reeded.frag
|
|
│ └─ frosted.frag
|
|
├─ input/
|
|
│ ├─ MediaLoader.ts
|
|
│ ├─ PointerController.ts
|
|
│ └─ GyroController.ts
|
|
├─ types/
|
|
│ └─ glass.ts
|
|
└─ ui/
|
|
└─ controls.ts
|
|
|
|
|
|
========================
|
|
2. 화면 구성
|
|
========================
|
|
|
|
단일 페이지로 구현.
|
|
|
|
화면은 크게 두 영역:
|
|
|
|
1. 상단/사이드 컨트롤 영역
|
|
2. Three.js Canvas 영역
|
|
|
|
초기 UI:
|
|
|
|
- 사진 업로드 버튼
|
|
- 동영상 업로드 버튼
|
|
- 유리 효과 선택
|
|
- Mosaic
|
|
- Reeded
|
|
- Frosted
|
|
- Distortion Strength 슬라이더
|
|
- View Sensitivity 슬라이더
|
|
- Reset View 버튼
|
|
- Gyroscope 사용 버튼
|
|
- 현재 효과명 표시
|
|
|
|
모바일 우선 반응형으로 구현.
|
|
|
|
|
|
========================
|
|
3. 미디어 업로드
|
|
========================
|
|
|
|
이미지:
|
|
- jpg
|
|
- jpeg
|
|
- png
|
|
- webp
|
|
|
|
동영상:
|
|
- mp4
|
|
- webm
|
|
- 브라우저에서 재생 가능한 파일
|
|
|
|
이미지 업로드 시:
|
|
THREE.Texture 생성
|
|
|
|
동영상 업로드 시:
|
|
HTMLVideoElement 생성 후
|
|
THREE.VideoTexture 사용
|
|
|
|
새 파일을 업로드하면
|
|
기존 Texture 및 Object URL을 적절히 dispose/revoke 처리할 것.
|
|
|
|
업로드한 미디어의 aspect ratio를 유지해야 한다.
|
|
|
|
예:
|
|
세로 사진이면 세로 비율 유지
|
|
가로 사진이면 가로 비율 유지
|
|
|
|
Canvas 안에서 최대한 크게 보이되 잘리지 않도록 contain 방식으로 배치.
|
|
|
|
|
|
========================
|
|
4. Three.js Scene 구성
|
|
========================
|
|
|
|
PerspectiveCamera 사용.
|
|
|
|
기본 구성:
|
|
|
|
Scene
|
|
- Camera
|
|
- PetPlane
|
|
- GlassPlane
|
|
|
|
PetPlane:
|
|
- PlaneGeometry
|
|
- 업로드된 Texture 적용
|
|
- 기본 위치 z = 0
|
|
|
|
GlassPlane:
|
|
- PlaneGeometry
|
|
- PetPlane보다 카메라 방향으로 조금 앞
|
|
- 예: z = 0.15 ~ 0.3
|
|
- ShaderMaterial 사용
|
|
|
|
Camera:
|
|
- 기본 정면 위치
|
|
- lookAt(0, 0, 0)
|
|
|
|
예:
|
|
camera.position.set(0, 0, 3)
|
|
|
|
|
|
========================
|
|
5. 시점 이동
|
|
========================
|
|
|
|
마우스 또는 터치 drag로 시점을 이동할 수 있어야 한다.
|
|
|
|
중요:
|
|
단순히 Canvas를 좌우 이동하는 것이 아니라
|
|
실제 카메라 또는 shader의 view offset이 변해야 한다.
|
|
|
|
PointerController 구현.
|
|
|
|
입력값을 정규화:
|
|
|
|
x: -1 ~ 1
|
|
y: -1 ~ 1
|
|
|
|
카메라 이동 예:
|
|
|
|
camera.position.x = normalizedX * maxOffsetX
|
|
camera.position.y = normalizedY * maxOffsetY
|
|
|
|
항상:
|
|
camera.lookAt(0, 0, 0)
|
|
|
|
단,
|
|
카메라 이동만으로 효과가 약한 경우
|
|
shader uniform으로도 view offset을 넘길 것.
|
|
|
|
예:
|
|
|
|
uViewOffset: vec2
|
|
|
|
normalized pointer 값을
|
|
ShaderMaterial uniform으로 전달.
|
|
|
|
|
|
========================
|
|
6. Mosaic Glass 구현
|
|
========================
|
|
|
|
가장 먼저 완성할 핵심 효과.
|
|
|
|
사각형 모자이크 유리 구현.
|
|
|
|
요구사항:
|
|
|
|
- 전체 UV를 NxN grid로 나눈다.
|
|
- 각 셀은 작은 볼록/오목 렌즈처럼 동작한다.
|
|
- 셀 내부 위치에 따라 source texture의 UV가 변형된다.
|
|
- 카메라/view offset에 따라 sampling 위치가 바뀐다.
|
|
- 셀마다 약간씩 다른 distortion을 넣을 수 있다.
|
|
- 셀 경계가 살짝 보이면 좋다.
|
|
|
|
필수 uniform:
|
|
|
|
uTexture
|
|
uGridSize
|
|
uStrength
|
|
uViewOffset
|
|
uResolution
|
|
uTime
|
|
|
|
개념:
|
|
|
|
vec2 scaled = vUv * uGridSize;
|
|
vec2 cell = floor(scaled);
|
|
vec2 local = fract(scaled);
|
|
|
|
local 좌표를 중심 기준:
|
|
|
|
vec2 centered = local - 0.5;
|
|
|
|
이를 이용해 렌즈 형태 distortion 적용.
|
|
|
|
그 후
|
|
uViewOffset에 따라 UV sampling 위치를 추가로 이동.
|
|
|
|
중요:
|
|
시점을 좌우로 움직였을 때
|
|
강아지의 눈/코/얼굴 부분이 셀 사이에서 서로 다른 위치로 이동해
|
|
실제 모자이크 유리를 옆에서 보는 느낌이 나야 한다.
|
|
|
|
단순 pixelation처럼 보이면 안 된다.
|
|
|
|
|
|
========================
|
|
7. Reeded Glass 구현
|
|
========================
|
|
|
|
세로 골유리 효과.
|
|
|
|
유리를 세로 방향으로 반복되는 원통형 렌즈 배열처럼 표현.
|
|
|
|
특징:
|
|
|
|
- Y 방향 변화 거의 없음
|
|
- X 방향 distortion 중심
|
|
- 일정 간격의 vertical ridge
|
|
- 카메라가 좌우 이동하면 ridge마다 보이는 이미지 위치가 달라짐
|
|
|
|
sin 또는 반복 함수 사용 가능.
|
|
|
|
예시 개념:
|
|
|
|
float ridge = sin(vUv.x * frequency);
|
|
|
|
uv.x += ridge * strength;
|
|
|
|
여기에
|
|
uViewOffset.x를 반영하여
|
|
시점 변화가 눈에 띄게 구현.
|
|
|
|
|
|
========================
|
|
8. Frosted Glass 구현
|
|
========================
|
|
|
|
반투명 서리유리 느낌.
|
|
|
|
요구사항:
|
|
|
|
- 기본 blur
|
|
- UV noise distortion
|
|
- 이미지 형태는 대략 보이지만 디테일은 흐려짐
|
|
- 시점 이동 시 noise sampling 또는 refraction 방향이 조금 변함
|
|
|
|
성능을 위해
|
|
무거운 multi-pass blur는 MVP에서는 피할 수 있음.
|
|
|
|
간단한 multi-sample 방식 사용 가능.
|
|
|
|
예:
|
|
주변 4~9개 texture sample을 평균.
|
|
|
|
|
|
========================
|
|
9. 효과 타입 관리
|
|
========================
|
|
|
|
TypeScript 타입 정의:
|
|
|
|
type GlassType =
|
|
| 'mosaic'
|
|
| 'reeded'
|
|
| 'frosted';
|
|
|
|
interface GlassSettings {
|
|
type: GlassType;
|
|
strength: number;
|
|
gridSize: number;
|
|
sensitivity: number;
|
|
}
|
|
|
|
GlassPlane에서 현재 효과를 변경 가능하도록 구현.
|
|
|
|
방법은 자유:
|
|
- material 교체
|
|
또는
|
|
- ShaderMaterial fragment shader 교체
|
|
|
|
가능하면 구조적으로 분리.
|
|
|
|
|
|
========================
|
|
10. Gyroscope
|
|
========================
|
|
|
|
모바일 환경에서 DeviceOrientation 사용.
|
|
|
|
사용자가 "Gyroscope 사용" 버튼을 누른 뒤
|
|
권한 요청.
|
|
|
|
iOS 등 권한 요청이 필요한 환경 고려.
|
|
|
|
기기의 좌우/상하 tilt 값을
|
|
|
|
-1 ~ 1
|
|
|
|
범위로 normalization하고
|
|
PointerController와 동일한 view offset 시스템에 연결.
|
|
|
|
즉:
|
|
|
|
손가락 드래그
|
|
또는
|
|
휴대폰 기울이기
|
|
|
|
둘 다 같은 ViewController 상태를 변경하도록 만들 것.
|
|
|
|
기본값은 gyro OFF.
|
|
|
|
|
|
========================
|
|
11. 애니메이션 루프
|
|
========================
|
|
|
|
requestAnimationFrame 사용.
|
|
|
|
매 프레임:
|
|
|
|
- input state 갱신
|
|
- camera interpolation
|
|
- shader uniform 갱신
|
|
- video texture update
|
|
- renderer.render()
|
|
|
|
카메라 움직임은 바로 jump시키지 말고
|
|
lerp를 적용해서 부드럽게 움직일 것.
|
|
|
|
예:
|
|
|
|
currentX += (targetX - currentX) * 0.08
|
|
|
|
|
|
========================
|
|
12. 모바일 UX
|
|
========================
|
|
|
|
모바일에서 가장 중요.
|
|
|
|
Canvas:
|
|
- 터치 드래그 가능
|
|
- 페이지 스크롤과 충돌하지 않도록 처리
|
|
|
|
화면 중앙에 처음 약 2초간 안내:
|
|
|
|
"화면을 좌우로 움직여보세요"
|
|
|
|
또는
|
|
|
|
"휴대폰을 기울여보세요"
|
|
|
|
미디어 업로드 전에는
|
|
placeholder 화면 표시:
|
|
|
|
"반려동물 사진 또는 영상을 선택하세요"
|
|
|
|
|
|
========================
|
|
13. 기본 샘플
|
|
========================
|
|
|
|
사용자가 아무 파일도 업로드하지 않아도 테스트 가능하도록
|
|
assets/sample-pet.* 형태의 샘플 이미지를 둘 수 있는 구조를 만들어둘 것.
|
|
|
|
실제 파일이 없다면
|
|
fallback placeholder로 구현해도 됨.
|
|
|
|
샘플을 코드에 강하게 의존시키지는 말 것.
|
|
|
|
|
|
========================
|
|
14. 성능 요구
|
|
========================
|
|
|
|
모바일 웹 기준.
|
|
|
|
목표:
|
|
60fps에 최대한 근접.
|
|
|
|
주의:
|
|
|
|
- 셀마다 실제 Mesh 생성 금지
|
|
- Mosaic는 반드시 shader 기반
|
|
- 수백 개 Plane 생성 금지
|
|
- texture 재생성 최소화
|
|
- resize 시 renderer.setSize 처리
|
|
- devicePixelRatio 제한
|
|
|
|
예:
|
|
|
|
renderer.setPixelRatio(
|
|
Math.min(window.devicePixelRatio, 2)
|
|
);
|
|
|
|
|
|
========================
|
|
15. 리소스 정리
|
|
========================
|
|
|
|
다음 경우 dispose 처리:
|
|
|
|
- 새 이미지 업로드
|
|
- 새 동영상 업로드
|
|
- material 교체
|
|
- geometry 교체
|
|
- component/scene 제거
|
|
|
|
Three.js:
|
|
texture.dispose()
|
|
material.dispose()
|
|
geometry.dispose()
|
|
|
|
Object URL:
|
|
URL.revokeObjectURL()
|
|
|
|
|
|
========================
|
|
16. UI 스타일
|
|
========================
|
|
|
|
디자인은 MVP 수준으로 간단하게.
|
|
|
|
스타일 방향:
|
|
- Dark UI
|
|
- Canvas가 가장 큰 비중
|
|
- 컨트롤은 반투명 panel
|
|
- 모바일에서는 하단 control sheet처럼 배치 가능
|
|
|
|
과한 디자인 작업 하지 말 것.
|
|
|
|
기능 검증이 우선.
|
|
|
|
|
|
========================
|
|
17. 중요 구현 원칙
|
|
========================
|
|
|
|
이 프로젝트의 핵심은 아래이다.
|
|
|
|
X:
|
|
사진에 단순 pixelation 필터 적용
|
|
|
|
O:
|
|
유리 plane과 반려동물 plane 사이에 공간이 있고,
|
|
사용자의 시점 변화에 따라
|
|
유리를 통해 보이는 반려동물의 위치와 형태가 달라지는 것.
|
|
|
|
따라서 Mosaic 효과 구현 시
|
|
정면에서 좌우로 드래그했을 때
|
|
결과 이미지가 확실히 변화해야 한다.
|
|
|
|
단순하게 전체 이미지를 translate하는 방식도 안 된다.
|
|
|
|
각 유리 cell/ridge마다
|
|
서로 다른 refraction 느낌이 보여야 한다.
|
|
|
|
|
|
========================
|
|
18. 1차 완료 기준
|
|
========================
|
|
|
|
아래 조건 만족 시 MVP 1차 완료.
|
|
|
|
1.
|
|
npm install
|
|
npm run dev
|
|
로 실행 가능
|
|
|
|
2.
|
|
사진 업로드 가능
|
|
|
|
3.
|
|
동영상 업로드 가능
|
|
|
|
4.
|
|
업로드한 미디어가 PetPlane에 표시됨
|
|
|
|
5.
|
|
Mosaic Glass 적용 가능
|
|
|
|
6.
|
|
마우스/터치 좌우 drag 시
|
|
모자이크 굴절 결과가 동적으로 변화함
|
|
|
|
7.
|
|
Reeded Glass 적용 가능
|
|
|
|
8.
|
|
Frosted Glass 적용 가능
|
|
|
|
9.
|
|
Strength 조절 가능
|
|
|
|
10.
|
|
모바일 viewport 정상 대응
|
|
|
|
11.
|
|
TypeScript 오류 없음
|
|
|
|
12.
|
|
콘솔에 지속적인 에러 없음
|
|
|
|
|
|
========================
|
|
19. 구현 우선순위
|
|
========================
|
|
|
|
아래 순서를 반드시 우선할 것.
|
|
|
|
1.
|
|
Three.js Scene
|
|
|
|
2.
|
|
이미지 Texture
|
|
|
|
3.
|
|
PetPlane
|
|
|
|
4.
|
|
GlassPlane
|
|
|
|
5.
|
|
Pointer drag
|
|
|
|
6.
|
|
Mosaic Shader
|
|
|
|
7.
|
|
시점에 따른 refraction
|
|
|
|
8.
|
|
VideoTexture
|
|
|
|
9.
|
|
Reeded Shader
|
|
|
|
10.
|
|
Frosted Shader
|
|
|
|
11.
|
|
Gyroscope
|
|
|
|
12.
|
|
UI polish
|
|
|
|
|
|
========================
|
|
20. 작업 방식
|
|
========================
|
|
|
|
먼저 현재 프로젝트 파일 구조를 확인한다.
|
|
|
|
이미 코드가 있다면
|
|
기존 구조를 최대한 유지하면서 구현한다.
|
|
|
|
필요 이상으로 추상화하지 않는다.
|
|
|
|
처음부터 완벽한 아키텍처를 만들기보다
|
|
실제로 동작하는 shader prototype을 우선한다.
|
|
|
|
특히 Mosaic Shader를 가장 먼저 눈으로 확인 가능한 수준까지 구현할 것.
|
|
|
|
각 주요 단계 완료 후
|
|
어떤 파일을 생성/수정했는지 간단히 정리한다.
|
|
|
|
작업 완료 후 아래도 보고한다.
|
|
|
|
- 생성/수정 파일 목록
|
|
- 구현된 기능
|
|
- 아직 구현하지 않은 기능
|
|
- 실행 방법
|
|
- 주요 shader 동작 원리
|
|
- 다음 개선 포인트 |