프로젝트명 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 동작 원리 - 다음 개선 포인트